Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Joven — a Cormac McCarthy ebook Spanish annotator

CI Python 3.11+ License: MIT

                                      :@@#                               =@@+.
                                     +@@@@                              #@@@@.
                                    .@@#@@                            .*@@=@@.
                                    #@@@@%                            #@@=@@#
                                    @@:@@*                           :@@+:@@
                                    @@=@@@                           %@@-:@@
                                    @@%@@@*.                        .@@=*+@@
                                    @@@+@@@=.                       *@%+#%@@
                                    *@@@@%@@%=.                    .@@*:-@@@
                                     @@@====#@%=.                  .@@@@*@@@.
                                     :#@@*::--#@@@#.%@@@@@:        #@@=+**@@.
                                       =@@#-*+@@@@@@@@@@@@@@%      @@@%*#*@@.
                                    -@@@@@@@@@@@-%@@@%..*@@@@@@-+@@@@@@%*+@@
                               +@@@@@@@@@@@@@@=:: ....:::::%@@@-@#*=:+#**@@@
                              =@@@@@@@*:.:::.:.::.:....... :@@@%@:**=:-%@@@+
                            .%@@@-  :..    ..:::  ....::::-=#@@@=:+=**#@%=
                      .. %@@@@@@+   :.:...=@@@@@@:  .:--:**==%@@+%#%*#*-
                  .@@@@@@@@@@@+=-   :::  :@@@@@@@+. :.:--#-*:@@@#%@+
             +@@@@@@@@@@@@%. :+-*:.      *@@=@@@@@.:=@@%:-*=%@@.
            .#@@@@@@+@@+=--==:==-::  .:=+@@+%@%@@@@@@@=@*%+ @@%.
            +@@@-===@-@==-----:::::  .===*@@@@@@@+@%#=*%@%@#@@
         %@@@@@%=-:=%@%:---==.-=-:. ..-++*%@@@@@:=*@@ @@@@@+@@
   ...%@@@@@=@#=..:-:=====-: :..::   .:-==+:+@@@*@@*+%@@%@@-@@
   #@@@@%*@@*@*=:-:-**=-: ...:-==:      .:=*:*+=+=:%@@@@=@@@@%
.#@@@@@:@*=@@===:.=+-. :::.:::.          *@-+*:.. =@@@@= %@@@=
*@@@=%==@@@#+=@@@@@@@+.: :              *@*%-:..:.@@@
@@@=*.:+:-@@@@@@@@%@@@%.::            .=@+@@:. :--#@@
@@@=@@%*@%@*=*@-@@##@@@%=:::: .:==-:::=@*+%-. .:=:.@@
%@@@@%-%%%@+@@@@@=*=-@@*=#=.:=+-:::.:-**+%+:  .:=::@@
  %@@@@@@=@*@@@@@@@@@@@*%=#@%=:-::-:-::*%*:..::::#@@@
   .=@@@@@@+@@@%-@@@@*.:%#*.@=*-:=..:===- ::::-=*@@@@
        #@@@@@@@@@@%   :=+@@% :=+-  .:-: : .::-+@@@@+
             .#@@@@@@@@@.              ..:=@@@@@@@@*
                . %@@@@@@@@@@@%%##@@@@@@@@@@@@@@@%-
                     .#@@@@@@@@@@@@@@@@@@@@@#+=:
                           -%@@@@@@@+:.

The boy sat down to read the book. He read of horses and of the country to the south and of men who spoke in a tongue he did not have. Escúchame, joven, said the old man, and the boy did not know what it was he was meant to hear. So he set down the book and took up the phone and fed the words into Google Translate one at a time like a man counting stones across a river, and when he looked up again the twilight had gone out of the valley and the page had gone cold and the old man was still standing there in the dark holding his counsel like a man holding a lamp for nobody.

