cli-ux records the conventions every command follows: one accent colour used
only for the tool's own chrome, a stdout a program consumes carrying nothing
else, decoration only where there is a terminal to draw it on, errors that name
the fix, help as a lowercase imperative phrase, British spelling, and what a
long operation must keep saying while it works. Read it before adding a
surface, rather than copying whichever neighbour is nearest.
cmd/spinloop/palette.go— the colours and the spinner every surface draws from, in two groups that must not be swapped: the brand colours for the tool's own chrome, the state colours for what an engine is doing. (cli-ux)
This file provides guidance to coding agents, such as Claude Code, when working with code in this repository.
spinloop covers a model's whole lifecycle — deploy it, host it, watch it, and point a coding agent at it — driven from one declarative Spinloop file:
- Harness configuration — deep-merges provider settings into a coding agent's config. Three harnesses are supported: opencode (config under
${XDG_CONFIG_HOME:-$HOME/.config}/opencode), Pi (~/.pi/agent/models.json), and lucinate (~/.lucinate/connections.json). The harness is chosen at runtime — never baked into a Spinloop file — so the same selection applies to any of them. - Local hosting —
spinloop serveruns an inference engine (llama.cpp, oMLX) directly;spinloop daemonsupervises one instead, exposing an HTTP control API for starting/stopping it and reading its status, metrics, and logs. - Cloud deployment —
spinloop remotedrives a scale-to-zero GPU instance through its own AWS control plane (remote/): starts it on demand, deploys a model to it, and stops it when idle. - Fleet monitoring and control —
spinloop fleetobserves and drives every engine you run — local daemons and remote environments alike — across machines from one place, including an interactive dashboard, and can route a harness launch to whichever node already has (or can load) the wanted model.
Architecture and design decisions are specified in openspec/specs/ — one directory per feature area (fleet-routing, alias-registry, provider-catalog, opencode-integration, …), each a spec.md of SHALL requirements with scenarios. That is the source of truth for behavior: read the relevant spec before changing how something works, and update it as part of the change (see openspec/changes/ and the openspec-* skills).
Implementation gotchas and cross-cutting rationale that aren't behavior requirements — things worth knowing before you touch the code, not things a user or a spec scenario cares about — live in docs/internals.md.
User-facing documentation (install, commands, the Spinloop file format, the HTTP API) lives under docs/ — see docs/README.md.
This file stays an orientation map: what's where, and which spec or doc governs it.
go test ./... # run the suite
go test ./... -cover # with coverage (keep total >= 80%)
go vet ./... # vet
go build -o spinloop ./cmd/spinloop # build the CLI binary
gofmt -w ./... # formatRun a single test: go test -run TestWriteConfig_Idempotent ./...
The binary lives under cmd/; domain logic is split into internal/ packages so each concern is isolated and independently testable. Spec pointers below name the openspec/specs/ directory that governs a package's behavior — read it before changing that behavior.
cmd/spinloop/commands.go+main.go— the Cobra command tree and command bodies:add/remove/apply/unapply/show/export/harness/alias/unalias. (harness-management,alias-registry)cmd/spinloop/serve.go— theservecommand: engine dispatch (llama.cpp, oMLX) and the Spinloop/preset→flags mappings. (local-serving,inference-runners)cmd/spinloop/complete.go— tab completion, built on Cobra's__completeengine. (shell-completion)internal/config— spinloop's own config file (${XDG_CONFIG_HOME:-~/.config}/spinloop/config.json): default-harness preference and the alias registry. A leaf package — stdlib only, never importsinternal/spinloop. (config-location,alias-registry)internal/spinloop— the Spinloop file format and the sharedSelectiontype. A pure grammar leaf: no I/O. (spinloop-files)internal/spinloopsrc— resolves and fetches a Spinloop-family reference (path,PRESET, path-formREMOTE) that may be local or anhttp(s)URL. (remote-spinloop-sources)internal/harness— the harness abstraction: theHarnessinterface, the opencode/Pi/lucinate adapter registry, and runtime resolution viaharness.Resolve. Start here when adding another harness. (harness-management)internal/catalog— the embedded provider catalogue (//go:embed providers.yaml) and the block builders that turn a selection into an opencode or Pi provider entry. (provider-catalog,provider-selection)internal/opencode— opencode config IO: JSONC read/merge/write via RFC 6902 patches on the hujson AST, preserving comments and sibling config. (opencode-integration)internal/pi— Pi'smodels.jsonIO: deep-merge of one managed provider, preserving siblings and unknown fields. (pi-integration)internal/lucinate— lucinate'sconnections.jsonIO: one managed connection, no secret ever written to disk. (lucinate-integration)cmd/spinloop/fleet.go,metrics_render.go,status_render.go,fleet_dashboard.go,dashboard_*.go— thefleetcommand group and its Bubble Tea dashboard (the CLI's only TUI). (fleet-client,fleet-config)internal/fleet— the fleet client: theNodeinterface (daemonNode/remoteNode), concurrent fan-out (each reading stamped with the time its call returned), theStartPhasea start reports and the one function that renders it, and routing/waking a node for a launch. (fleet-client,fleet-config,fleet-routing,remote-node)examples/fleet-docker/— a runnable multi-node fleet that doubles as the fleet integration test, run per PR by CI. (fleet-docker-example)cmd/spinloop/remote.go+internal/remote— theremotecommand group and the scale-to-zero cloud GPU control plane (SigV4-signed Lambda Function URL calls — the repo's only AWS/network dependency). (remote-environments,endpoint-lifecycle,endpoint-provisioning,remote-endpoint,remote-seed,weight-seeding,remote-keep,remote-start-probe)internal/daemon— the engine supervisor and the HTTP control API.Routes()inapi.gois checked againstdocs/openapi.yamlbyopenapi_test.go— keep them in sync when adding an endpoint. (daemon-api,daemon-api-contract,engine-activity,engine-metrics,api-logging,serve-daemon)internal/contextsize— parses human-friendly sizes (128k,1.5m) forCONTEXT/OUTPUT.internal/preset— parses llama.cpp-style preset.inifiles, dialect-aware (LlamaCpp vs. OMLX). (inference-runners)internal/catalog/providers.yaml— externalised provider plumbing (URLs, key env vars, npm packages) — no model ids. Add providers here, not in Go. (provider-catalog)examples/— runnable guides, each a directory with a README and aSpinloop.remote/— the TypeScript CDK projectspinloop remotedrives (Lambdas, EC2 Image Builder, S3 weights), built and tested by pnpm with its own CI job. Public repo: nothing identifying a deployment (account ids, ARNs, hosts, bucket names) may be committed — enforced byscripts/check-no-cloud-identifiers.sh.
opencode merges stay idempotent and never drop unrelated config or comments; Pi and lucinate merges preserve sibling entries and unknown fields; no harness ever writes a resolved secret to disk; config files that may hold a key are written 0600; the harness never appears in a Spinloop, and neither does an alias — both are machine-local; spinloop's own config.json is only ever written read-modify-write; __complete never returns an error and never writes to stderr, whatever the state of the config or catalogue.
Warn the user if they are going to merge a branch with un-archived openspec changes.