@synchain/cli
v0.5.1
Published
Command-line interface for Synchain — the all-in-one music and audio production collaboration platform. Manage project files, scheduling, discussion and members from your terminal.
Downloads
872
Maintainers
Readme
Synchain CLI (synchain)
Command-line interface for Synchain — the all-in-one music and audio production collaboration platform. Manage project files, scheduling, discussion, members, and notifications from your terminal — built for humans and AI agents alike.
Install
npm install -g @synchain/cli # global `synchain` binary
# or run without installing:
npx @synchain/cli --helpFrom source (development):
cd cli && npm install && npm run build && npm linkRequires Node.js ≥ 20.
Quickstart
Generate a CLI key in the web app under Settings → CLI Access, then:
synchain login # paste the key (or set SYNCHAIN_TOKEN)
synchain project ls # list projects, then:
synchain project use <id> # set the active project
synchain files upload ./mix.wav # upload a file
synchain notifications ls # your unread notificationsCommands
login · logout · whoami · project ls|use · files ls|upload|download|mv|rename|rm ·
folders ls|mkdir|rename|rm · calendar add|ls|edit|rm · discussion ls|read|post|reply ·
members ls · notifications ls|read (alias notif). Run synchain help or synchain <group> --help.
Every command that writes remote data also takes --dry-run (see below).
discussion ls is paginated (--limit, --offset; newest threads first). members ls
lists the project roster (read-only) and requires the members scope to be enabled.
--dry-run
Every command that changes remote data accepts --dry-run:
files upload|mv|rm|rename · folders mkdir|rm|rename · calendar add|edit|rm
discussion post|reply · notifications readA dry run still resolves 8-char id prefixes and validates your input against the
server (read-only GETs run as usual), then prints the resolved target, sends no write
request, skips the files rm confirmation, and exits 0.
That resolution step is the point. Ids are UUIDs and every command accepts the 8-char
prefix printed by ls — a prefix copied from the wrong row deletes someone else's file
just as happily. --dry-run shows which full id and which filename your prefix actually
landed on, before anything is destroyed:
$ synchain files rm a1b2c3d4 --dry-run
[dry-run] would delete mix_v2.wav (a1b2c3d4, 12.0 MB) from project 1111….
Nothing was sent. Re-run without --dry-run to apply.
$ synchain files rm a1b2c3d4 --dry-run --json
{
"dryRun": true,
"action": "files.rm",
"target": {
"project": "1111…",
"file": { "id": "a1b2c3d4-0000-4000-8000-000000000001", "name": "mix_v2.wav", … }
}
}dryRun is always true and action is a stable <group>.<command> identifier, so a
caller can branch on the plan without parsing prose. target is shaped per action.
Caveats worth knowing: a dry run cannot predict server-side state it never queries — e.g.
folders rm --dry-run does not know whether the folder is empty, so the real DELETE can
still fail with 409 folder_not_empty. project use deliberately has no --dry-run:
it only writes the local config file and touches nothing remote.
Errors in JSON mode
In text mode errors are unchanged (red prose on stderr). Under --json / --format json
they become a single-line envelope on stderr (stdout stays reserved for results):
{ "error": { "code": "folder_not_empty", "status": 409, "url": "https://…", "detail": "…" } }code is the server's own snake_case error code (invalid_project_id, forbidden,
storage_key_in_use, …) taken straight from the response body — the same vocabulary the
HTTP API uses, so CLI and API callers can share one error table. When the body carries no
usable code it falls back to http_<status>; failures with no HTTP response at all
(DNS/TLS, timeouts, local file errors, an ambiguous id prefix) report
{"code":"client_error","status":0}. All four fields are always present.
Error classes (programmatic use)
When the CLI is imported as a library, every non-2xx response throws a typed error. ApiError
is the base class; the subclass is chosen by HTTP status, so callers can catch narrowly instead
of re-testing err.status:
| Class | Status | What to do about it |
| --- | --- | --- |
| AuthError | 401 | Re-authenticate (synchain login / SYNCHAIN_TOKEN). Retrying will not help. |
| ForbiddenError | 403 | Usually the key lacks the scope, or you are not a member — retrying will not help. One exception: a files upload whose presigned storage URL expired mid-transfer also lands here, and that one is worth retrying. Branch on the envelope's code, not on the class, when you need to tell them apart. |
| NotFoundError | 404 | Re-list to get a current id; the target is gone or invisible to you. |
| ConflictError | 409 | Fix the conflicting state (empty the folder, pick a free key), then retry. |
| ValidationError | 400, 422 | Correct the input. Retrying the same request always fails. |
| RateLimitError | 429 | Back off, honoring Retry-After. |
| ServerError | 5xx | Retry with exponential backoff. The body is often gateway HTML, not JSON. |
Every subclass is instanceof ApiError, and all of them keep status, url, and body, so
existing err instanceof ApiError && err.status === 404 checks keep working unchanged. Statuses
outside the table (402, 451, …) throw the base ApiError rather than being forced into the
nearest subclass — use the envelope's code for the fine-grained reason.
import { apiErrorFor, NotFoundError, RateLimitError } from "@synchain/cli";apiErrorFor(status, url, body, message?) is the same factory the CLI uses internally, exported
so callers can classify a status they obtained elsewhere with the identical rules.
Notes
- The key is stored in the OS config dir (
%APPDATA%\synchain/~/.config/synchain, mode0600) and is never accepted via an argv flag. Override the server with--base-url(defaulthttps://synchain.vercel.app; cleartexthttpis rejected for non-loopback hosts). - Machine-readable output: the global
--format text|json(defaulttext) is equivalent to the per-command--jsonflag, andsynchain --help --format jsonprints the whole command tree as JSON — commands, options, and usage — so agents can discover the surface without scraping the human help text. - The package ships type declarations (
dist/index.d.ts);buildProgram(),main(), theApiErrorhierarchy above, and theErrorEnvelopetype can be imported programmatically from@synchain/cli. - Discussion posts made through a CLI key are stamped
is_ai_generated=trueand show an[AI]badge in the web UI.
Full guide for AI agents: synchain.ca/AGENTS.md — install,
non-interactive auth, --json output shapes, and which paths not to crawl.