So the boy wrote a python service. It went through the book and marked out every passage that was not English and put the question to a small model that ran on his own machine and asked nothing of anyone and told no one what it had seen. Where the Spanish had been it set a single asterisk and no more than that, and under the asterisk it laid the English down like a coin under a tongue, and the boy could take it or leave it as he pleased. He read the book through and he never once opened Google Translate. The old man went on speaking in his own language as he had always done and as he would go on doing, and the boy understood him, and the sun went down bloodred over the mesa and he read on by the pale light of the device until the battery gave out.

Joven is a command-line tool that finds the untranslated Spanish in an English novel and inserts tappable translation footnotes. You give it an EPUB you own; it gives you back an annotated copy for your e-reader, with the prose byte-for-byte unchanged. It was written specifically to make the Spanish sections of Cormac McCarthy's novel The Crossing more readable on a Kobo. It also works cleanly for other McCarthy westerns, and may work for other multilingual literature.

Everything runs on your machine against a local model: no API key, no per-book cost, nothing uploaded.


Why this exists

The Crossing is an English novel with a great deal of Spanish in it, and the book never translates or contextualizes it. 726 Spanish sentences and phrases, mixed into English paragraphs. Reaching for a dictionary breaks the trance the prose spent forty pages building; skimming past leaves a hole in the page.

Your e-reader's own Dictionary and Translate do not rescue you, because McCarthy uses no quotation marks — speech and narration run together in one stream, and the Spanish arrives in four distinct shapes:

A  Vaya con Dios.                          a whole paragraph, no English at all
B  Cuántos años tienes? the old man said.  Spanish speech, English dialogue tag
C  The matríz will not help you, he said.  English prose, Spanish loanword
D  Escúchame, joven, the old man wheezed.  Spanish opener, then English narration

C and D are why per-paragraph language detection fails in both directions: C is a false positive waiting to happen, D is a guaranteed miss. Solving that is most of what this tool is — see DESIGN.md §2 for the measurements.

What you get

A copy of your EPUB in which every Spanish passage carries a small *. Tap it and the translation appears in the reader's own footnote popup; ignore it and the page reads exactly as the author set it down.

A page of The Crossing on a Kobo, with a small asterisk after each Spanish line The same page with Kobo's Footnote preview open, showing the English translation
The page as McCarthy set it down, plus one asterisk per Spanish passage. The same page one tap later — the Kobo's own Footnote preview.
  • Unintrusive by construction. One asterisk. No inline brackets, no interlinear clutter, no colour. The footnote is opt-in, the way McCarthy's silence is opt-in.
  • The prose is untouched. Strip the inserted nodes from the output and what remains is byte-identical to the original — enforced by a test, verified on the real book.
  • It stays on your machine. A local qwen3:8b does the translating — 73 minutes for the whole novel, $0, and the book never leaves the laptop.
  • Precision over recall. A spurious footnote on Go on. is worse than a missed one, so ambiguous cases escalate to the model rather than guess. Measured on a McCarthy novel with no Spanish in it: six false positives in 177,000 words.

How it works

Five commands, two of which do the real work. Detection is two-tier: a cheap statistical pass judges every sentence, and only the fraction it cannot call is put to the local LLM.

flowchart TD
    START(["your book.epub"])

    subgraph DETECT["joven detect · ~73 min · $0"]
        direction TB
        EXTRACT["extract text units<br>addressable by file, element path, offset"]
        SEG["segment into sentences<br>character offsets preserved"]
        T1{"TIER 1 — lingua triage<br>statistical, instant, free"}
        T2{"TIER 2 — local LLM<br>qwen3:8b via Ollama, with surrounding context"}
        MERGE["merge contiguous Spanish<br>one footnote per paragraph"]
        EXTRACT --> SEG --> T1
        T1 -- "abstains — 21% of segments" --> T2
        T1 -- "confident Spanish" --> MERGE
        T2 -- "yes, and here is the span" --> MERGE
    end

    SKIP["left alone<br>but still recorded in trace.jsonl"]
    T1 -- "confident English" --> SKIP
    T2 -- "not Spanish" --> SKIP

    SIDECAR[("annotations.json<br>source of truth, human-editable")]
    REVIEW["joven review<br>approve / edit / reject<br>decisions survive re-detection"]
    MERGE --> SIDECAR
    SIDECAR <--> REVIEW

    subgraph RENDER["joven render · idempotent"]
        direction TB
        ANN["insert markers and note documents<br>EPUB 2 → 3 package upgrade"]
        EPUB3(["book.annotated.epub"])
        KEP["kepubify"]
        ANN --> EPUB3 --> KEP
    end

    START --> EXTRACT
    START -. "the original, never edited in place" .-> ANN
    SIDECAR --> ANN
    KEP --> OUT(["book.annotated.kepub.epub<br>copy this to the Kobo"])
    EPUB3 -. "validated by" .-> VERIFY["joven verify<br>12 checks · epubcheck · text-preservation invariant"]

    classDef artifact fill:#fff4e0,stroke:#b8860b,stroke-width:2px,color:#111
    classDef endpoint fill:#e6f4ea,stroke:#2e7d4f,stroke-width:2px,color:#111
    classDef muted fill:#f2f2f2,stroke:#999,color:#444
    class SIDECAR artifact
    class START,OUT endpoint
    class SKIP muted

