sylphx CLI
Every Sylphx API method as a command, generated from the one schema, plus a small hand-written porcelain on the generated Rust SDK.
| # keys go to the OS keychain (or an explicitly chosen 0600 file), never stdout
- Login needs no organization up front: select one, several, or all memberships
in the browser.
sylphx login --org acmelocks approval to that organization. The CLI stores every issued key separately; there is no cross-tenant key. Commands choose credentials by--org(slug, id, ororgs/{id}), a full resource name,SYLPHX_ENVIRONMENT/ the nearest project link, then the active org set bysylphx org use <slug>. Switching active org does not rewrite a checkout's link; specify the project/environment when working in another org.--key/--api-keyandSYLPHX_API_KEYstill override stored credentials. Existing single-key credentials files and keychain entries remain readable. Re-login merges approved org credentials without deleting other orgs;logout --org <slug>removes just that org, whilelogout --allremoves all. sylphx <service> <collection> <verb> [NAME|PARENT|ID] [--flags]: flags are the spec fields in dotted kebab-case (--spec.compute-units);--from-filetakes a whole request;--output table|json|yaml|name.- A bare id (
main) expands below the linked env; parents default to the linked project, else the key's scope. The link is the nearest.sylphx/project.jsonnaming an org, project or env; a file in another shape (such as{orgId, projectId}) is skipped with a warning.SYLPHX_ENVIRONMENT=orgs/…/projects/…/envs/…(a full name) overrides the link for that process, so a CI job or a build wrapper never depends on the checkout's.sylphx/project.json. - Mutations wait for their Operation (
--no-waitto skip);--dry-runsetsvalidate_only; updates send the etag they read and a mask of the flags given; destructive calls ask first (--yesto skip). sylphx applyreadssylphx.toml(or--manifest PATH, including a Release'sdeclarations.json) and uses the shared Resource planner and writer.--offlinechecks without a key or network;--dry-runplans without writing;-o jsonprints the Resource actions and unsettled names.--detailed-exitcodereturns 0 for no changes, 2 for changes, and 1 for errors.--timeout SECONDSdefaults to 600 per Resource.--release IDand--source REPO@SHA:PATHrecord provenance; a Release run also writes its compact JSON result to/dev/termination-log.--forceallows a standalone run to write a Resource managed by a Release. Eligible kinds are listed inclients/apply/declarable.toml; this list remains empty until each kind's writer cutover is complete.sylphx api GET /v1/whoamiis the raw escape hatch.sylphx data docs sync|search|readkeeps a Markdown tree in one of the environment's search indexes.sync [GLOB…] --index ID --revision REV(run it on each merge) writes one document per#to###section with its path, heading and line numbers, then deletes the documents it wrote for that source at any other revision (nothing else in the index); a run matching no file is refused.search QUERYprints the best sections aspath:linewith the matching line;read PATH[:LINE] [--lines A-B]prints a file or a range of it.--indexand--sourcedefault toSYLPHX_DOCS_INDEXandSYLPHX_DOCS_SOURCE, which also givesylphx mcpitsdocs_searchanddocs_readtools.sylphx events listen [TOPIC] --forward URLrelays a topic (default: the environment's whole bus) to a local URL while you develop. It creates a temporary Queue and a Subscription of the topic into it, long-polls the Queue, and POSTs each event as the CloudEvent JSON body a webhook endpoint receives, withwebhook-id,webhook-timestampandwebhook-signature(Standard Webhooks) under thewhsec_…secret printed at start:--secret, elseSYLPHX_LISTEN_SECRET, else one generated once and kept in the config directory (listen-secret, 0600). An event the receiver answers is acked and printed with its status; one it cannot reach is tried again after 3 s.--typesand--sourcesfilter;--count Nexits after N events;-o jsonprints one JSON line per event. Ctrl-C deletes the Queue and the Subscription.sylphx devices runinstalls an APK on a fresh Android device lease, launches it (or a--game-loop), watches it with screenshots, and writesresult.json,junit.xml,logcat.txt, andcrash.txtinto--out(device-results/). Exit 0 passed, 1 failed, 2 infrastructure; the lease is released on the way out unless--keep.- Build workspaces are owned by the admitted lease, not a persistent client pool. A queued Build holds no workspace PVC reservation. After Kueue admits the suspended Job, Kubernetes creates the Pod's generic ephemeral raw-block workspace claim; lease end removes it through normal workload garbage collection after the guest unmounts. The catalog retains the current 64Gi workspace request. Explicit customer Sandboxes Volumes are unchanged.
sylphx build run [PATH] -- <command…>runs the command on a remote build machine against this git work tree and behaves like the local command: the output streams back, the exit code is the command's own, and--artifact GLOBfiles are copied into--out(.sylphx/out/<run>/). Every lease gets a fresh/workspace, including with an explicit--region; the CLI never lists, creates, evicts or attaches a workspace Volume before admission. Cross-leasetarget/, local toolchains and BuildKit state are not retained. The existing scoped remote build cache and dependency mirrors remain; they do not replace all local BuildKit layers or mutable cache mounts. Exit codes: the command's own, 2 usage, 124--timeout, 125 platform failure (retryablein the-o jsonresultevent; anything that fails before the command starts, such as the sync or the toolchain install or a toolchain that fails its check, is 125, and a machine lost or a stream broken before the command starts is retried once after a short backoff first), 130 interrupted, 137 the machine was lost after the command started. A started command is never run twice: a broken output stream is reattached to the running command. Cargo usesCARGO_INCREMENTAL=0with sccache when a remote cache is available, andCARGO_PROFILE_DEV_DEBUG=0;--envoverrides these defaults and an explicit compiler wrapper is preserved.--region REGIONpins placement, not a persistent workspace. Automatic placement remains the Sandboxes API's responsibility.--queue-timeout DURATION(default 30m) is the longest wait for a machine, after which the run releases its lease and exits 125, retryable, so a caller can try another region.-o jsonwrites NDJSON events (queued,running,sync,stdout,stderr,artifact,result);--dry-runprints what a cold run would send. Files:git ls-files(tracked and untracked, not ignored) minus.sylphxignore.- Cargo git dependencies come with the run: every git commit a
Cargo.lockof the tree locks is fetched here with your git credentials (depth 1, kept under the repository's git directory), sent to a mirror on the workspace only when it lacks it (git_depsevent), and Cargo's fetch reads that mirror, so a private dependency builds with no egress and no token on the machine. A lock that names thessh://spelling of a GitHub repository is retried overhttps://when the machine has no ssh key for it, since the two spellings name the same repository. One that cannot be fetched here stops the run with 125 before a machine is leased. Git printswarning: rejected refs/commit/… because shallow roots are not allowed to be updatedonce per new commit; the build is unaffected. - The run also gets the project's shared build cache (sccache, Turbo): the
CLI mints a cache token with your key before the command starts and merges
the cache's environment into the command's (your
--envwins). If the cache cannot be reached the run builds without it and prints onesylphx: warning: build cache unavailable (…)line;--no-cacheskips it. The same holds when sccache's server will not start on the machine because the cache stops answering after the token is minted: the command then runs withoutRUSTC_WRAPPERand the warning says so, rather than every compile failing with sccache's exit 2. The token is never printed, logged or put in an event. sylphx build cache env [--project ID] [-o json]mints a read-only token (12 h) and printsexport NAME='value'lines for a POSIX shell (plusexport RUSTC_WRAPPER=sccachewhen sccache is installed and the variable is unset);-o jsonprints the environment as an object. The output holds a credential. Exit 0; 2 usage; 125 platform failure. The gateway ishttps://build-cache.sylphx.net, orSYLPHX_BUILD_CACHE_URL.
sylphx work runner
A self-hosted runner for Sylphx Work: it takes ready steps from your workspace and runs your command for each one on this machine, as GitHub's self-hosted runners take jobs.
- It long-polls Work (
POST /v2/claimwithn: 1, its labels, its runner name andwait) for a ready step whose required labels--labelcovers. The runner (--name, default the host name) is catalogued on that claim, so the workspace's coverage counts it as a live runner for eachmember:<group>label it advertises. - The command gets the claim (item, checkpoint, context,
profile,workspaceProfile, token) as JSON on standard input and in$SYLPHX_WORK_CLAIM_FILE, and the claim token, scoped to that one item, in$SYLPHX_WORK_CLAIM_TOKEN;$SYLPHX_WORK_URL,$SYLPHX_WORK_ITEM,$SYLPHX_WORK_GENERATIONand$SYLPHX_WORK_RUNNERname the rest.SYLPHX_API_KEYis removed from its environment: the step acts with the claim token, never the runner's key.SYLPHX_CONFIG_DIRpoints to an empty per-step directory rather than the runner's saved login; it is removed after the step. Each beat writes the renewed token to$SYLPHX_WORK_CLAIM_TOKEN_FILE. Work names no harness or model: the command is yours. - While the command runs the runner beats the claim at half its
time-to-live (
--ttl, default the step kind's lease). A beat that answerscancel(dropped, reassigned or revoked) stops the command's process group (SIGTERM, SIGKILL after 10 s). - When the command ends the runner releases the step with a checkpoint that
names its exit status, unless the command already handed it over with the
claim token. With
--ephemeral(the default) it then exits 0, whatever the command's status;--looptakes the next step. Ctrl-C stops the command, hands the step over and exits 130.-o jsonprints one JSON line per step (item,generation,exitCode,cancelled,handedOverBy). - Work's address is
--work-url, elseSYLPHX_WORK_URL, elsehttps://work.sylphx.com; the key is your login,SYLPHX_API_KEYor--api-key.
sylphx ai top
The operator live view of the AI gateway's seats, replacing janus status,
top, doctor and capacity. It is read-only over GET /v1/operator/seats
and needs a platform key with ai:operator:seats:read (--base-url points it
at the gateway if api.sylphx.com does not route it).
It shows usable, spent and out-of-rotation seats; when the next spent seat
comes back and the earliest weekly reset; and, at the 24h and 6h pace, the
demand, seats needed, seats to add (with 25% headroom) and the simulated
runway. Anything that needs a person is a red line: a seat that needs a login
(reauth_required), is on hold (subscription_required), is quarantined or
has no fresh reading, no usable seat, a pool that runs dry, seats to add.
The pace is measured from the weekly-window readings each run keeps in
<config dir>/ai-top-history.json (a week, at most one reading per 4
minutes), so it reads n/a until about 6 hours of readings exist; leave a
terminal open or run --once from a timer. Sessions and subagents (seat,
model, effort, cache hit) and the API-equivalent value read n/a until the
gateway's receipts carry them; --json lists the missing fields.
sylphx token --scope <scope>
Prints one token and a newline on stdout, for tools that need a credential for a single scope (cargo, deploy scripts). Everything else goes to stderr; the exit code is non-zero, with a one-line reason, when you are not signed in, the login may not grant the scope, or the scope is not registered.
- The token is a child Access key in your login's own org/project/env, with
only that scope and a 15 minute life (label
token:<scope>). Your login key is never printed. Scopes are the registered ones (packages:read,packages:publish,hosting:deploy,ai:inference, ...); an unregistered scope is refused. - It is cached in
<config dir>/token-cache/(mode 0600) per login and scope, and reused until 2 minutes before it expires, so a cargo build mints one key, not one per crate.sylphx logoutclears the cache. - With
SYLPHX_API_KEY(CI) it mints from that key the same way. A key that may not mint keys is printed as-is only when it carries the scope and expires within 60 minutes (the key a GitHub Actions OIDC exchange issues); otherwise the command refuses. - A login that may not grant the scope is refused; a per-scope step-up device login needs server support that is not there yet.
# .cargo/config.toml
[]
= ["cargo:token-from-stdout", "sylphx", "token", "--scope", "packages:read"]
Use --scope packages:publish for publishing. Neither is covered by the
*:read / *:write wildcards, so a login must hold them by name.
generated/commands.json is sylphx-gen output; never edit it.
Container image
clients/cli/Dockerfile builds the CLI into a distroless image (no shell,
CA certificates, uid 65532, writable /tmp); the Release v2 apply Job runs
sylphx apply ... from it inside tenant environments. The build context is
the repository root: docker build -f clients/cli/Dockerfile ..
.github/workflows/cli-image.yml publishes it on every push to main that
changes anything under clients/, as registry.sylphx.com/library/sylphx-cli:<commit sha>, and
prints the digest reference in the run summary. Tenant namespaces pull it with
the platform registry-pull-secret.
The release controller pins the image by digest through one environment
variable, set in SylphxAI/infra (infra/addons/release-controller, the
controller Deployment's env):
- name: SYLPHX_APPLY_CLI_IMAGE
value: registry.sylphx.com/library/sylphx-cli@sha256:<digest from the run summary>