CLI reference
homespun deploy ./my-app # create or redeploy an apphomespun apps list --status active # list your appshomespun data app_abc123 todos list # read a collection's rowshomespun members add --app app_abc123 --email alice@example.comThe CLI is self-documenting: homespun <command> --help is always the authoritative reference
for flags and defaults on whatever version you have installed. This page mirrors that text so
you can read it without a terminal open, and calls out a couple of sharp edges the --help text
doesn’t mention.
Global flags
Section titled “Global flags”Every command below accepts these in addition to its own flags:
| Flag | Meaning |
|---|---|
--url <url> | Relay base URL. Overrides HOMESPUN_URL and the active profile. |
--api-key <key> | Agent API key. Overrides HOMESPUN_API_KEY and the active profile. |
--profile <name> | Pick a saved profile for this invocation. Overrides HOMESPUN_PROFILE. |
-h, --help | Show help. |
--json | Accepted but currently a no-op, kept for forward compatibility. |
Output is machine-readable JSON on stdout (JSON-lines for a couple of streaming verbs, noted
below). Errors go to stderr as {"error":{"code","message"}} with a non-zero exit code.
Config file and profiles
Section titled “Config file and profiles”homespun agent register provisions an API key and saves it, with the relay URL, to
${XDG_CONFIG_HOME:-~/.config}/homespun/config.json under a named profile (mode 0600).
Afterwards, commands need no environment variables. Manage multiple environments
(dev/staging/prod) with the config noun below.
deploy: create or redeploy an app
Section titled “deploy: create or redeploy an app”homespun deploy <dir|file> [--app <id>] [--manifest <path|json>] \ [--slug <slug>] [--visibility private|link|public] [--force]The only noun with no sub-verb: whether it creates or redeploys is decided by whether --app is
present, not by two different commands.
Packaging, one canonical shape and one escape hatch:
- Directory (canonical):
homespun deploy ./my-appreads./my-app/index.htmland./my-app/manifest.json, fixed filenames, both required. - Single file (escape hatch):
homespun deploy ./index.html --manifest ./manifest.json.--manifestaccepts a file path or inline JSON.
Create vs. redeploy:
Create (no --app) | Redeploy (--app <id>) | |
|---|---|---|
| Request | POST /v1/apps | POST /v1/apps/:id/versions |
--slug | Applies. Rejected only together with an explicit --visibility link: link-visibility slugs are always server-generated. Fine with private (the default) and public. | Rejected: the slug is immutable once set. |
--visibility | Applies. | Rejected: change it with homespun apps update instead. |
--force | N/A | Overrides the compatibility gate on a narrowing manifest change. |
Output: { app_id, slug, url, version, visibility, created, compat?, breaks? }.
Examples:
homespun deploy ./my-apphomespun deploy ./my-app --visibility public --slug my-app-slughomespun deploy ./index.html --manifest ./manifest.jsonhomespun deploy ./my-app --app app_abc123homespun deploy ./my-app --app app_abc123 --forceapps: app lifecycle
Section titled “apps: app lifecycle”homespun apps list [--status active|dormant|archived|all] [--limit] [--cursor] [--slug <slug>]homespun apps show <app>homespun apps update <app> --visibility <private|link|public>homespun apps delete <app> [--yes]homespun apps wake <app>homespun apps watch <app> [--since <cursor>] [--collection <name[,name2,...]>] [--once] [--timeout <secs>]<app> accepts either the app_id or its slug throughout the CLI, resolved via
GET /v1/apps?slug= when it doesn’t look like a generated id.
apps watch streams the app’s change feed as JSON-lines on stdout, one entry per line,
whether served over the live WebSocket or a long-poll fallback (used automatically if the WS
upgrade fails). A dormancy transition mid-watch emits a single {"type":"_dormant"} line and
exits cleanly.
apps delete is destructive (removes the app and all its data) and refuses to run without
--yes.
Examples:
homespun apps list --status active --limit 20homespun apps show app_abc123homespun apps update app_abc123 --visibility publichomespun apps delete app_abc123 --yeshomespun apps wake app_abc123homespun apps watch app_abc123 --since 42 --collection todos,comments --once --timeout 30data: collection row CRUD
Section titled “data: collection row CRUD”homespun data <app> <collection> list [--since <cursor>] [--limit <n>]homespun data <app> <collection> get <key>homespun data <app> <collection> upsert --data <path|json> [--key <key>]homespun data <app> <collection> update <key> --data <path|json> [--if-match <version>]homespun data <app> <collection> delete <key> [--if-match <version>] [--yes]App and collection are positional arguments here, not flags. upsert is the only
create-shaped verb: omit --key to add a new row with a server-generated key; pass --key to
ensure a row exists at that key, which returns the existing row with deduped: true on a
collision rather than erroring.
Examples:
homespun data app_abc123 todos list --since 10 --limit 50homespun data app_abc123 todos get row_key1homespun data app_abc123 todos upsert --data '{"text":"buy milk"}'homespun data app_abc123 todos upsert --data ./row.json --key mykeyhomespun data app_abc123 todos update row_key1 --data '{"done":true}' --if-match 3homespun data app_abc123 todos delete row_key1 --if-match 3members: app membership
Section titled “members: app membership”homespun members add --app <idOrSlug> --email <email> [--role member]homespun members list --app <idOrSlug>homespun members remove --app <idOrSlug> --human <humanId>add: if a Human already exists for that email, the member is attached immediately, response
{ member: { humanId, email, role, createdAt } }. Otherwise the relay emails an invite link,
response { ok: true, invited, expires_at }. member is the only valid --role; there’s no
ownership transfer here.
remove is idempotent and also revokes the human’s live sessions on this app. The app owner
can never be removed this way (the relay returns a 409 conflict).
Examples:
homespun members add --app app_abc123 --email alice@example.comhomespun members list --app app_abc123homespun members remove --app app_abc123 --human human_xyzkey: your own API key
Section titled “key: your own API key”homespun key listhomespun key revoke --yesScoped to the calling agent’s own key only, there’s exactly one key per agent. revoke is a
self-destruct: the key stops working immediately and every subsequent command fails until you
run homespun agent register again.
agent: this agent’s identity
Section titled “agent: this agent’s identity”homespun agent register [--name <n>] [--profile <name>] [--secret <s>] [--print-key]homespun agent claim <code>homespun agent set-key <api-key> [--url <url>] [--profile <name>]homespun agent logout [--profile <name>] [--all]register provisions an API key and saves it locally (see “Config file and profiles” above).
claim <code> binds this agent to the human who generated the one-time code (begins cc_);
one-way, no unclaim. set-key saves a key you already have, e.g. after regenerating it in the
web UI. logout clears the locally saved profile (or every profile, with --all); it does not
revoke the key on the relay, use key revoke for that.
config: local profile management
Section titled “config: local profile management”homespun config showhomespun config listhomespun config use <profile>homespun config add <profile> --url <u> --api-key <k>homespun config rm <profile>Purely local: none of these make a network call. A profile is one (url, api_key) pair under a
short name. The active profile is what every other command uses unless overridden by --url /
--api-key or their environment-variable equivalents. The full API key is never printed, only a
masked prefix.
taste: this agent’s UI taste notes
Section titled “taste: this agent’s UI taste notes”homespun taste gethomespun taste set --file <path|->homespun taste clear --yesA small markdown attachment recording presentation preferences a human has given feedback on
(“denser table”, “no rounded corners”). Read it before generating a new app so prior feedback
carries over; rewrite it whenever the human gives new presentation feedback. set reads from
--file, from stdin via --file -, or from piped stdin with no flag at all. clear requires
--yes.
homespun taste set --file ./taste.mdecho "- denser layout" | homespun taste sethomespun taste clear --yesfeedback: one-shot notes to the relay operator
Section titled “feedback: one-shot notes to the relay operator”homespun feedback create --type <bug|feature|note> --message <text|-> [--app-id <id>]homespun feedback list [--limit <n>] [--before <cursor>]homespun feedback create --type bug --message "watch hangs on empty app"homespun feedback list --limit 20attachment: binary attachments
Section titled “attachment: binary attachments”homespun attachment upload --file <path> [--scope agent|app] [--app-id <id>] [--filename <name>] [--mime <type>]homespun attachment download <attachment-id> [--out <path>]homespun attachment show <attachment-id>homespun attachment list [--cursor <token>] [--limit <n>]homespun attachment delete <attachment-id>homespun attachment token mint <attachment-id> [--ttl <seconds>] [--once]homespun attachment token revoke <attachment-id> <token-id>homespun attachment token list <attachment-id>Attachments are scoped to an agent (default, reusable across the agent’s apps) or to an app
(deleted with it). Reference an attachment from a row’s data with
format: homespun-attachment-id in its schema. token mint produces a /b/<token> capability
URL anyone can GET without an API key, useful for handing a human a direct download link;
default TTL is 30 days for an app-scoped attachment and 24 hours for an agent-scoped one, and the
caller can only shorten it, never extend it.
homespun attachment upload --file ./photo.png --scope app --app-id app_abc123homespun attachment download attachment_id123 --out ./photo.pnghomespun attachment token mint attachment_id123 --ttl 3600 --onceskill: fetch the relay’s SKILL.md
Section titled “skill: fetch the relay’s SKILL.md”homespun skill showhomespun skill version [--plain]Unauthenticated, no API key required, useful for bootstrapping before agent register.
show writes the raw markdown to stdout:
homespun skill show > ~/.claude/skills/homespun/SKILL.mdversion --plain prints just the version string, handy in a shell comparison against a locally
cached copy.