The EPUB is never edited in place. annotations.json is the durable artifact, and rendering is a pure function of (original EPUB + sidecar). Corrections mean editing the sidecar and re-rendering — never re-translating — and re-running detection merges into your edits instead of clobbering them.

Inside one Tier-2 call

Tier 2 is not a translation step with a question bolted on. It answers three things at once — is this Spanish, which part of it is Spanish, and what does it mean — which is why it is an instruct model and not a translation engine. An NMT engine can only answer the third, and would cheerfully "translate" Yes mam.

flowchart TB
    subgraph REQUEST["one /api/chat request to Ollama"]
        direction TB
        SYS["SYSTEM<br>the task, the JSON contract, and a warning<br>that McCarthy's dialect English is English"]
        FS["6 FEW-SHOT TURNS<br>one per mixing pattern, plus two dialect traps<br>added after a traced run exposed them"]
        USR["USER<br>up to 400 characters of preceding prose, fenced<br>and marked do-not-translate, then the paragraph"]
        SYS --- FS --- USR
    end

    USR --> MODEL["qwen3:8b · temperature 0 · thinking off<br>reply constrained to a JSON schema"]
    MODEL --> OUT["is_spanish · spanish_text · translation"]

    OUT --> G1{"is the translation<br>just the source tidied up?"}
    G1 -- yes --> VETO["vetoed — a footnote<br>that teaches nothing"]
    G1 -- no --> G2{"one Spanish word<br>inside English prose?"}
    G2 -- yes --> LOAN["suppressed —<br>embedded loanword"]
    G2 -- no --> KEEP["annotation, span narrowed<br>to spanish_text"]

    VETO --> TRACE[("trace.jsonl<br>every outcome, with the raw reply")]
    LOAN --> TRACE
    KEEP --> TRACE

    classDef keep fill:#e6f4ea,stroke:#2e7d4f,stroke-width:2px,color:#111
    classDef drop fill:#f2f2f2,stroke:#999,color:#444
    classDef store fill:#fff4e0,stroke:#b8860b,stroke-width:2px,color:#111
    class KEEP keep
    class VETO,LOAN drop
    class TRACE store

There are two prompts, not one, and which runs depends on what Tier 1 already decided. The split was forced by a bug in each direction:

Tier 1 said Prompt Because
abstained (the band) adjudicate — is this Spanish, and if so which part? The genuine open question
confident Spanish translate only — this is Spanish; do not second-guess it A combined prompt let the model veto Tier 1's correct calls. It labels Dieciseis. English.

Skipping the model entirely for Tier-1 accepts was the first implementation, and it shipped annotations with no translation at all — blank popups on the device.

The prompts themselves, the context-bleed problem, a real call from the trace, and the three gates in detail: docs/anatomy-of-a-call.md.

The components

src/joven/ is about 4,500 lines. The pieces map onto the pipeline above:

