Skip to content

Latest commit

 

History

15 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

daemons CLI

The daemons command-line client is distributed as self-contained macOS and Linux binaries. The first beta is unsigned and not notarized; it is not described as signed anywhere in these instructions.

Install from a GitHub Release

This is the supported installation path. It needs neither Go, Node, a package manager, nor a checkout of this repository.

  1. Open the latest release and choose the asset matching your system:

    System Asset
    macOS on Intel daemons_vX.Y.Z_darwin_amd64.zip
    macOS on Apple silicon daemons_vX.Y.Z_darwin_arm64.zip
    Linux on x86-64 daemons_vX.Y.Z_linux_amd64.tar.gz
    Linux on ARM64 daemons_vX.Y.Z_linux_arm64.tar.gz

    Download that archive and the SHA256SUMS file from the same release. Release archive names always use daemons_vVERSION_OS_ARCH, with ZIP for macOS and tar.gz for Linux. Every archive contains daemons, LICENSE, and NOTICE.

  2. Verify the archive before extracting it. Replace ARCHIVE with the downloaded filename.

    On macOS:

    grep -F "  ARCHIVE" SHA256SUMS | shasum -a 256 -c -

    On Linux:

    grep -F "  ARCHIVE" SHA256SUMS | sha256sum -c -

    The command must report OK. Stop if it does not.

  3. Extract the archive and install the executable in a directory on your PATH (the example uses ~/.local/bin).

    On macOS:

    unzip ARCHIVE
    mkdir -p "$HOME/.local/bin"
    install -m 0755 daemons "$HOME/.local/bin/daemons"

    On Linux:

    tar -xzf ARCHIVE
    mkdir -p "$HOME/.local/bin"
    install -m 0755 daemons "$HOME/.local/bin/daemons"

    If needed, add the directory to your shell startup file and start a new shell:

    export PATH="$HOME/.local/bin:$PATH"
  4. On macOS, Gatekeeper can block this temporary unsigned, unnotarized beta when the downloaded file has the com.apple.quarantine attribute. After the checksum succeeds, remove the attribute only when it is present:

    if xattr -p com.apple.quarantine "$HOME/.local/bin/daemons" >/dev/null 2>&1; then
        xattr -d com.apple.quarantine "$HOME/.local/bin/daemons"
    fi

    This is a temporary beta fallback while Developer ID signing and Apple notarization credentials are unavailable. It is not needed for a future signed and notarized release.

  5. Confirm the installation:

    daemons --version

Commands

Run daemons help for the full list. Every command accepts --help. Global options: --json (canonical API document on stdout), --quiet, --host URL, --no-color, --request-id ID.

Reads

Command API route Scope
daemons whoami GET /api/v1/me control-plane:discover
daemons capabilities GET /api/v1/capabilities control-plane:discover
daemons servers list / servers show ID GET /api/v1/servers[/{server}] servers:read
daemons list (alias ls) / show ID GET /api/v1/daemons[/{daemon}] daemons:read
daemons operations list [--limit N] GET /api/v1/operations?limit=N (1 to 200) operations:read
daemons operations show ID GET /api/v1/operations/{operation} operations:read

daemons show ID prints the daemon's current ETag; daemons destroy uses that value for its conditional delete.

Tasks, files, and logs

Tasks, workspace listings, and logs are always addressed through their daemon. DAEMON is the daemon UUID or its exact name.

Command API route Scope
daemons task run DAEMON (PROMPT | -) [--agent AGENT] [--model MODEL] [--permission-mode yolo|approval-auto-deny] [--working-directory /workspace/DIR] [--timeout SECONDS] POST /api/v1/daemons/{daemon}/tasks tasks:write
daemons task show DAEMON TASK GET /api/v1/daemons/{daemon}/tasks/{task} tasks:read
daemons task list DAEMON [--limit N] GET /api/v1/daemons/{daemon}/tasks tasks:read
daemons task cancel DAEMON TASK POST /api/v1/daemons/{daemon}/tasks/{task}/cancel tasks:cancel
daemons files list DAEMON [PATH] [--cursor CURSOR] [--limit N] [--all] GET /api/v1/daemons/{daemon}/files files:read
daemons logs DAEMON --source agent|app|daemon|provisioning [--level LEVEL] [--cursor CURSOR] [--limit N] GET /api/v1/daemons/{daemon}/logs logs:read

Pass - as the prompt to read it from stdin (cat brief.md | daemons task run research -), which keeps it out of shell history and process listings. task run and task cancel are mutations and follow the idempotency-key rules below; a Codex task in YOLO mode may answer confirmation_required. A task that ended failed, cancelled, or timed_out makes task show exit 1 under the task's error_code. There is no --wait or --follow for tasks yet: poll with task show.

files list is an inventory only (name, type, size, mtime); it never downloads content. One page is printed per call with the next cursor on stderr; --all follows cursors for up to 50 pages and then stops with a resumable cursor. In --json mode every page is written as its own canonical document.

logs prints one bounded, server-redacted snapshot. --source is required and validated locally against the closed set; --follow is refused because the Control Plane has no log event route yet. Rerun with the printed --cursor to read newer lines.

