The CLIs
Two command lines, rendered from one contract. services.dev is yours, to write, check, serve and test a brand. npx <brand> is your buyers', their agents' and your members'.
Your brand has two command lines, for two different people.
services.devis yours. It writes a Service, checks it, serves it on localhost, and drives every branch with magic values and test clocks.npx <brand>is your buyers'. It's published under your own package name, in your brand's words, and never names services.dev. Your buyers, their agents and the members of your Pool run it. Each deploy publishes it.
Both render one column of the contract. Every command a buyer runs is a row of the operations registry, the same row that gives the API its route, MCP its tool and the SDK method its name, so a command can never do something the other surfaces can't.
In an empty folder, one command writes a Service and serves it. The banner prints the CLI's line beside every other surface's:
$ mkdir lingua && cd lingua
$ npx services.dev
wrote package.json · services/translation.ts · services/translation.md · AGENTS.md
lingua · AI delivers every job.
site http://localhost:8787/translation
api POST http://localhost:8787/jobs {"service":"translation"}
key sk_test_7Hq2 · a call with no key uses it, on localhost only
mcp http://localhost:8787/mcp · stdio: npx services.dev mcp
cli npx services.dev translation k=v …
sdk connect({ origin: 'http://localhost:8787' }).translation.order(input)
docs http://localhost:8787/llms.txt
buyers http://localhost:8787/portal
you http://localhost:8787/console
AI and money are simulated until you wire them. $0.42 a call is an example.Your first paid job runs it to an accepted job.
help groups every command by who runs it: you, a buyer or their agent, the person at the line, and you again, testing in the sandbox.
magic values: none
$ npx services.dev help
services.dev
You
npx services.dev write a Service if this folder has none, then serve it
init [name] write services/<id>.ts, its brief and AGENTS.md
dev [--port 8787] [--members N --market tight|normal|flush] serve every Service on localhost; an agent orders a first job
check [--json] what each Service sells and leaves you, and anything to fix
sdk [--out sdk] write the brand's typed client: index.js, index.d.ts and package.json
diff <before> <after> what a change does on the API, CLI, MCP, SDK, Site, Docs and App
verify [--url <url>] [--surface http|mcp|all] [--suite <file|url>] run a brand's conformance suite against it, or any suite@1 document: pass, fail, skipped
mcp [--url <url>] serve this folder's MCP server on stdio, or relay to a running brand's
tasks offer <task> <member> name the member a Task goes to
clocks assign <clock> <member> assign the member who answers for a clock
members login <member> mint a member's own session on localhost
asks get <ask> read an ask
jobs export <job> one job's evidence bundle
jobs ledger <job> one job, as the owner reads itSeven commands read the folder: init, dev, check, sdk, diff, verify and mcp. Every other command is generated from the operations registry: add a row and its command appears, with no CLI code to write. A Service is ordered by its own id, so translation is a command the moment the file exists.
A command's own help prints the row it runs, as every other surface renders it:
$ npx services.dev jobs send-back --help
npx services.dev jobs send-back <job> <note>
send a job back with a note, and the AI redoes it
row job.send_back · POST /jobs/:id/send_back · send_back_job · jobs.sendBack()
note What to fix.
key Any string you choose: the same key replays the first answer and never acts twice. The Idempotency-Key header does the same.
exit 0 ok · 2 usage · 3 invalid request · 4 authentication · 5 blocked · 6 conflict · 10 retryEvery command that acts is safe to retry: it is idempotent by nature, like jobs accept, or it takes a key, like this one. The join is checked, not hoped for: check fails surface_drift when a command has no row, or a live row has no command. In CI shows it failing on a Service named like a command.
On localhost, your brand's CLI is services.dev itself. A buyer's command runs against dev exactly as your buyers' will, and gets the job every other surface returns:
$ npx services.dev translation document=https://example.com/lease.pdf to=fr-CA --max 49 --key lease
job_7Hq2 · working · $49.00 held
job_7Hq2 · delivered · lease.fr-CA.pdf · $49.00 charged
record draft · ai | review · ai · pass
ledger +$49.00 · AI $0.84 · you $48.16
next npx services.dev accept job_7Hq2$ curl -X POST http://localhost:8787/jobs -H 'content-type: application/json' -d '{"service":"translation","document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","max_usd":49,"key":"lease"}'
← 202 · job_7Hq2 · working · $49.00 held
next get_job {"id":"job_7Hq2"}→ tools/call order_translation {"document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","max_usd":49,"key":"lease"}
← job_7Hq2 · working · $49.00 held
next get_job {"id":"job_7Hq2"}await lingua.translation.order({"document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","max_usd":49,"key":"lease"})
← job_7Hq2 · working · $49.00 held
next get_job {"id":"job_7Hq2"}The capture fails unless all four return the same body, request ids aside. The CLI also waits for delivery and prints the record and the ledger, because on localhost you're the owner. Set a key and it reads the job as that key does, with the buyer's view only: Install and log in shows both.
npx <brand>
Each deploy publishes npx <brand> under your package name, beside your generated SDK. It renders the same column in your brand's own words, from Brand.words (the contract), and adds login for buyers. The members of your Pool get their own commands under a member's session, for the acts the delivery portal offers: attest, send back, decline and release. No key and no agent can run those. Its pages live in your brand's docs, one per command, so your buyers never land on services.dev.
Every command takes --json, or reads SERVICES_DEV_JSON=1 from an agent's environment. It prints the answer as the API sent it, and every failure as the one error shape: type, category, code, reason, next and doc_url. The exit code is the failure's category, so an agent or a CI job branches without reading the words. Build with your coding agent puts it to work.
- Install and log in: npx, a global install, and the key each call sends.
- In CI:
check --json, exit codes, magic values and test clocks. - Listening locally:
listen --forward-toandtrigger.