Module Job
epub/ Byte-level zip surgery — copy every archive entry verbatim, reserialize only the XHTML that actually changed
detect/ segment.py splits paragraphs into sentences; triage.py is Tier 1 (lingua + tag-stripping); pipeline.py runs both tiers and merges contiguous Spanish into one footnote per paragraph
translate.py Tier 2 — the Ollama client, the Translator protocol, and the similarity veto that suppresses no-op "translations"
dialogue.py McCarthy's , he said. vocabulary, shared by every gate that must not measure it
model.py The annotations.json sidecar — content-hash IDs, statuses, merge semantics
render/ Marker insertion, the EPUB 2→3 package upgrade, one-note-per-file footnote documents
kepub.py The kepubify hand-off that produces the file the Kobo wants
review.py, suspicion.py The localhost review UI, and the heuristic that sorts likely-wrong translations to the top of it
verify.py The 12-check integrity gate, including the text-preservation invariant
trace.py One JSONL record per segment, annotated or not, so any missing footnote can be explained afterwards

Requirements

Platform macOS on Apple Silicon — where this was developed and device-verified. Linux and Windows 10/11 run the full suite in CI, and a whole book has been through the pipeline on each.
Memory 16 GB, which is what sets the model ceiling at 7–14B parameters
Disk ~6 GB for the model, plus room for the outputs
Python 3.11+ (developed on 3.13)
A package manager Homebrew on macOS, Scoop or winget on Windows — for the three external binaries below
Java epubcheck is a JAR and needs a JVM. macOS ships one at /usr/bin/java; on Windows install a JDK (winget install Microsoft.OpenJDK.21) if java -version comes back empty
Reader A Kobo (sideloaded over USB) is the verified target — tested on firmware 4.45.23697. Apple Books renders the footnotes as popups too.

Three external tools do work Python should not:

Tool Why
ollama Runs qwen3:8b (5.2 GB) locally. Chosen on a benchmark, not a hunch — docs/model-selection.md.
kepubify Converts the finished EPUB into Kobo's own KEPUB flavour, which renders better on device
epubcheck The reference EPUB validator — the external gate on our output

Bring your own EPUB. Joven reads a file you already have; it does not fetch, share, or unlock anything, and it refuses DRM-protected files outright. Books that merely obfuscate an embedded font are read normally — that is not DRM, and the scrambled font is carried through untouched.

Install

1. uv, which manages the isolated environment the tool lives in:

brew install uv

Any other install route works too — see the uv docs.

2. The three external binaries (what each is for is in the table above):

brew install epubcheck kepubify ollama
On Windows
winget install astral-sh.uv Ollama.Ollama Microsoft.OpenJDK.21
scoop install kepubify epubcheck

Without Scoop, both binaries install by hand. kepubify is a single executable — download kepubify-windows-64bit.exe, rename it kepubify.exe, and put it in a folder on your PATH.

epubcheck needs one extra step, because the official download is a JAR and no launcher — there is nothing for PATH to find. Unzip epubcheck and point Joven at the jar:

setx JOVEN_EPUBCHECK_JAR "C:\tools\epubcheck-5.1.0\epubcheck.jar"

Open a new terminal afterwards, as setx only affects later sessions. Skipping this does not fail loudly — joven verify reports epubcheck as SKIPPED and passes, so the one external check on the output silently stops running.

3. Joven itself:

uv tool install joven-ebook-annotator

This installs into ~/.local/bin, which is often not on your PATH. If joven --help comes back with command not found, add it once:

uv tool update-shell

That edits your shell profile, so open a new terminal afterwards. pipx install joven-ebook-annotator works the same way if you would rather use pipx.

Both routes pull prebuilt wheels. Worth knowing that lingua carries its language models inside the wheel, so this step moves about 170 MB.

4. The model, once:

ollama serve &            # leave running
ollama pull qwen3:8b      # 5.2 GB

Check it all landed:

joven inspect book.epub

There is no Homebrew formula for Joven itself. One dependency (lingua-language-detector) publishes no source distribution at all — only per-platform wheels — which a Homebrew Python formula cannot consume without hand-pinned wheel URLs per architecture and per CPython minor version. Two commands that work beat one command that breaks on the next python@ bump.