Mutations

Every mutation takes --idempotency-key KEY (8 to 128 characters of letters, digits, ., _, :, -). Interactively the CLI generates one, prints it on stderr before submitting, and reuses it for the whole command. With --json or without a terminal an explicit key is required, so a script can never retry under a fresh key by accident. Every mutation also performs the API version preflight (GET /api/v1, scope control-plane:discover).

Command API route Scope
daemons spawn NAME --server SERVER [--agent AGENT] [--disk-quota-gb N] POST /api/v1/daemons daemons:write (plus servers:read when --server is a name)
daemons start|stop|restart|retry ID POST /api/v1/daemons/{daemon}/{action} daemons:write
daemons destroy ID [--etag ETAG] GET /api/v1/daemons/{daemon} then DELETE /api/v1/daemons/{daemon} with If-Match daemons:read, daemons:destroy

--server accepts the server UUID or its exact name (no prefix matching). daemons spawn prints the new daemon and its daemon.spawn operation; in --json mode stdout is the API's 202 document with the operation under meta.operation.

daemons destroy reads the daemon first to capture its ETag and sends it as If-Match, so a daemon that changed in between is never destroyed blindly. Pass --etag to pin a value you captured yourself and skip the read. On 412 precondition_failed the CLI re-reads the daemon, shows its current state and new ETag, and exits 1 without resubmitting.

Waiting for operations

Add --wait to any mutation to poll its operation until it reaches a terminal state (succeeded, failed, partially_succeeded, cancelled, timed_out, or outcome_unknown). Polling honours Retry-After, otherwise backs off from 2s to 15s with jitter. --wait-timeout bounds the wait (default 10m); use a duration such as 1s or 10m, or a bare number of seconds such as 90. Ctrl-C stops waiting locally; it never cancels the operation on the server.

In --json mode with --wait, stdout carries the mutation's document first and, once polling ends, the final operation document on its own line. Progress lines go to stderr and are suppressed by --quiet.

Outcomes: partially_succeeded is reported as a failure (exit 1) with the operation's result printed so you can see what landed. A wait that hits its timeout exits 8 with the last known state and the daemons operations show ID command to check it.

Confirmation and unknown outcomes

When the API answers 409 confirmation_required (for example on daemons destroy), nothing has changed. The CLI prints the safe summary, the approval URL, the expiry and the confirmation ID, and exits 6. In an interactive terminal it offers to open the approval URL in your browser; opening it is never treated as consent, and the CLI never polls or approves on your behalf. Approve in the browser, then run the same command again. With --json the canonical problem document is written to stdout and no browser is opened.

When a mutation's outcome cannot be determined (a transport failure after dispatch, an invalid response, or an operation ending in outcome_unknown), the CLI exits 8 and prints the reconciliation step to run first (for example daemons list after a spawn, daemons show ID after a lifecycle action) and the exact replay command with the original idempotency key. It never retries a possibly destructive or billable mutation on its own, and never under a new key.

Authentication and credentials

daemons login runs the device flow. daemons login --token-stdin reads an existing Control Plane token from stdin instead (one line), verifies it with GET /api/v1/me, and stores it; a token is never accepted as an argument and never echoed. DAEMONS_TOKEN in the environment overrides the store for CI and is never written to disk.

Credentials live in an owner-only file (~/.config/daemons/credentials.json, or DAEMONS_CREDENTIALS_FILE) keyed by normalized host, so logging in to a second --host never overwrites the first. Without --host or DAEMONS_HOST the CLI uses the production host when it has a credential, otherwise the only stored host; two or more non-production hosts require an explicit --host. daemons logout revokes and removes only the current host's credential. A credential file from an older release is migrated on the next login.

Terminal attach

daemons attach DAEMON [--session NAME] requires the ticket to advertise the takeover_v1 terminal feature; if the Control Plane does not, attach refuses (exit 2) before connecting instead of guessing at the gateway's behaviour. Raw terminal mode is restored on every exit path, including a panic.

SSH and local IDEs

Enable SSH with a locally held identity (only its adjacent .pub file is read):

daemons ssh enable DAEMON --identity ~/.ssh/id_ed25519 --wait
daemons ssh-config DAEMON --identity ~/.ssh/id_ed25519
daemons ide DAEMON --editor code

ssh-config writes private, managed OpenSSH files below ~/.ssh/daemons-run/ and adds one marked Include to ~/.ssh/config; it never copies a private key or stores tickets. daemons ssh keys list DAEMON lists fingerprints, ssh keys remove DAEMON FINGERPRINT removes one key, and ssh disable DAEMON removes SSH access. ide --cached uses the saved config but every new SSH transport still mints a fresh ticket and needs a current login. The proxy sends only SSH bytes on stdout; diagnostics stay on stderr.

Exit codes

0 success; 1 API or operation failure; 2 usage or local validation; 3 authentication; 4 not found; 5 scope or capability denied; 6 web confirmation required; 7 rate or quota; 8 outcome unknown.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages