Dependencies¶
Every dependency needs a reason. If one below stops earning its line, it should go. The bar is: would writing this ourselves be worse than carrying it?
Two things are deliberately not here, and both were close calls, see Deliberate omissions.
Go¶
| Module | Why |
|---|---|
github.com/tailscale/tailcat |
Built-in encrypted private transport for agents and, through zoomies gateway, for providers on a home network; NAT traversal and relay fallback without installing and supervising a separate tunnel process. Pinned in go.mod, which is the one place its version is written; requires Go 1.27.1. |
tailscale.com |
Tailcat’s network and key types, plus a local DERP test relay to verify real tunnel behaviour without hosted infrastructure. |
modernc.org/sqlite |
Pure-Go SQLite. No cgo means the binary is static, cross-compiles to five platforms from one machine, and runs on distroless. |
github.com/go-chi/chi/v5 |
Router. net/http's 1.22 mux nearly suffices, but chi's middleware chaining and sub-router mounting keep the API's role and audit middleware readable. Small, stable, no reflection. |
github.com/google/go-github/v88 |
The GitHub REST client. Hand-rolling the Actions endpoints, their pagination and their error shapes would be a permanent maintenance tax for no gain. |
github.com/bradleyfalzon/ghinstallation/v2 |
GitHub App installation tokens: JWT signing, token caching, refresh. It is 400 lines we would otherwise get subtly wrong. |
golang.org/x/crypto |
argon2 for password hashing. The algorithm is not something to implement. |
golang.org/x/oauth2 |
OIDC's authorisation-code flow. |
github.com/coreos/go-oidc/v3 |
OIDC discovery and ID-token verification, including JWKS rotation: single sign-on, and the GitHub Actions tokens Zoomies-only AI Context uploads carry. Optional features, well-maintained library. |
github.com/prometheus/client_golang |
The metrics endpoint. The exposition format has enough edge cases that hand-writing it is a false economy. |
github.com/prometheus/client_model |
The metric type a test reads a value back out of (dto.Metric), so a timing can be asserted on without parsing the text format. Only a test imports it, and client_golang already requires it, so it adds nothing to the build. |
gopkg.in/yaml.v3 |
zoomies.yaml. Strict decoding turns a misspelled key into an error naming the line. |
github.com/charmbracelet/huh |
The installer's prompts, select, input, confirm, with validation. It is what makes zoomies init feel like a product rather than a script. It brings bubbletea with it as its runtime, which is why that appears in go.mod as indirect and has no row of its own. |
github.com/charmbracelet/lipgloss |
Styling for the installer and the CLI's table output. |
golang.org/x/term |
Terminal detection, so the CLI and installer degrade to plain output when piped. |
golang.org/x/sys |
The Windows half of the agent: the service control dispatcher a process started by the service manager must answer, the job object that lets killing a runner kill what it started, and the process and disk queries POSIX answers with a signal and a statfs. It was already in the tree as a transitive dependency; making it direct added no module. |
Standard library for everything else: net/http, log/slog, database/sql,
crypto/*, embed, os/exec, context, testing.
npm¶
Runtime (shipped to the browser):
| Package | Why |
|---|---|
svelte |
The framework. Runes, no virtual DOM, and a compiled output small enough to hit the 200 KB shell budget. |
@tanstack/svelte-table |
Headless data-grid logic (sorting, filtering, column visibility, row selection) with our own markup. Reimplementing this correctly, including keyboard navigation over a virtualised body, is weeks. |
@xterm/xterm + addon-fit, addon-search, addon-webgl |
The log viewer. 100k lines without jank is a hard requirement, and xterm's WebGL renderer is the reason it is achievable. |
@lucide/svelte |
Icons. Imported individually so tree-shaking keeps only what is used. |
@fontsource-variable/inter, @fontsource/jetbrains-mono |
Self-hosted fonts. An air-gapped install must not make a third-party font request, and neither should anyone else's browser. |
Build and test only:
| Package | Why |
|---|---|
vite, @sveltejs/vite-plugin-svelte |
Build and dev server. |
tailwindcss, @tailwindcss/vite |
Utility CSS. v4's CSS-first @theme reads our design tokens directly, so there is exactly one source of truth for a colour. |
typescript, svelte-check |
Types, and type checking inside .svelte files. |
@types/node |
Node's own types, for the handful of files that run under Node rather than in the browser: vite.config.ts, the Playwright specs and the test harness. Without it those files typecheck against a DOM that has no process. |
openapi-typescript |
Generates the API client's types from api/openapi.yaml. The UI cannot drift from the API without the build failing. |
@playwright/test |
UI tests against the real binary. |
eslint, typescript-eslint, eslint-plugin-svelte, @eslint/js, globals |
Linting. |
prettier, prettier-plugin-svelte |
Formatting. |
Managed AI Context workflows¶
| Package | Why |
|---|---|
repomix 1.18.1 |
Scans and packs repository source for AI context. The setup template embeds its integrity-locked npm dependency tree; Actions installs it in a temporary tool directory with lifecycle scripts disabled. It is not a controller or browser dependency. Source bytes come from the pinned Git commit so Repomix boundary trimming cannot change line numbers. |
Runner images¶
Runner images also carry gawk to extract the runner's masked build console
lines and discard verbose worker noise before it reaches the live relay, without
adding a second runtime or buffering a build's output. It is gawk on the apt
images too, in place of the mawk they ship as awk: mawk reads a pipe in large
blocks, so a quiet build's lines would not reach the relay until it ended.
The docs site¶
docs/requirements.txt, pinned. These never reach a user's machine; they
build zoomies.sh and nothing else.
| Package | Why |
|---|---|
mkdocs |
Turns docs/ into the site, so the website is the repository's own documentation rather than a second copy of it. Pinned below 2.0 deliberately; the reason is in docs/requirements.txt. |
mkdocs-material |
The theme. It carries the search, the light/dark palettes, the tabbed blocks the quick start uses, and the Mermaid integration below. |
pymdown-extensions |
The fenced-block, tabbed and admonition syntax the pages are written in, including the custom mermaid fence. |
| Mermaid | Diagrams. Material fetches it from a CDN in the reader's browser, only on a page that has one, and colours it from the palette above, so a diagram lives in the Markdown beside what it explains, renders on GitHub as well, and there is no exported image to go stale. |
pillow |
Encodes the screenshots in docs/screenshots/ as lossless WebP, at under half the size of the PNGs Playwright takes. Only make screenshots needs it, so it is not in docs/requirements.txt and the site builds without it; the browser can write lossless WebP itself but compresses it six times worse. |
Coverage reporting¶
| Action | Why |
|---|---|
codecov/codecov-action |
Publishes the Go coverage report so coverage is visible in Codecov and pull requests. Pinned to a release commit; GitHub OIDC authenticates uploads without a stored secret. |
The Codecov GitHub App must have access to eyupio/zoomies. CI uploads
coverage.out after successful Go tests and fails visibly if the upload fails.
Fork and Dependabot pull requests keep the tests and coverage summary but skip
the upload because their restricted permissions do not provide the OIDC token.
make cover measures with -coverpkg=./..., and that flag is load-bearing
rather than a detail. Without it Go credits a package only for the lines its
own _test.go files run, and this repository deliberately tests several things
through the layer above them; the controller's migration service is driven
entirely by internal/api's handler tests. Those files reported 0%, which is
worse than a low number: it is a dashboard pointing the next contributor at the
best-covered file in the tree.
Deliberate omissions¶
github.com/docker/docker. The official client drags in a very large
dependency tree (containerd, gRPC, OpenTelemetry, swarm types) for what
amounts to a dozen JSON endpoints over a unix socket. internal/backend/dockerapi.go
is a few hundred lines of net/http against the documented Engine API, and it
buys Podman support almost free, because podman.sock speaks the same protocol.
The one genuinely fiddly part, demultiplexing the log stream's 8-byte frame
headers, is unit-tested.
A Proxmox VE client. The Go clients for it (luthermonson/go-proxmox,
Telmate/proxmox-api-go) model most of a hypervisor for what Zoomies asks of
one: clone a template, start it, write a file into the guest, destroy it, and
poll a task. That is a dozen endpoints, and internal/provider/proxmox reaches
them with the same hand-rolled net/http approach as the Engine API client
above. The deciding argument is not the dependency tree, though: it is that the
one classification the whole design rests on (whether a deadline or a reset
happened before or after the request body went out, which is the difference
between "nothing happened" and "we do not know") can only be made inside the
transport, and no third-party client exposes it. A client that reports both as
one error would have Zoomies retry a create that already worked, and rent a
second machine nobody is tracking.
An S3 SDK. The backup remotes need four verbs (put an object, get it,
list a prefix, delete a key) and signature version 4, which is forty lines of
HMAC with a published definition. internal/backup/s3.go is that, against the
same hand-rolled net/http approach as the two clients above, and it speaks to
every implementation for the same reason a hand-rolled client speaks to Podman:
ListObjectsV2 and SigV4 are what they all agree on. aws-sdk-go-v2 would bring
its own configuration, credential-provider and middleware stacks (and its own
release cadence and vulnerability surface) so that a controller can write one
file a night; minio-go is smaller and still an order of magnitude more client
than four requests deserve. The one part with an external definition, the
canonical request's encoding, is unit-tested against that definition rather
than against itself.
A charting library. The Overview needs sparklines and utilisation bars. Those are inline SVG paths of a few dozen lines each. The smallest credible charting library is larger than the entire rest of the app shell.
A client-side router. Seventeen routes, two of them with a parameter
segment, do not need a routing library. web/src/lib/router.ts is small enough
to read in one sitting and does exactly what the History API already offers.
A state-management library. Svelte 5 runes are the state management library.
Reviewing a new dependency¶
- Could the standard library, or fifty lines of our own, do it?
- Is it maintained, and does it have a release in the last year?
- What does it pull in transitively? (
go mod graph,npm ls --all.) - If it ships to the browser, what does it cost against the shell budget?
- Add a line to the right table above, in the same voice: why, not what.
A pull request that adds a dependency without a line here should be asked for one.