To work on the code rather than use it, see Development.

Use

One book, five commands, in order:

joven inspect book.epub                                    # structure, DRM, word counts
joven detect  book.epub -o annotations.json --trace trace.jsonl
joven review  annotations.json --epub book.epub            # triage, suspect passages first
joven render  book.epub annotations.json -o out/           # EPUB 3 + KEPUB for the Kobo
joven verify  out/book.annotated.epub --original book.epub

inspect reports what you are holding — EPUB version, DRM, spine, word counts. Run it first; it fails fast on a file the rest of the pipeline cannot use.

detect is the slow step: about 73 minutes for a 150,000-word novel, single threaded, $0. It writes annotations.json (the sidecar you will edit) and, with --trace, a JSONL record of every segment it looked at. Re-running it is safe — your review decisions are sticky.

The trace is also the run's recovery log: it is flushed a record at a time, so an interrupted run loses nothing that the model already answered.

joven detect book.epub -o annotations.json --trace trace.jsonl --resume trace.jsonl

--resume reuses every model answer the trace already holds and pays only for the segments the previous run never reached. Tier 1 and every suppression gate still run over the whole book, so a resumed run picks up threshold changes rather than replaying stale conclusions; recorded errors are retried rather than inherited.

review opens a local page listing every annotation with the surrounding prose and the Spanish highlighted. Approve, edit, or reject (a/r/e); each decision writes straight to the sidecar the moment you make it, so quitting mid-review loses nothing. Suspect annotations sort first, each badged with the reason — untranslated: historic, garbled source: Conoc16 — and everything else follows in book order.

render applies the sidecar to a fresh copy of the original and emits two files: a spec-clean .epub (the verifiable intermediate, which is what epubcheck validates) and a .kepub.epub (what you copy to the Kobo). It is idempotent and never touches your source file.

verify runs the 12-check integrity gate — the text-preservation invariant, epubcheck, noteref→footnote resolution, and the rest.

Then copy the .kepub.epub to the Kobo's root over USB, eject, and the device imports it.

Missed a passage while reading?

joven add annotations.json --epub book.epub \
  --find "Bueno pues" --translation "Well then"

Added entries are marked edited, so re-detection will never overwrite them.

For tuning, tracing, and the debug flags, see docs/troubleshooting.md.

Results

Working end to end on real books: lossless round-trip, two-tier detection with a full decision trace, EPUB 2→3 upgrade, footnote rendering device-verified on a Kobo, KEPUB output, a review pass, and a finished book on the device.

Three McCarthy novels have been through the full pipeline. The third is a control — Suttree has essentially no Spanish in it, so almost every footnote it produces is a false positive you can read and count.

words segments escalated footnotes
The Crossing (Knopf 1994) 151,865 12,302 2,556 (21%) 726
All the Pretty Horses 101,182 9,310 1,814 (20%) 270
Suttree — control 177,257 17,344 2,548 (15%) 2

Two footnotes in a 177,000-word English novel — one per ~88,000 words. Of 2,548 escalations the model correctly rejected 2,544 and vetoed 2, which puts precision on the "leave English alone" side above 99.9%.

That control run is what found the defects fixed in v1.0.0b3. It first produced six false positives, of which three were Latin liturgy — a two-language detector cannot answer "neither", so it called Stabat Mater Dolorosa. Spanish at 0.94. Both tiers were blind to it and both are now fixed; see DESIGN.md §2.6.

The two that remain are honest hard cases: Ay. (English here, Spanish elsewhere) and No suh. → "No sir.", dialect English that the similarity veto misses at a 0.67 ratio against its 0.75 threshold.

Wall clock is ~1.5 s per escalated segment on an M-series laptop — 47 minutes for All the Pretty Horses, 63 for Suttree, 73 for The Crossing — at $0.

Known limitations, and what the project has and has not proven, are in CHANGELOG.md.

Development

git clone https://github.com/vyanhursky/joven && cd joven
python3.13 -m venv .venv
./.venv/bin/pip install -e '.[dev]'
./.venv/bin/pytest                       # 379 tests, synthetic fixtures only
./.venv/bin/ruff check src tests tools

# opt in to the real-book tests (the book is never committed)
JOVEN_TEST_EPUB=/path/to/book.epub ./.venv/bin/pytest

On Windows the venv puts its executables in Scripts\, not bin/:

py -3.13 -m venv .venv
.\.venv\Scripts\pip install -e ".[dev]"
.\.venv\Scripts\pytest
$env:JOVEN_TEST_EPUB = "C:\path\to\book.epub"; .\.venv\Scripts\pytest

One Windows-only trap, and it is not Joven's: pip install -e . cannot replace a file another process has open, so reinstalling while joven detect is still running leaves a half-uninstalled package behind — a ~oven_ebook_annotator* directory in site-packages and ModuleNotFoundError: No module named 'joven' from a venv that looks fine. Delete that directory and reinstall.

Tests never hit the network — a StubTranslator stands in for the LLM everywhere. Two guard scripts run in CI, each of which has caught a real bug that survived a careful manual read of the same files minutes earlier:

python tools/check_docs.py               # every documented command and flag exists
python tools/check_no_book_content.py    # no book text in tracked files

Model benchmarks are separate tools, not tests, because they need a running Ollama:

python tools/bench_models.py             # the LLM in isolation
python tools/bench_pipeline.py           # the two-tier system that actually ships

Further reading

DESIGN.md Why the architecture is shaped this way — the measurements behind every decision, what the device tests overturned, and the work deliberately left undone
docs/model-selection.md The local-model benchmark: why qwen3:8b
docs/anatomy-of-a-call.md What the local model is asked, what it may answer, and the gates that check it
docs/troubleshooting.md Tracing a missing footnote, improving translation quality, debug flags, and Kobo quirks
docs/releasing.md Cutting a release: the one-time PyPI trusted-publisher setup, and what CI does with a tag
CHANGELOG.md Release notes and known limitations

Licence

The code is MIT — see LICENSE. Use it, fork it, ship it.

The books are not. The annotated output is a derivative of a copyrighted work: read it, don't distribute it.

The repository deliberately tracks no book content. The hazard is not the EPUB but what the pipeline derives from it — a full decision trace holds every segment's source text verbatim, which for a 150,000-word novel is the entire book. .gitignore excludes those by pattern, and tools/check_no_book_content.py verifies it by content, diffing every tracked file against the book itself:

python tools/check_no_book_content.py path/to/book.epub

Docs and tests quote short passages to illustrate the detection problem, and the two screenshots above are photographs of the author's own copy on his own device.

Metadata

Release files for joven-ebook-annotator 1.0.0b4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for joven-ebook-annotator 1.0.0b4
File Size Uploaded
joven_ebook_annotator-1.0.0b4.tar.gz 88.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for joven-ebook-annotator 1.0.0b4
File Interpreter ABI Platform
joven_ebook_annotator-1.0.0b4-py3-none-any.whl Python 3 none any Details

Total release size: 176.1 kB

Release files / joven_ebook_annotator-1.0.0b4.tar.gz

Download URL joven_ebook_annotator-1.0.0b4.tar.gz
Size 88.0 kB
Tags Source
SHA-256 checksum
How to use checksums
32bca058bf6c9239dc920cae967431499774925d85f15a78728141aa4ed9d94a
BLAKE2b-256 checksum
How to use checksums
dd41d4bc2c9b17d0a6aec9bd4bac2ef98b9f8e3a8b8cc16393bb58cd493c0a28
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log

Release files / joven_ebook_annotator-1.0.0b4-py3-none-any.whl

Download URL joven_ebook_annotator-1.0.0b4-py3-none-any.whl
Size 88.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d89008eab44110d227d2f47087b3323b9e8d5a8b00b0a526b11104a0395f819d
BLAKE2b-256 checksum
How to use checksums
3514922fc708511dd3d4697fef219ff2065cf6460aaedde766b7e4fcb0b6e14b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 2, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page