# Set up Aether with a coding agent Source: https://docs.runaether.dev/agent-setup Canonical instructions for a coding agent to install and configure Aether for its human # Set up Aether with a coding agent ## Instructions to the executing agent You are setting up Aether for your human. Execute the steps below in order from the repository your human wants to use with Aether. Do not use `aether setup`; that command intentionally requires an interactive terminal. This non-TTY flow prepares cloud tasks and Teleport. It does not connect the current local session to the hosted delegation MCP. If your human wants [cloud delegation](/guides/cloud-delegation), explain that its supported setup uses `aether setup` in an interactive terminal on this computer, including native browser authorization and the client's project trust prompts. Do not report delegation as connected after only connecting a cloud agent provider. After **every** step, run its verification probe. Do not continue when a probe fails, and **NEVER claim that a step succeeded unless its probe passed**. Diagnose a failure, retry the step once, and rerun the probe. If the same step fails twice, stop and report the command, the error, and the failed probe honestly. When a step needs a browser, keep the CLI command running, immediately give your human the printed URL, tell them exactly what to do there, and poll or wait until the CLI reports success. Do not ask your human to paste a credential, token, authorization code, secret value, or file contents into the agent conversation. A URL can be opened on any device with a browser. A **command** is different: it must run on this computer — the machine this session is running on. Whenever you hand your human a command to run themselves: say explicitly that it must run on this computer and name its operating system; if your human may not know how, include how to open the terminal application (macOS: press Cmd+Space, type Terminal, press Enter); and write the command using the absolute path to the `aether` binary whenever it is installed outside `PATH`. A command pasted into the wrong machine's shell fails confusingly — prevent that, don't recover from it. Secret files have an additional, non-negotiable consent gate: you may list candidate **paths**, but you must never choose or upload a file on your own judgment. Ask your human which exact paths, if any, they approve. Upload only those paths. ## 1. Install the Aether CLI Run: ```bash theme={"dark"} curl -fsSL https://app.runaether.dev/install.sh | sh ``` Expected output includes: ```text theme={"dark"} Downloading Aether for /... Aether installed to /aether ``` Verification probe: ```bash theme={"dark"} aether version ``` The probe passes only if it exits zero and prints `aether version` plus the Go and OS/architecture lines. If installation fails, report the installer error. Check that `curl`, `tar`, `install`, and the platform checksum tool are available and that the install directory is writable. Retry once; do not substitute an unverified binary or claim success from a downloaded file alone. If the install directory is outside `PATH` — for example, you set `AETHER_INSTALL_DIR` to a user-writable directory because the default needed sudo — the installer prints the shell-profile line to add. From then on, use the absolute binary path (`/aether`) in every command you run **and every command you hand to your human**. You may offer to append the printed `PATH` line to your human's shell profile, but only with their explicit consent; do not edit shell configuration unasked. ## 2. Authenticate the Aether account First run the probe. If it already prints `Logged in` or `Authenticated via AETHER_TOKEN environment variable`, this step is complete. Otherwise, start the browser callback flow in the headless shell: ```bash theme={"dark"} aether login ``` Expected output includes a URL like: ```text theme={"dark"} https://app.runaether.dev/login?port=&state= Waiting for authentication... ``` Immediately give that URL to your human and say: **“Open this URL in a browser, sign in to Aether, and leave this command running until I confirm it completed.”** The CLI blocks while it waits for the localhost callback. Success ends with: ```text theme={"dark"} Logged in Config saved to: ``` Verification probe: ```bash theme={"dark"} aether auth status ``` The probe passes only when it reports `Logged in` or authentication through `AETHER_TOKEN`. `Not logged in` is a failed probe even though the status command itself exits zero. If the callback times out or the credential is rejected, run `aether login` again to obtain a fresh URL. Never ask for a token as a workaround. Stop after a second failed login or probe. ## 3. Connect GitHub Run the verification probe first. If it already prints `GitHub: connected` and at least one installation, do not start a second installation flow. Otherwise run: ```bash theme={"dark"} aether github connect ``` Expected output includes the installation URL and then waits: ```text theme={"dark"} Connect GitHub in your browser. Install the Aether GitHub App to grant repository access. URL: https://github.com/apps//installations/new... Waiting for connection... ``` Immediately give the printed URL to your human and say: **“Open this URL, install or configure the Aether GitHub App, grant access to the repository we are setting up, and return here. I will keep polling.”** Do not terminate the command while the human is in GitHub. Success prints `GitHub connected`. Verification probe: ```bash theme={"dark"} aether github status ``` The probe passes only if the output starts with `GitHub: connected` and lists at least one `Installation`. `GitHub: not connected` and `GitHub: reauthorization required` are failures. If the repository is not covered by the installation, rerun the connection flow and tell the human to add that repository. Retry once, then stop and report the status output. ## 4. Connect coding-agent providers At least one of OpenCode, Codex, or Claude Code must pass its status probe before onboarding can complete. Connect each provider that your human already uses locally. Do not treat “not configured” as success, and do not stop at “skip providers” when none is installed or logged in. ### If no provider is installed or logged in Ask your human which coding agent they already use or want to use, and explain the account each connect flow expects: * **Claude Code:** a Claude Pro, Max, Team, or Enterprise subscription. * **Codex:** an eligible ChatGPT plan. Current supported plans include Free, Go, Plus, Pro, Business, Edu, and Enterprise, with different usage limits. * **OpenCode:** no single subscription is required, but it needs a supported model-provider login or API credential. OpenCode Go is its subscription option; OpenCode Zen is pay-as-you-go. After they choose, offer to install that agent on this computer. The agent must run the installer; do **not** hand the install command to the human. Before running anything, show the exact command appropriate for this computer's operating system and ask for explicit consent. Use the lowest-dependency official command: * Claude Code on macOS, Linux, or WSL: `curl -fsSL https://claude.ai/install.sh | bash` * Claude Code in Windows PowerShell: `irm https://claude.ai/install.ps1 | iex` * Codex on macOS or Linux: `curl -fsSL https://chatgpt.com/codex/install.sh | sh` * Codex on Windows: `powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"` * OpenCode on macOS, Linux, or WSL: `curl -fsSL https://opencode.ai/install | bash` * OpenCode on native Windows: use `choco install opencode` if Chocolatey is already installed, or `scoop install opencode` if Scoop is already installed. Otherwise stop and explain that OpenCode's official guidance recommends WSL; do not install another package manager without separate consent. Do not infer consent from the agent choice. Run only the exact command the human approved, then immediately verify it with the matching probe: ```bash theme={"dark"} claude --version codex --version opencode --version ``` Run only the probe for the installed agent. It passes only if it exits zero and prints a version. If installation or the probe fails, diagnose and retry once under the existing probe discipline; never fabricate an installation or login. After the probe passes, continue with that agent's normal connect leg below. The human only consents to installation and completes browser sign-in; the driving agent runs the installer, login, connect, and verification commands. ### OpenCode — fully non-interactive If `${XDG_DATA_HOME:-~/.local/share}/opencode/auth.json` exists, run: ```bash theme={"dark"} aether opencode connect ``` This reads and uploads that local auth file without a prompt. Expected output: ```text theme={"dark"} OpenCode connected (uploaded bytes from /opencode/auth.json). ``` Verification probe: ```bash theme={"dark"} aether opencode status ``` The probe passes only if it prints `OpenCode: configured`. If the local file is absent, ask which supported model provider the human uses, then run `opencode auth login --provider ` from the agent shell. Relay a browser sign-in URL immediately and keep the command running while the human approves it. Do not invent the provider, request a secret, or ask the human to paste an API key. If the chosen method requires manual secret entry instead of browser sign-in, stop and report that it cannot be completed through this agent-guided flow. After browser login, verify that `opencode auth list` names the chosen provider, then retry the Aether connection once. Treat an invalid auth file as a failure; do not fabricate or modify its contents. ### Codex — device flow requiring human confirmation Run: ```bash theme={"dark"} aether codex connect ``` Expected output: ```text theme={"dark"} Open this URL and enter the code: Code: Waiting for Codex authorization... ``` Immediately give your human the verification URL and code and say: **“Open this URL, enter this code, and approve Codex access. I will keep the command running.”** The CLI polls until authorization succeeds. Expected success is `Codex subscription auth connected.` Verification probe: ```bash theme={"dark"} aether codex status ``` The probe passes only if it prints `Codex: configured`. If the device code expires, start the command once more and provide the new URL and code. Stop after the second failed flow or probe. ### Claude Code — headless browser flow (aether ≥ 0.2.5) Headless support requires **aether 0.2.5 or newer**. Because this guide is served live before every workspace has the latest released binary, attempt the command headlessly first, but treat `claude setup-token did not display a sign-in link within 30s` as the tell that the installed aether is older and still has the non-TTY rendering bug. Run from the agent shell: ```bash theme={"dark"} aether claude-code connect ``` Relay the printed sign-in URL to your human immediately and say: **“Open this URL and approve Claude access. I will keep the command running.”** Keep the command running while the browser flow completes. Expected success is: ```text theme={"dark"} Claude Code setup token connected. ``` Only if the headless command errors — including the older-binary 30-second failure above — fall back to a human terminal. Use the absolute aether path established in step 1 (or resolve it with `command -v aether` when aether is on `PATH`) in the handoff. Name this computer's detected operating system and tell your human exactly: * On macOS: **“On this computer (macOS), press Cmd+Space, type Terminal, press Enter. In Terminal, run ` claude-code connect` and complete the Claude browser flow. Do not paste the token or code here. Tell me when the command succeeds.”** * On another operating system: **“On this computer (``), open its terminal application. Run ` claude-code connect` and complete the Claude browser flow. Do not paste the token or code here. Tell me when the command succeeds.”** Replace the placeholders with the detected operating system and actual absolute binary path before sending the handoff. Then rerun only the probe: ```bash theme={"dark"} aether claude-code status ``` The probe passes only if it prints `Claude Code: configured`. If the human-run command or the follow-up probe still fails after one retry, stop and report the failure. Finally, rerun the status probes for every provider attempted. Proceed only when at least one reports `configured`. ## 5. Create or select the project for this repository An inherited `AETHER_PROJECT` can silently override the stored default. From this step through first-task creation, prefix every project-scoped Aether command with `AETHER_PROJECT=` exactly as shown. This clears only that per-process override; do not unset or change any credential environment variable. Read the current repository's GitHub remote: ```bash theme={"dark"} git remote get-url origin ``` Normalize the HTTPS or SSH GitHub URL to its exact `owner/repository` name. First inspect projects using machine-readable output: ```bash theme={"dark"} AETHER_PROJECT= aether project list --json ``` If an existing project has a `repo_url` for this same repository, select it: ```bash theme={"dark"} AETHER_PROJECT= aether project select ``` Otherwise inspect the accessible GitHub repositories: ```bash theme={"dark"} AETHER_PROJECT= aether github repos --json ``` The output contains `full_name` and `installation_id`. Find the entry whose `full_name` exactly equals the current remote. Do not guess an installation ID. Then create, select, and link a project. Use the repository name as the project name unless your human already specified another name: ```bash theme={"dark"} AETHER_PROJECT= aether project create --name "" --json AETHER_PROJECT= aether project select AETHER_PROJECT= aether github import --repo "" --installation-id ``` Expected output includes: ```text theme={"dark"} Default project set to: () Linked repo: () Clone URL: ``` Verification probes: ```bash theme={"dark"} AETHER_PROJECT= aether project get AETHER_PROJECT= aether github link AETHER_PROJECT= aether config get default_project ``` The probes pass only when the project exists, `aether github link` names the exact current repository, and `aether config get default_project` prints that project's full ID. All of these commands work without a TTY when the IDs and flags above are supplied. If the repository is absent from `aether github repos --json`, return to the GitHub step and ask the human to grant it access. If create, select, or import fails twice, stop; do not create extra projects to hide the error. ## 6. Claim the devbox prewarm slot (best-effort) Prewarming builds the project's devbox while the remaining steps run, so the first task starts warm. This step is best-effort by design: no outcome here may stop the setup. First inspect the current slots: ```bash theme={"dark"} AETHER_PROJECT= aether warm-slot list --json ``` * If a slot already lists this project's ID, the claim exists; record "prewarm: already claimed" and continue to the next step. * If a slot has no project assigned, claim it: ```bash theme={"dark"} AETHER_PROJECT= aether warm-slot assign --project ``` * If every slot is held by another project, record "prewarm: unavailable — the first task will cold-start" and continue. If the assign command reports a missing required flag on your CLI version (for example `--size`), inspect `aether warm-slot assign --help` and rerun once with the smallest documented value (for example `--size medium`). Verification probe: ```bash theme={"dark"} AETHER_PROJECT= aether warm-slot list --json ``` The probe passes when a slot lists this project's ID. If the assign command fails for any reason, do not retry more than once and do not stop: record "prewarm: failed — the first task will cold-start" with the exact error and continue. Prewarm never blocks onboarding. ## 7. Install the aether-teleport skill The `/aether-teleport` skill lets a local coding agent hand its session off to Aether later. Install it when Claude Code or Codex is present in this environment (a `claude` or `codex` executable on `PATH`, or a `~/.claude` or `~/.codex` directory): ```bash theme={"dark"} aether skill install aether-teleport ``` The installer detects local agents and writes into their skill directories. Expected output names each installed directory. If it reports that no supported agent was detected, record "teleport skill: skipped (no local agent detected)" and continue; do not force a `--dir` the human did not ask for. Verification probe — check the directories the installer reported, for example: ```bash theme={"dark"} test -f ~/.claude/skills/aether-teleport/SKILL.md && echo claude-ok test -f ~/.agents/skills/aether-teleport/SKILL.md && echo codex-ok ``` The probe passes when every directory the installer reported contains `SKILL.md`. The installer is safe to rerun; retry once on failure, then record the failure and continue — this step never blocks onboarding. ## 8. Ask for consent before uploading secret files List gitignored file paths without reading or printing their contents. Exclude dependency and build-output directories so the list stays reviewable: ```bash theme={"dark"} git ls-files --others --ignored --exclude-standard -- . \ ':(exclude,glob)**/.git/**' \ ':(exclude,glob)**/node_modules/**' \ ':(exclude,glob)**/dist/**' \ ':(exclude,glob)**/build/**' \ ':(exclude,glob)**/.next/**' \ ':(exclude,glob)**/__pycache__/**' \ ':(exclude,glob)**/.venv/**' \ ':(exclude,glob)**/venv/**' \ ':(exclude,glob)**/.turbo/**' \ ':(exclude,glob)**/.cache/**' ``` Show the candidate **paths only** to your human and ask: **“Which of these exact files, if any, do you approve uploading to the default Aether project?”** Wait for an explicit answer. An empty selection is valid. For each and only each approved path, run one command from the repository root: ```bash theme={"dark"} AETHER_PROJECT= aether secret upload ``` Do not use `aether secret bulk-upload` in this guide. Expected output for each approved file: ```text theme={"dark"} Uploaded secret file . ``` Verification probe: ```bash theme={"dark"} AETHER_PROJECT= aether secret list --json ``` The probe passes when every approved path appears exactly once. If the human approved no files, the probe passes when it exits zero; record that this run uploaded zero files. Never use `aether secret get` as a probe because it prints decrypted contents. If an approved file is missing, unreadable, too large, or rejected, report that path without printing its contents and retry its individual upload once. Do not replace it with another candidate on your own. ## 9. Complete onboarding Run: ```bash theme={"dark"} aether onboarding complete ``` Expected output is one of these idempotent successes: ```text theme={"dark"} Onboarding completed. ``` ```text theme={"dark"} Onboarding already completed. ``` Verification probe: run the same idempotent command again. ```bash theme={"dark"} aether onboarding complete ``` The probe passes only if it exits zero and prints `Onboarding already completed.` If the server returns `403`, report its requirements-missing message verbatim, return to the named missing GitHub or provider step, and retry completion once after that probe passes. Never hide or paraphrase the server's missing-requirements error. ## 10. Launch the first task Check for the repository's Aether environment file without modifying it: ```bash theme={"dark"} if test -f .aether/environment.json; then echo present; else echo absent; fi ``` If the file is absent, use the CLI's canonical embedded environment-bootstrap prompt. Do not reproduce, summarize, or inline that prompt: ```bash theme={"dark"} AETHER_PROJECT= aether run --bootstrap-environment --json ``` If the file exists, start with a friendly exploration task: ```bash theme={"dark"} AETHER_PROJECT= aether run "explore this repository and suggest an improvement" --json ``` Expected stdout is a JSON task object containing a nonempty `id`. (`Creating task... done` may appear separately on stderr.) Extract the full task ID from the JSON and construct: ```text theme={"dark"} https://app.runaether.dev/agents/ ``` Verification probe: ```bash theme={"dark"} aether task get --json ``` The probe passes only when it exits zero and returns that same task ID. If task creation or lookup fails, retry the same branch once; do not switch away from the embedded bootstrap prompt to make the task easier to create. Give the verified web URL to your human and say: **“Aether is set up. Your first task is running here: ``.”** ## Final report to your human Report this checklist using only facts proven by the probes: ```text theme={"dark"} - Account: connected - GitHub: connected () - Coding provider(s): - Default project: (), linked to - Devbox prewarm: - Teleport skill: | skipped (no local agent detected)> - Secret files uploaded by this run: - Onboarding: completed - First task: ``` If any item is not proven, do not print a completed checklist. Stop at the failed step and report it honestly. # Release a billing warm slot Source: https://docs.runaether.dev/api-reference/account/billing/delete-billing-slots-by-slotid api-reference/openapi.account.json DELETE /billing/slots/{slotID} # Get billing catalog Source: https://docs.runaether.dev/api-reference/account/billing/get-billing-catalog api-reference/openapi.account.json GET /billing/catalog # Get billing checkout session Source: https://docs.runaether.dev/api-reference/account/billing/get-billing-checkout-sessions-by-lookup api-reference/openapi.account.json GET /billing/checkout-sessions/{lookup} # List billing credit packs Source: https://docs.runaether.dev/api-reference/account/billing/get-billing-credit-packs api-reference/openapi.account.json GET /billing/credit-packs # List billing warm slots Source: https://docs.runaether.dev/api-reference/account/billing/get-billing-slots api-reference/openapi.account.json GET /billing/slots # Get billing subscription Source: https://docs.runaether.dev/api-reference/account/billing/get-billing-subscription api-reference/openapi.account.json GET /billing/subscription # Create billing checkout session Source: https://docs.runaether.dev/api-reference/account/billing/post-billing-checkout-sessions api-reference/openapi.account.json POST /billing/checkout-sessions # Create billing portal session Source: https://docs.runaether.dev/api-reference/account/billing/post-billing-portal-sessions api-reference/openapi.account.json POST /billing/portal-sessions # Assign a project to a billing warm slot Source: https://docs.runaether.dev/api-reference/account/billing/post-billing-slots api-reference/openapi.account.json POST /billing/slots # Delete Claude Code auth Source: https://docs.runaether.dev/api-reference/account/claude-code-auth/delete-claude-code-auth api-reference/openapi.account.json DELETE /claude-code/auth # Get Claude Code auth status Source: https://docs.runaether.dev/api-reference/account/claude-code-auth/get-claude-code-auth api-reference/openapi.account.json GET /claude-code/auth # Store Claude Code Anthropic API key Source: https://docs.runaether.dev/api-reference/account/claude-code-auth/put-claude-code-auth-api-key api-reference/openapi.account.json PUT /claude-code/auth/api-key # Store Claude Code setup token Source: https://docs.runaether.dev/api-reference/account/claude-code-auth/put-claude-code-auth-setup-token api-reference/openapi.account.json PUT /claude-code/auth/setup-token # Delete Codex auth Source: https://docs.runaether.dev/api-reference/account/codex-auth/delete-codex-auth api-reference/openapi.account.json DELETE /codex/auth # Get Codex auth status Source: https://docs.runaether.dev/api-reference/account/codex-auth/get-codex-auth api-reference/openapi.account.json GET /codex/auth # Poll Codex device auth Source: https://docs.runaether.dev/api-reference/account/codex-auth/post-codex-device-auth-poll api-reference/openapi.account.json POST /codex/device-auth/poll # Start Codex device auth Source: https://docs.runaether.dev/api-reference/account/codex-auth/post-codex-device-auth-start api-reference/openapi.account.json POST /codex/device-auth/start # Store Codex API key auth Source: https://docs.runaether.dev/api-reference/account/codex-auth/put-codex-auth-api-key api-reference/openapi.account.json PUT /codex/auth/api-key # Delete Cursor auth Source: https://docs.runaether.dev/api-reference/account/cursor-auth/delete-cursor-auth api-reference/openapi.account.json DELETE /cursor/auth # Get Cursor auth status Source: https://docs.runaether.dev/api-reference/account/cursor-auth/get-cursor-auth api-reference/openapi.account.json GET /cursor/auth # Store Cursor subscription auth Source: https://docs.runaether.dev/api-reference/account/cursor-auth/put-cursor-auth-subscription api-reference/openapi.account.json PUT /cursor/auth/subscription # Submit feedback Source: https://docs.runaether.dev/api-reference/account/feedback/post-feedback api-reference/openapi.account.json POST /feedback # Disconnect GitHub Source: https://docs.runaether.dev/api-reference/account/github/delete-github-connection api-reference/openapi.account.json DELETE /github/connection # Get GitHub authorize URL Source: https://docs.runaether.dev/api-reference/account/github/get-auth-github-authorize-url api-reference/openapi.account.json GET /auth/github/authorize-url # Get GitHub install URL Source: https://docs.runaether.dev/api-reference/account/github/get-auth-github-url api-reference/openapi.account.json GET /auth/github/url # List GitHub repos Source: https://docs.runaether.dev/api-reference/account/github/get-github-repos api-reference/openapi.account.json GET /github/repos # Get GitHub status Source: https://docs.runaether.dev/api-reference/account/github/get-github-status api-reference/openapi.account.json GET /github/status # List GitHub repo branch suggestions Source: https://docs.runaether.dev/api-reference/account/github/get-projects-by-id-github-branch-suggestions api-reference/openapi.account.json GET /projects/{id}/github/branch-suggestions # Get GitHub git status Source: https://docs.runaether.dev/api-reference/account/github/get-projects-by-id-github-git-status api-reference/openapi.account.json GET /projects/{id}/github/git-status # Get GitHub project link Source: https://docs.runaether.dev/api-reference/account/github/get-projects-by-id-github-link api-reference/openapi.account.json GET /projects/{id}/github/link # Fetch GitHub repo tree Source: https://docs.runaether.dev/api-reference/account/github/get-projects-by-id-github-tree api-reference/openapi.account.json GET /projects/{id}/github/tree # Claim GitHub installations from an OAuth code Source: https://docs.runaether.dev/api-reference/account/github/post-github-installations-claim api-reference/openapi.account.json POST /github/installations/claim # Export GitHub repo Source: https://docs.runaether.dev/api-reference/account/github/post-projects-by-id-github-export api-reference/openapi.account.json POST /projects/{id}/github/export # Import GitHub repo Source: https://docs.runaether.dev/api-reference/account/github/post-projects-by-id-github-import api-reference/openapi.account.json POST /projects/{id}/github/import # Disconnect Linear Source: https://docs.runaether.dev/api-reference/account/linear/delete-linear-connection api-reference/openapi.account.json DELETE /linear/connection # Delete Linear route Source: https://docs.runaether.dev/api-reference/account/linear/delete-linear-routes-by-routeid api-reference/openapi.account.json DELETE /linear/routes/{routeId} # Get Linear install URL Source: https://docs.runaether.dev/api-reference/account/linear/get-auth-linear-url api-reference/openapi.account.json GET /auth/linear/url # List Linear projects Source: https://docs.runaether.dev/api-reference/account/linear/get-linear-projects api-reference/openapi.account.json GET /linear/projects # List Linear routes Source: https://docs.runaether.dev/api-reference/account/linear/get-linear-routes api-reference/openapi.account.json GET /linear/routes # Get Linear status Source: https://docs.runaether.dev/api-reference/account/linear/get-linear-status api-reference/openapi.account.json GET /linear/status # List Linear teams Source: https://docs.runaether.dev/api-reference/account/linear/get-linear-teams api-reference/openapi.account.json GET /linear/teams # Update Linear connection Source: https://docs.runaether.dev/api-reference/account/linear/patch-linear-connection api-reference/openapi.account.json PATCH /linear/connection # Update Linear route Source: https://docs.runaether.dev/api-reference/account/linear/patch-linear-routes-by-routeid api-reference/openapi.account.json PATCH /linear/routes/{routeId} # Create Linear route Source: https://docs.runaether.dev/api-reference/account/linear/post-linear-routes api-reference/openapi.account.json POST /linear/routes # Get notification preferences Source: https://docs.runaether.dev/api-reference/account/notifications/get-user-notification-preferences api-reference/openapi.account.json GET /user/notification-preferences # List notifications Source: https://docs.runaether.dev/api-reference/account/notifications/get-user-notifications api-reference/openapi.account.json GET /user/notifications # Dismiss notification Source: https://docs.runaether.dev/api-reference/account/notifications/post-user-notifications-by-id-dismiss api-reference/openapi.account.json POST /user/notifications/{id}/dismiss # Resolve task notifications Source: https://docs.runaether.dev/api-reference/account/notifications/post-user-notifications-tasks-by-taskid-resolve api-reference/openapi.account.json POST /user/notifications/tasks/{taskID}/resolve Resolve every active notification for the task, regardless of kind. Clients call this when the task is viewed. # Update notification preferences Source: https://docs.runaether.dev/api-reference/account/notifications/put-user-notification-preferences api-reference/openapi.account.json PUT /user/notification-preferences # Delete opencode auth Source: https://docs.runaether.dev/api-reference/account/opencode-auth/delete-opencode-auth api-reference/openapi.account.json DELETE /opencode/auth # Get opencode auth status Source: https://docs.runaether.dev/api-reference/account/opencode-auth/get-opencode-auth api-reference/openapi.account.json GET /opencode/auth # Store opencode auth file Source: https://docs.runaether.dev/api-reference/account/opencode-auth/put-opencode-auth-file api-reference/openapi.account.json PUT /opencode/auth/file # Store an OpenRouter key in the OpenCode connection Source: https://docs.runaether.dev/api-reference/account/opencode-auth/put-opencode-auth-openrouter api-reference/openapi.account.json PUT /opencode/auth/openrouter # Revoke an organization invitation Source: https://docs.runaether.dev/api-reference/account/orgs/delete-orgs-by-orgid-invites-by-inviteid api-reference/openapi.account.json DELETE /orgs/{orgID}/invites/{inviteID} # Remove an organization member Source: https://docs.runaether.dev/api-reference/account/orgs/delete-orgs-by-orgid-members-by-userid api-reference/openapi.account.json DELETE /orgs/{orgID}/members/{userID} Returns counts of active resources attributed to the removed member that may require credential reassignment. # List my pending organization invitations Source: https://docs.runaether.dev/api-reference/account/orgs/get-invites api-reference/openapi.account.json GET /invites # List my organizations Source: https://docs.runaether.dev/api-reference/account/orgs/get-orgs api-reference/openapi.account.json GET /orgs # List pending organization invitations Source: https://docs.runaether.dev/api-reference/account/orgs/get-orgs-by-orgid-invites api-reference/openapi.account.json GET /orgs/{orgID}/invites # List organization members Source: https://docs.runaether.dev/api-reference/account/orgs/get-orgs-by-orgid-members api-reference/openapi.account.json GET /orgs/{orgID}/members # Change an organization member role Source: https://docs.runaether.dev/api-reference/account/orgs/patch-orgs-by-orgid-members-by-userid api-reference/openapi.account.json PATCH /orgs/{orgID}/members/{userID} # Accept an organization invitation Source: https://docs.runaether.dev/api-reference/account/orgs/post-invites-by-inviteid-accept api-reference/openapi.account.json POST /invites/{inviteID}/accept # Create a team organization Source: https://docs.runaether.dev/api-reference/account/orgs/post-orgs api-reference/openapi.account.json POST /orgs # Invite a user to an organization Source: https://docs.runaether.dev/api-reference/account/orgs/post-orgs-by-orgid-invites api-reference/openapi.account.json POST /orgs/{orgID}/invites # Leave an organization Source: https://docs.runaether.dev/api-reference/account/orgs/post-orgs-by-orgid-leave api-reference/openapi.account.json POST /orgs/{orgID}/leave # Revoke platform API key Source: https://docs.runaether.dev/api-reference/account/platform-keys/delete-user-platform-keys-by-id api-reference/openapi.account.json DELETE /user/platform-keys/{id} # List platform API keys Source: https://docs.runaether.dev/api-reference/account/platform-keys/get-user-platform-keys api-reference/openapi.account.json GET /user/platform-keys # Create platform API key Source: https://docs.runaether.dev/api-reference/account/platform-keys/post-user-platform-keys api-reference/openapi.account.json POST /user/platform-keys # List reviewed pull requests with their review runs Source: https://docs.runaether.dev/api-reference/account/pr-reviews/get-pr-reviews api-reference/openapi.account.json GET /pr-reviews One group per reviewed pull request. Without view, groups are newest activity first. Grouping and the review-run status are resolved server-side so the listing can be paginated by group. needs_attention marks an open group requiring user action from its newest run or posted verdict; it does not affect default ordering. view=queue returns open pull requests only, ordered by needs_attention then newest activity. status and pr_state are comma-joined filters; status matches the group's LATEST run status — the status the list row displays — not any run in the group. # Get one reviewed pull request with its review runs Source: https://docs.runaether.dev/api-reference/account/pr-reviews/get-pr-reviews-by-pullrequestid api-reference/openapi.account.json GET /pr-reviews/{pullRequestID} The single-PR read of listPRReviews: the same group facts plus the pull request description and captured reviews posted to the PR (newest first), addressed by the pull_request_id every listed group carries. Reviews posted before capture shipped have no content row and are honestly absent. 404 covers both a pull request that does not exist and one the caller's organizations have no review runs against — the two are deliberately indistinguishable. The caller must also retain current GitHub installation and repository access before the response exposes workspace preview URLs; revoked access returns the same 404. # Get one file's content at a reviewed commit of a pull request Source: https://docs.runaether.dev/api-reference/account/pr-reviews/get-pr-reviews-by-pullrequestid-file-content api-reference/openapi.account.json GET /pr-reviews/{pullRequestID}/file-content Fetched live from GitHub at request time; nothing is persisted or cached. ref must be a full commit SHA the review group actually reviewed — the pull request's reconciled head or a review run's head — and any other SHA is a 404 before a single GitHub call: the endpoint serves reviewed commits, not arbitrary ones. kind discriminates the body: text carries content, and binary, too_large (GitHub's 1 MB contents cap) and not_found carry none. A path with no file at that commit — deleted there, never added there, or naming a directory — is a 200 not_found, NOT a 404: this route's 404 means the caller may not read this pull request at this ref at all (no review group, no current GitHub installation link, or an unreviewed ref), and a client cannot tell those from a missing file by status alone. # Delete your account Source: https://docs.runaether.dev/api-reference/account/profile/delete-profile api-reference/openapi.account.json DELETE /profile Permanently deletes the authenticated account and everything it owns: workspaces, tasks, projects, secrets, and personal data. The subject is always the caller's own session — there is no target parameter and no undo. Answers 202 once the staged pipeline is enqueued; it then runs asynchronously and revokes every session. Requires session-authenticated credentials: a platform API key is refused with 403 session_auth_required. An account that still owns team organizations is refused with 409 team_ownership_transfer_required. Once deletion has begun every authenticated request from the account — including a repeat of this one — is refused with 403 account_deleting. # Get profile Source: https://docs.runaether.dev/api-reference/account/profile/get-profile api-reference/openapi.account.json GET /profile # Complete onboarding Source: https://docs.runaether.dev/api-reference/account/profile/post-profile-onboarding-complete api-reference/openapi.account.json POST /profile/onboarding/complete Completes onboarding without creating a project or launch task for either a user who belongs to a team or a self-serve user with a connected GitHub installation and a connected Codex, Claude Code, or OpenCode credential. # Launch the onboarding task Source: https://docs.runaether.dev/api-reference/account/profile/post-profile-onboarding-launch api-reference/openapi.account.json POST /profile/onboarding/launch Idempotently creates the user's first task and completes onboarding in one transaction. Repeat calls return the already-launched task. If onboarding completed without a linked launch, returns 409 with code onboarding_already_completed. # Set up the onboarding repository Source: https://docs.runaether.dev/api-reference/account/profile/post-profile-onboarding-repo api-reference/openapi.account.json POST /profile/onboarding/repo Idempotently creates the user's project for a GitHub repo — project row, repo link, env vars, and skill indexing commit in one transaction. Repeat calls for the same repo return the already-linked project. # List Aether feedback dispatches for a pull request Source: https://docs.runaether.dev/api-reference/account/pull-requests/get-pull-requests-by-pullrequestid-github-dispatches api-reference/openapi.account.json GET /pull-requests/{pullRequestID}/github/dispatches Returns 404 when the pull request has no same-org authoring Aether task. Requires membership in the owning organization of a task linked to this pull request — the task that published it or a task that reviewed it — and current GitHub access to the repository. 404 covers an unknown pull request, one linked to no organization the caller belongs to, and one whose GitHub access the caller has lost; the three are deliberately indistinguishable. # Get a pull request's merge box and live checks Source: https://docs.runaether.dev/api-reference/account/pull-requests/get-pull-requests-by-pullrequestid-github-pr api-reference/openapi.account.json GET /pull-requests/{pullRequestID}/github/pr The merge box comes from the pull request mirror; ci_status and the check counts come from the same live GitHub fetch as the checks list, so both halves of one response agree even while webhooks lag. Requires membership in the owning organization of a task linked to this pull request — the task that published it or a task that reviewed it — and current GitHub access to the repository. 404 covers an unknown pull request, one linked to no organization the caller belongs to, and one whose GitHub access the caller has lost; the three are deliberately indistinguishable. # Get a pull request's changed files with unified-diff patches Source: https://docs.runaether.dev/api-reference/account/pull-requests/get-pull-requests-by-pullrequestid-github-pr-diff api-reference/openapi.account.json GET /pull-requests/{pullRequestID}/github/pr/diff Fetched live from GitHub at request time; nothing is persisted. patch is absent when GitHub provides none (binary or oversized files). truncated is true when the listing hit GitHub's 3000-file cap. expected_head is optional and pins the answer to one commit: GitHub computes this listing against the pull request's head at request time, so the head is read back after the walk and a head that has moved is a 409 rather than the file list of a different commit. Pass it when the patches will be paired with file content read at that same commit; omit it to read whatever the head is now. Requires membership in the owning organization of a task linked to this pull request — the task that published it or a task that reviewed it — and current GitHub access to the repository. 404 covers an unknown pull request, one linked to no organization the caller belongs to, and one whose GitHub access the caller has lost; the three are deliberately indistinguishable. # List a pull request's review threads Source: https://docs.runaether.dev/api-reference/account/pull-requests/get-pull-requests-by-pullrequestid-github-pr-threads api-reference/openapi.account.json GET /pull-requests/{pullRequestID}/github/pr/threads Fetched live from GitHub GraphQL at request time; nothing is persisted. The listing is all-or-nothing — pagination runs to exhaustion and a pull request past the hard caps fails with 502 rather than returning a truncated 200. Requires membership in the owning organization of a task linked to this pull request — the task that published it or a task that reviewed it — and current GitHub access to the repository. 404 covers an unknown pull request, one linked to no organization the caller belongs to, and one whose GitHub access the caller has lost; the three are deliberately indistinguishable. # Dispatch pull request feedback to Aether Source: https://docs.runaether.dev/api-reference/account/pull-requests/post-pull-requests-by-pullrequestid-github-dispatch api-reference/openapi.account.json POST /pull-requests/{pullRequestID}/github/dispatch Targets only a same-org Aether task that published this exact pull request. Requires membership in the owning organization of a task linked to this pull request — the task that published it or a task that reviewed it — and current GitHub access to the repository. 404 covers an unknown pull request, one linked to no organization the caller belongs to, and one whose GitHub access the caller has lost; the three are deliberately indistinguishable. # Post a top-level comment on a pull request Source: https://docs.runaether.dev/api-reference/account/pull-requests/post-pull-requests-by-pullrequestid-github-pr-comments api-reference/openapi.account.json POST /pull-requests/{pullRequestID}/github/pr/comments The idempotency key is claimed before GitHub is called: completed attempts replay successfully, while ambiguous or failed claims conflict without posting again. Requires membership in the owning organization of a task linked to this pull request — the task that published it or a task that reviewed it — and current GitHub access to the repository. 404 covers an unknown pull request, one linked to no organization the caller belongs to, and one whose GitHub access the caller has lost; the three are deliberately indistinguishable. # Reply to a review comment on a pull request Source: https://docs.runaether.dev/api-reference/account/pull-requests/post-pull-requests-by-pullrequestid-github-pr-comments-by-commentid-reply api-reference/openapi.account.json POST /pull-requests/{pullRequestID}/github/pr/comments/{commentID}/reply The reply is posted by the Aether GitHub app, footer-attributed to the authenticated caller, and recorded against this pull request in the GitHub post-origin ledger so its own webhook echo never wakes a task. The idempotency key is claimed before GitHub is called: completed attempts replay the same reply. Requires membership in the owning organization of a task linked to this pull request — the task that published it or a task that reviewed it — and current GitHub access to the repository. 404 covers an unknown pull request, one linked to no organization the caller belongs to, and one whose GitHub access the caller has lost; the three are deliberately indistinguishable. # Resolve the review thread containing a comment on a pull request Source: https://docs.runaether.dev/api-reference/account/pull-requests/post-pull-requests-by-pullrequestid-github-pr-comments-by-commentid-resolve api-reference/openapi.account.json POST /pull-requests/{pullRequestID}/github/pr/comments/{commentID}/resolve Idempotent by GitHub state: resolving an already-resolved thread succeeds. Additionally requires the caller to hold push permission on the repository: a caller who can read the pull request but not push to its repository is refused with 403 github_push_access_required. Requires membership in the owning organization of a task linked to this pull request — the task that published it or a task that reviewed it — and current GitHub access to the repository. 404 covers an unknown pull request, one linked to no organization the caller belongs to, and one whose GitHub access the caller has lost; the three are deliberately indistinguishable. # Merge a pull request Source: https://docs.runaether.dev/api-reference/account/pull-requests/post-pull-requests-by-pullrequestid-github-pr-merge api-reference/openapi.account.json POST /pull-requests/{pullRequestID}/github/pr/merge Squash-merges the exact pull request row named by pullRequestID, pinned to the head commit and the number the caller confirmed. Additionally requires the caller to hold push permission on the repository: a caller who can read the pull request but not push to its repository is refused with 403 github_push_access_required. Requires membership in the owning organization of a task linked to this pull request — the task that published it or a task that reviewed it — and current GitHub access to the repository. 404 covers an unknown pull request, one linked to no organization the caller belongs to, and one whose GitHub access the caller has lost; the three are deliberately indistinguishable. # Submit a human pull request review Source: https://docs.runaether.dev/api-reference/account/pull-requests/post-pull-requests-by-pullrequestid-github-review api-reference/openapi.account.json POST /pull-requests/{pullRequestID}/github/review Posts the verdict through the authenticated caller's own GitHub identity and pins it to expected_head_sha. No app-identity fallback is used. The idempotency key is actor-scoped and claimed before GitHub is called: completed attempts replay the same review, while failed claims conflict without posting again. Requires membership in the owning organization of a task linked to this pull request — the task that published it or a task that reviewed it — and current GitHub access to the repository. 404 covers an unknown pull request, one linked to no organization the caller belongs to, and one whose GitHub access the caller has lost; the three are deliberately indistinguishable. # Request an Aether review of a pull request Source: https://docs.runaether.dev/api-reference/account/pull-requests/post-pull-requests-by-pullrequestid-github-review-runs api-reference/openapi.account.json POST /pull-requests/{pullRequestID}/github/review-runs Wakes the pull request's existing review task at the current live GitHub head, including when that head is unchanged. Creates a manual-triggered review task only when the pull request has no review task. Repeated requests at one head replay without delivering a duplicate instruction. Requires membership in the owning organization of a task linked to this pull request — the task that published it or a task that reviewed it — and current GitHub access to the repository. 404 covers an unknown pull request, one linked to no organization the caller belongs to, and one whose GitHub access the caller has lost; the three are deliberately indistinguishable. # Register an Expo push token for the current user Source: https://docs.runaether.dev/api-reference/account/push-tokens/post-user-push-tokens api-reference/openapi.account.json POST /user/push-tokens # Unregister an Expo push token for the current user Source: https://docs.runaether.dev/api-reference/account/push-tokens/post-user-push-tokens-unregister api-reference/openapi.account.json POST /user/push-tokens/unregister # Disconnect Sentry Source: https://docs.runaether.dev/api-reference/account/sentry/delete-sentry-connection api-reference/openapi.account.json DELETE /sentry/connection # Delete Sentry project link Source: https://docs.runaether.dev/api-reference/account/sentry/delete-sentry-links-by-linkid api-reference/openapi.account.json DELETE /sentry/links/{linkId} # Get Sentry install URL Source: https://docs.runaether.dev/api-reference/account/sentry/get-auth-sentry-url api-reference/openapi.account.json GET /auth/sentry/url # List Sentry project links Source: https://docs.runaether.dev/api-reference/account/sentry/get-sentry-links api-reference/openapi.account.json GET /sentry/links # List Sentry projects Source: https://docs.runaether.dev/api-reference/account/sentry/get-sentry-projects api-reference/openapi.account.json GET /sentry/projects # Get Sentry status Source: https://docs.runaether.dev/api-reference/account/sentry/get-sentry-status api-reference/openapi.account.json GET /sentry/status # Update Sentry connection Source: https://docs.runaether.dev/api-reference/account/sentry/patch-sentry-connection api-reference/openapi.account.json PATCH /sentry/connection # Update Sentry project link Source: https://docs.runaether.dev/api-reference/account/sentry/patch-sentry-links-by-linkid api-reference/openapi.account.json PATCH /sentry/links/{linkId} # Create Sentry project link Source: https://docs.runaether.dev/api-reference/account/sentry/post-sentry-links api-reference/openapi.account.json POST /sentry/links # Disconnect Slack Source: https://docs.runaether.dev/api-reference/account/slack/delete-slack-connection api-reference/openapi.account.json DELETE /slack/connection # Unlink my Slack identity Source: https://docs.runaether.dev/api-reference/account/slack/delete-slack-identity-link api-reference/openapi.account.json DELETE /slack/identity/link # Delete Slack route Source: https://docs.runaether.dev/api-reference/account/slack/delete-slack-routes-by-routeid api-reference/openapi.account.json DELETE /slack/routes/{routeId} # Get Slack install URL Source: https://docs.runaether.dev/api-reference/account/slack/get-auth-slack-url api-reference/openapi.account.json GET /auth/slack/url # List Slack channels Source: https://docs.runaether.dev/api-reference/account/slack/get-slack-channels api-reference/openapi.account.json GET /slack/channels # List Slack routes Source: https://docs.runaether.dev/api-reference/account/slack/get-slack-routes api-reference/openapi.account.json GET /slack/routes # Get Slack status Source: https://docs.runaether.dev/api-reference/account/slack/get-slack-status api-reference/openapi.account.json GET /slack/status # Update Slack connection Source: https://docs.runaether.dev/api-reference/account/slack/patch-slack-connection api-reference/openapi.account.json PATCH /slack/connection # Update Slack route Source: https://docs.runaether.dev/api-reference/account/slack/patch-slack-routes-by-routeid api-reference/openapi.account.json PATCH /slack/routes/{routeId} # Link my Slack identity Source: https://docs.runaether.dev/api-reference/account/slack/post-slack-identity-link api-reference/openapi.account.json POST /slack/identity/link # Create Slack route Source: https://docs.runaether.dev/api-reference/account/slack/post-slack-routes api-reference/openapi.account.json POST /slack/routes # Get dismissed discovery keys Source: https://docs.runaether.dev/api-reference/account/user-discovery/get-user-discovery api-reference/openapi.account.json GET /user/discovery # Dismiss a discovery key Source: https://docs.runaether.dev/api-reference/account/user-discovery/post-user-discovery-dismiss api-reference/openapi.account.json POST /user/discovery/dismiss # Get user settings Source: https://docs.runaether.dev/api-reference/account/user-settings/get-user-settings api-reference/openapi.account.json GET /user/settings # Update user settings Source: https://docs.runaether.dev/api-reference/account/user-settings/put-user-settings api-reference/openapi.account.json PUT /user/settings # Authentication Source: https://docs.runaether.dev/api-reference/authentication Authenticate with a platform API key or a session JWT Every Aether API request carries a Bearer token in the `Authorization` header: ``` Authorization: Bearer ``` Aether accepts two kinds of token, and both resolve to your user account: * **Platform API key** — a token prefixed with `aether_`. This is the credential for programmatic and public API use. Mint it yourself; it does not expire on a timer. * **Session JWT** — a Supabase-issued token the web app obtains when you sign in. It is short-lived and refreshed automatically by the browser client. For your own scripts and integrations, use a platform API key. ## Platform API Keys A platform API key is the credential intended for the public `/v1` API. The token starts with `aether_` and is shown in plaintext exactly once, when you create it — Aether stores only a hash, so save it somewhere safe. ```bash theme={"dark"} aether token create --name "CI pipeline" ``` The command prints the new key once. List and revoke keys with: ```bash theme={"dark"} aether token list aether token revoke ``` Mint a key with [Create platform API key](/api-reference/account/platform-keys/post-user-platform-keys). The response includes the plaintext `key` field — the only time the full token is returned. List your keys with [List platform API keys](/api-reference/account/platform-keys/get-user-platform-keys) and revoke one with [Revoke platform API key](/api-reference/account/platform-keys/delete-user-platform-keys-by-id). Use the key against the `/v1` base URL: ```bash theme={"dark"} curl https://api.runaether.dev/v1/projects \ -H "Authorization: Bearer aether_..." ``` A platform API key acts on behalf of your account. Treat it like a password — store it in a secret manager, never commit it, and revoke any key that may have leaked. ## Session JWT When you sign in to the web app, Aether issues a Supabase session JWT carrying your user ID. The web app uses it for its own requests and refreshes it automatically. You normally don't handle this token directly; reach for a platform API key for anything programmatic. The CLI does not keep a session. `aether auth login` signs you in, exchanges that sign-in for a platform API key named `cli-`, and stores the key — so every CLI request is platform-key authenticated, and revoking that key under **Settings → API keys** logs the machine out. ## WebSocket Authentication The workspace WebSocket at `/workspaces/{id}/ws` reads the token from the `Sec-WebSocket-Protocol` header, since browsers can't set custom headers on a WebSocket. Send `bearer` followed by your token as subprotocols: ```javascript theme={"dark"} const ws = new WebSocket( "wss://api.runaether.dev/workspaces//ws", ["bearer", ""] ); ``` ## Provider API Keys Are Different Don't confuse platform API keys with **provider API keys**. A provider key is an LLM-provider credential (such as OpenRouter) you optionally bring so the agent uses your own account for model calls — "bring your own key" (BYOK). It does not authenticate the Aether API. Manage provider keys with `aether opencode api-key`: ```bash theme={"dark"} aether opencode api-key aether opencode status aether opencode disconnect ``` See [connect agent providers](/guides/connect-agent-providers) for when you need one. ## Unauthenticated Requests A request with a missing or invalid token returns `401 Unauthorized`: ```json theme={"dark"} { "error": "missing authorization header" } ``` # API Overview Source: https://docs.runaether.dev/api-reference/overview The Aether REST API: base URL, scope, request format, errors, and pagination Aether exposes a REST API for managing projects, workspaces, tasks, and related resources programmatically. Requests and responses are JSON, and every endpoint is authenticated. ## Base URL The public, versioned API lives under the `/v1` prefix: ``` https://api.runaether.dev/v1 ``` A request to list your projects is therefore: ```bash theme={"dark"} curl https://api.runaether.dev/v1/projects \ -H "Authorization: Bearer aether_..." ``` ## Scope Only the versioned `/v1` groups are part of the public API: * `/v1/projects` * `/v1/workspaces` * `/v1/tasks` * `/v1/projects/{id}/env-vars` * `/v1/projects/{id}/secret-files` * `/v1/skills` * `/v1/automations` * `/v1/usage` Account and integration endpoints — profile, billing, notifications, platform keys, provider API keys, GitHub, Sentry, Linear, Slack, and the agent-auth groups — are mounted at the root (no `/v1` prefix) and are used by the web app and CLI. They are documented in the API Reference for completeness but are not part of the stable versioned surface. Internal service-to-service endpoints (`/internal/*`), inbound webhooks, OAuth callbacks, and operator routes are not part of the public API. The same handlers backing `/v1` are also reachable at the root path for the first-party web app and CLI. The difference is CORS: `/v1/*` allows any origin, while the root surface is restricted to the Aether frontend origin. For your own integrations, always use `/v1`. ## Authentication Every endpoint requires a Bearer token in the `Authorization` header — either an `aether_` platform API key or a session JWT. See [Authentication](/api-reference/authentication) for how to mint a key and what each credential is for. ## Request Format All request and response bodies are JSON. Set `Content-Type: application/json` on any request that has a body. ## Errors Errors return an HTTP status code with a JSON body containing a single `error` field: ```json theme={"dark"} { "error": "Project not found" } ``` | Code | Meaning | | ----- | ----------------------------------------------------------- | | `200` | Success | | `201` | Created | | `202` | Accepted — work was enqueued (for example, creating a task) | | `204` | No Content — successful deletion or update with no body | | `400` | Bad Request — malformed input | | `401` | Unauthorized — missing or invalid token | | `404` | Not Found | | `409` | Conflict | | `422` | Unprocessable Entity — validation failed | | `500` | Internal Server Error | Each endpoint documents the exact status codes it can return. See the per-endpoint pages under this section. ## Pagination List endpoints page with two query parameters: | Parameter | Description | | --------- | ------------------------------------------------- | | `page` | Page number, 1-based. Defaults to the first page. | | `limit` | Items per page. Maximum `100`. | ```bash theme={"dark"} curl "https://api.runaether.dev/v1/tasks?page=2&limit=50" \ -H "Authorization: Bearer aether_..." ``` The [usage summary](/api-reference/v1/usage/get-usage-summary) endpoint paginates its per-task breakdown with `task_offset` and `task_limit` instead. # Delete agent profile Source: https://docs.runaether.dev/api-reference/v1/agent-profiles/delete-agent-profiles-by-profileid api-reference/openapi.v1.json DELETE /agent-profiles/{profileID} # List agent profiles Source: https://docs.runaether.dev/api-reference/v1/agent-profiles/get-agent-profiles api-reference/openapi.v1.json GET /agent-profiles # Create agent profile Source: https://docs.runaether.dev/api-reference/v1/agent-profiles/post-agent-profiles api-reference/openapi.v1.json POST /agent-profiles # Update agent profile Source: https://docs.runaether.dev/api-reference/v1/agent-profiles/put-agent-profiles-by-profileid api-reference/openapi.v1.json PUT /agent-profiles/{profileID} # Delete automation Source: https://docs.runaether.dev/api-reference/v1/automations/delete-automations-by-automationid api-reference/openapi.v1.json DELETE /automations/{automationID} # List automations Source: https://docs.runaether.dev/api-reference/v1/automations/get-automations api-reference/openapi.v1.json GET /automations # List automation runs Source: https://docs.runaether.dev/api-reference/v1/automations/get-automations-by-automationid-runs api-reference/openapi.v1.json GET /automations/{automationID}/runs # Pause automation Source: https://docs.runaether.dev/api-reference/v1/automations/post-automations-by-automationid-pause api-reference/openapi.v1.json POST /automations/{automationID}/pause # Resume automation Source: https://docs.runaether.dev/api-reference/v1/automations/post-automations-by-automationid-resume api-reference/openapi.v1.json POST /automations/{automationID}/resume # Create project automation Source: https://docs.runaether.dev/api-reference/v1/automations/post-projects-by-projectid-automations api-reference/openapi.v1.json POST /projects/{projectID}/automations # Update automation Source: https://docs.runaether.dev/api-reference/v1/automations/put-automations-by-automationid api-reference/openapi.v1.json PUT /automations/{automationID} # Delete project environment variable Source: https://docs.runaether.dev/api-reference/v1/env-vars/delete-projects-by-id-env-vars-by-key api-reference/openapi.v1.json DELETE /projects/{id}/env-vars/{key} # List project environment variables Source: https://docs.runaether.dev/api-reference/v1/env-vars/get-projects-by-id-env-vars api-reference/openapi.v1.json GET /projects/{id}/env-vars # Get project environment variable Source: https://docs.runaether.dev/api-reference/v1/env-vars/get-projects-by-id-env-vars-by-key api-reference/openapi.v1.json GET /projects/{id}/env-vars/{key} # Import project environment variables Source: https://docs.runaether.dev/api-reference/v1/env-vars/post-projects-by-id-env-vars-import api-reference/openapi.v1.json POST /projects/{id}/env-vars/import # Set project environment variable Source: https://docs.runaether.dev/api-reference/v1/env-vars/put-projects-by-id-env-vars-by-key api-reference/openapi.v1.json PUT /projects/{id}/env-vars/{key} # List projects Source: https://docs.runaether.dev/api-reference/v1/projects/get-projects api-reference/openapi.v1.json GET /projects # Get project Source: https://docs.runaether.dev/api-reference/v1/projects/get-projects-by-id api-reference/openapi.v1.json GET /projects/{id} # Get project runtime options Source: https://docs.runaether.dev/api-reference/v1/projects/get-projects-by-projectid-runtime-options api-reference/openapi.v1.json GET /projects/{projectID}/runtime-options # Update project Source: https://docs.runaether.dev/api-reference/v1/projects/patch-projects-by-id api-reference/openapi.v1.json PATCH /projects/{id} # Create project Source: https://docs.runaether.dev/api-reference/v1/projects/post-projects api-reference/openapi.v1.json POST /projects # Archive project Source: https://docs.runaether.dev/api-reference/v1/projects/post-projects-by-id-archive api-reference/openapi.v1.json POST /projects/{id}/archive # Delete project secret file Source: https://docs.runaether.dev/api-reference/v1/secret-files/delete-projects-by-id-secret-files-by-file-key api-reference/openapi.v1.json DELETE /projects/{id}/secret-files/{file_key} # List project secret files Source: https://docs.runaether.dev/api-reference/v1/secret-files/get-projects-by-id-secret-files api-reference/openapi.v1.json GET /projects/{id}/secret-files # Get project secret file Source: https://docs.runaether.dev/api-reference/v1/secret-files/get-projects-by-id-secret-files-by-file-key api-reference/openapi.v1.json GET /projects/{id}/secret-files/{file_key} # Upload project secret file Source: https://docs.runaether.dev/api-reference/v1/secret-files/post-projects-by-id-secret-files api-reference/openapi.v1.json POST /projects/{id}/secret-files # Bulk upload project secret files Source: https://docs.runaether.dev/api-reference/v1/secret-files/post-projects-by-id-secret-files-bulk api-reference/openapi.v1.json POST /projects/{id}/secret-files/bulk # Delete user skill Source: https://docs.runaether.dev/api-reference/v1/skills/delete-skills-by-slug api-reference/openapi.v1.json DELETE /skills/{slug} # List default skills Source: https://docs.runaether.dev/api-reference/v1/skills/get-default-skills api-reference/openapi.v1.json GET /default-skills The skills Aether provides to every workspace, with whether the current user receives each one. # List project skills Source: https://docs.runaether.dev/api-reference/v1/skills/get-projects-by-id-skills api-reference/openapi.v1.json GET /projects/{id}/skills # List user skills Source: https://docs.runaether.dev/api-reference/v1/skills/get-skills api-reference/openapi.v1.json GET /skills # Get user skill Source: https://docs.runaether.dev/api-reference/v1/skills/get-skills-by-slug api-reference/openapi.v1.json GET /skills/{slug} # Upload user skill Source: https://docs.runaether.dev/api-reference/v1/skills/post-skills api-reference/openapi.v1.json POST /skills # Enable or disable a default skill Source: https://docs.runaether.dev/api-reference/v1/skills/put-default-skills-by-slug api-reference/openapi.v1.json PUT /default-skills/{slug} Turns one of Aether's default skills on or off for the current user's workspaces. # Unarchive a task Source: https://docs.runaether.dev/api-reference/v1/tasks/delete-tasks-by-taskid-archive api-reference/openapi.v1.json DELETE /tasks/{taskID}/archive # Unpin a task Source: https://docs.runaether.dev/api-reference/v1/tasks/delete-tasks-by-taskid-pin api-reference/openapi.v1.json DELETE /tasks/{taskID}/pin # List tasks Source: https://docs.runaether.dev/api-reference/v1/tasks/get-tasks api-reference/openapi.v1.json GET /tasks # Get a task with its activity Source: https://docs.runaether.dev/api-reference/v1/tasks/get-tasks-by-taskid api-reference/openapi.v1.json GET /tasks/{taskID} # Get a page of task conversation turn summaries Source: https://docs.runaether.dev/api-reference/v1/tasks/get-tasks-by-taskid-conversation-turns api-reference/openapi.v1.json GET /tasks/{taskID}/conversation/turns # Get a page of activity owned by a task conversation turn Source: https://docs.runaether.dev/api-reference/v1/tasks/get-tasks-by-taskid-conversation-turns-by-turnid-activity api-reference/openapi.v1.json GET /tasks/{taskID}/conversation/turns/{turnID}/activity # List media captured for a task Source: https://docs.runaether.dev/api-reference/v1/tasks/get-tasks-by-taskid-media api-reference/openapi.v1.json GET /tasks/{taskID}/media # Get the content of one referenced conversation attachment Source: https://docs.runaether.dev/api-reference/v1/tasks/get-tasks-by-taskid-messages-by-messageid-attachments-by-sha256 api-reference/openapi.v1.json GET /tasks/{taskID}/messages/{messageID}/attachments/{sha256} # List the provider-native subagents a task owns Source: https://docs.runaether.dev/api-reference/v1/tasks/get-tasks-by-taskid-subagents api-reference/openapi.v1.json GET /tasks/{taskID}/subagents Claude Code Task agents, background shells and workflows, and Codex collab agents the task's agent spawned. These are not Aether tasks: they have no workspace, branch, or pull request of their own. # Read one subagent's transcript Source: https://docs.runaether.dev/api-reference/v1/tasks/get-tasks-by-taskid-subagents-by-toolcallid-events api-reference/openapi.v1.json GET /tasks/{taskID}/subagents/{toolCallID}/events The text a subagent produced and the tools it called, in stream order, named by the tool call in the parent transcript that spawned it. Read-only and capped; `truncated` says the cap was hit. # Get the tree containing a task Source: https://docs.runaether.dev/api-reference/v1/tasks/get-tasks-by-taskid-tree api-reference/openapi.v1.json GET /tasks/{taskID}/tree # Search conversations Source: https://docs.runaether.dev/api-reference/v1/tasks/get-tasks-search api-reference/openapi.v1.json GET /tasks/search # Create a task Source: https://docs.runaether.dev/api-reference/v1/tasks/post-tasks api-reference/openapi.v1.json POST /tasks # Archive a task Source: https://docs.runaether.dev/api-reference/v1/tasks/post-tasks-by-taskid-archive api-reference/openapi.v1.json POST /tasks/{taskID}/archive # Pin a task Source: https://docs.runaether.dev/api-reference/v1/tasks/post-tasks-by-taskid-pin api-reference/openapi.v1.json POST /tasks/{taskID}/pin # Remove a queued message from a task Source: https://docs.runaether.dev/api-reference/v1/tasks/post-tasks-by-taskid-remove-from-queue api-reference/openapi.v1.json POST /tasks/{taskID}/remove-from-queue # Respond to a task Source: https://docs.runaether.dev/api-reference/v1/tasks/post-tasks-by-taskid-respond api-reference/openapi.v1.json POST /tasks/{taskID}/respond # Stop a running task Source: https://docs.runaether.dev/api-reference/v1/tasks/post-tasks-by-taskid-stop api-reference/openapi.v1.json POST /tasks/{taskID}/stop # Create a task from an imported local agent session Source: https://docs.runaether.dev/api-reference/v1/tasks/post-tasks-teleport api-reference/openapi.v1.json POST /tasks/teleport # Replace task settings Source: https://docs.runaether.dev/api-reference/v1/tasks/put-tasks-by-taskid api-reference/openapi.v1.json PUT /tasks/{taskID} # Get usage summary Source: https://docs.runaether.dev/api-reference/v1/usage/get-usage-summary api-reference/openapi.v1.json GET /usage/summary # Delete a workspace Source: https://docs.runaether.dev/api-reference/v1/workspaces/delete-workspaces-by-workspaceid api-reference/openapi.v1.json DELETE /workspaces/{workspaceID} # List workspaces for a project Source: https://docs.runaether.dev/api-reference/v1/workspaces/get-projects-by-projectid-workspaces api-reference/openapi.v1.json GET /projects/{projectID}/workspaces # List workspaces across all projects Source: https://docs.runaether.dev/api-reference/v1/workspaces/get-workspaces api-reference/openapi.v1.json GET /workspaces # Get a workspace Source: https://docs.runaether.dev/api-reference/v1/workspaces/get-workspaces-by-workspaceid api-reference/openapi.v1.json GET /workspaces/{workspaceID} # Update workspace settings Source: https://docs.runaether.dev/api-reference/v1/workspaces/patch-workspaces-by-workspaceid api-reference/openapi.v1.json PATCH /workspaces/{workspaceID} # Connect to a workspace Source: https://docs.runaether.dev/api-reference/v1/workspaces/post-workspaces-by-workspaceid-connect api-reference/openapi.v1.json POST /workspaces/{workspaceID}/connect # Create an authenticated preview Source: https://docs.runaether.dev/api-reference/v1/workspaces/post-workspaces-by-workspaceid-preview-auth api-reference/openapi.v1.json POST /workspaces/{workspaceID}/preview-auth Runs the repository-owned preview auth hook inside the workspace. URL results can be opened directly; local_storage results require explicit client-side application. # Stop a workspace Source: https://docs.runaether.dev/api-reference/v1/workspaces/post-workspaces-by-workspaceid-stop api-reference/openapi.v1.json POST /workspaces/{workspaceID}/stop # Agent Channel Source: https://docs.runaether.dev/api-reference/websocket/agent Subscribe to a task's live event stream over the workspace WebSocket The `agent` channel is a one-way live feed of a task's events. The client only **subscribes** and **unsubscribes**; the server streams `task_event` messages as the agent works. See the [WebSocket overview](/api-reference/websocket/overview) for connecting. You do not prompt the agent, answer its questions, or stop it over the WebSocket. Those are REST calls — see [Driving a task](#driving-a-task). The agent channel is read-only event streaming. ## Client to Server ### `subscribe` Attach this connection to a task's live event stream. You then receive every `task_event` for that task until you unsubscribe or disconnect. ```json theme={"dark"} { "channel": "agent", "type": "subscribe", "taskId": "task-abc-123" } ``` ### `unsubscribe` Detach from a task's stream. ```json theme={"dark"} { "channel": "agent", "type": "unsubscribe", "taskId": "task-abc-123" } ``` These are the only two client messages on this channel. ## Server to Client: `task_event` Every server message on the agent channel has `type: "task_event"`. A `kind` field discriminates what happened. All events carry `taskId` and an optional `createdAt` (ISO 8601). Every turn-owned event carries a required, non-zero UUID `conversationTurnId`; `conversation.truncated` and `slash_commands.updated` are task-level events and carry none. Message-scoped events also carry `messageId`; most events carry a provider `turnId`. ### Tool events `kind` is `tool_call.started`, `tool_call.completed`, or `tool_call.failed`. These carry a `toolCallId` and a `payload` describing the tool call. ```json theme={"dark"} { "channel": "agent", "type": "task_event", "kind": "tool_call.completed", "taskId": "task-abc-123", "conversationTurnId": "650e8400-e29b-41d4-a716-446655440000", "toolCallId": "tc-456", "turnId": "turn-1", "createdAt": "2026-02-20T10:30:00Z", "payload": { "name": "write_file", "input": { "path": "/src/routes/users.ts" }, "display": { "label": "Write users.ts", "primary": "/src/routes/users.ts" }, "status": "output-available", "itemType": "file_change", "result": "File written" } } ``` The `payload`: | Field | Type | Description | | ---------- | ------ | ------------------------------------------------------------ | | `name` | string | Tool name | | `input` | object | Tool arguments | | `display` | object | UI labels: `label`, optional `primary`, optional `secondary` | | `status` | enum | Tool status (below) | | `itemType` | enum | Optional timeline classification (below) | | `result` | string | Optional result text | | `error` | string | Optional error text | `status` is one of `input-streaming`, `input-available`, `output-available`, `output-error`, `output-denied`, `approval-requested`, `approval-responded`. `itemType` classifies the timeline entry for rendering. Possible values: `user_message`, `assistant_message`, `reasoning`, `plan`, `command_execution`, `file_change`, `file_read`, `mcp_tool_call`, `dynamic_tool_call`, `collab_agent_tool_call`, `web_search`, `web_fetch`, `image_view`, `task_tracking`, `subagent_invocation`, `review_entered`, `review_exited`, `context_compaction`, `error`, `unknown`. ### Message and stream events `assistant_message` and `thinking` arrive as token deltas followed by a completion. These carry the durable `conversationTurnId`, plus `messageId` and `turnId`. | `kind` | Payload | Meaning | | ----------------------------- | ---------------------- | ------------------------------------------ | | `assistant_message.delta` | `{ "delta": "..." }` | Incremental assistant text | | `thinking.delta` | `{ "delta": "..." }` | Incremental reasoning text | | `assistant_message.completed` | `{ "content": "..." }` | Full assistant message | | `thinking.completed` | `{ "content": "..." }` | Full reasoning block | | `stream.complete` | *(none)* | The model stream for this message finished | ```json assistant_message.delta theme={"dark"} { "channel": "agent", "type": "task_event", "kind": "assistant_message.delta", "taskId": "task-abc-123", "conversationTurnId": "650e8400-e29b-41d4-a716-446655440000", "messageId": "msg-9", "turnId": "turn-1", "payload": { "delta": "I'll create the route" } } ``` ### Turn lifecycle events A turn is one agent step. These carry the required durable `conversationTurnId` and a provider `turnId`. | `kind` | Payload | Meaning | | --------------------- | --------------------------- | -------------------------------------- | | `turn.completed` | `{ "status": "completed" }` | The turn finished | | `turn.awaiting_input` | see below | The agent needs your input to continue | | `turn.failed` | `{ "errorMessage": "..." }` | The turn failed | `turn.awaiting_input` additionally carries `pendingInputId` and `toolCallId`, and its payload names the tool requesting input: ```json theme={"dark"} { "channel": "agent", "type": "task_event", "kind": "turn.awaiting_input", "taskId": "task-abc-123", "conversationTurnId": "650e8400-e29b-41d4-a716-446655440000", "turnId": "turn-1", "pendingInputId": "pending-7", "toolCallId": "tc-456", "payload": { "toolName": "ask_user", "input": { "question": "Use PostgreSQL or SQLite?" } } } ``` Respond to this over REST (see below). ### Other events | `kind` | Payload | Meaning | | ------------------------ | ------------------------------ | ------------------------------------------------------------- | | `conversation.truncated` | `{ "anchorMessageId": "..." }` | History was rolled back to a checkpoint (carries `messageId`) | | `slash_commands.updated` | `{ "slashCommands": [...] }` | The set of available slash commands changed | Each slash command has `name`, `description`, and an optional `input` hint. ## Driving a Task The agent channel is receive-only. To act on a task, use the REST API: | Action | Endpoint | | ----------------------------------------- | ------------------------------------------------------------------------------------- | | Start a task | [`POST /v1/tasks`](/api-reference/v1/tasks/post-tasks) | | Answer a question (`turn.awaiting_input`) | [`POST /v1/tasks/{id}/respond`](/api-reference/v1/tasks/post-tasks-by-taskid-respond) | | Stop a task | [`POST /v1/tasks/{id}/stop`](/api-reference/v1/tasks/post-tasks-by-taskid-stop) | There is no public SSE or task-stream endpoint. Live task events reach clients **only** over this WebSocket. For prior conversation history, fetch [turn summaries](/api-reference/v1/tasks/get-tasks-by-taskid-conversation-turns) and per-turn activity over REST — history is not replayed on the socket. # Files Channel Source: https://docs.runaether.dev/api-reference/websocket/files File operations, fuzzy path search, and real-time change notifications The `files` channel reads and writes the workspace file system and pushes change notifications as files move underneath you. Operations follow a request/response pattern keyed by `requestId`; watcher events are server pushes with no `requestId`. See the [WebSocket overview](/api-reference/websocket/overview) for connecting. Files up to 100 MB can be read or written. Binary content is base64-encoded; text is UTF-8. ## Operations Each request carries a `requestId`. On success the server replies with the same `type` and `requestId`; on failure it replies with `type: "error"` (see [Errors](#errors)). ### `read` ```json Request theme={"dark"} { "channel": "files", "type": "read", "requestId": "req-1", "path": "/src/index.ts" } ``` ```json Response theme={"dark"} { "channel": "files", "type": "read", "requestId": "req-1", "success": true, "path": "/src/index.ts", "content": "import express from 'express';\n...", "encoding": "utf8", "size": 1420, "modified": "2026-02-20T10:30:00Z", "isBinary": false } ``` | Field | Type | Description | | ---------- | ---------------------- | ----------------------------------- | | `content` | string | File content (UTF-8 text or base64) | | `encoding` | `"utf8"` \| `"base64"` | How `content` is encoded | | `size` | integer | Size in bytes | | `modified` | string | ISO 8601 last-modified timestamp | | `isBinary` | boolean | Whether the file is binary | ### `write` Writes a file, creating parent directories as needed. ```json theme={"dark"} { "channel": "files", "type": "write", "requestId": "req-2", "path": "/src/config.ts", "content": "export const PORT = 3000;\n", "encoding": "utf8" } ``` `encoding` is required and must be `"utf8"` or `"base64"`. The response carries `success`, `path`, `size`, and `modified`. ### `list` Lists a directory. The response field is `entries` (not `data`). ```json Request theme={"dark"} { "channel": "files", "type": "list", "requestId": "req-3", "path": "/src" } ``` ```json Response theme={"dark"} { "channel": "files", "type": "list", "requestId": "req-3", "success": true, "path": "/src", "entries": [ { "name": "index.ts", "type": "file", "size": 1420, "modified": "2026-02-20T10:30:00Z" }, { "name": "utils", "type": "directory", "size": 0, "modified": "2026-02-19T08:15:00Z" } ] } ``` Each entry has `name`, `type` (`"file"` or `"directory"`), `size`, and `modified`. ### `stat` Metadata for a single path. The response is flat (no `data` wrapper) and uses `fileType`. ```json Request theme={"dark"} { "channel": "files", "type": "stat", "requestId": "req-4", "path": "/src/index.ts" } ``` ```json Response theme={"dark"} { "channel": "files", "type": "stat", "requestId": "req-4", "success": true, "path": "/src/index.ts", "fileType": "file", "size": 1420, "modified": "2026-02-20T10:30:00Z" } ``` ### `find-paths` Fuzzy path search across the workspace. Walks the tree, respects the repo's ignore files, and applies the same ignore list as `list`. ```json Request theme={"dark"} { "channel": "files", "type": "find-paths", "requestId": "req-5", "query": "userroute", "limit": 50 } ``` ```json Response theme={"dark"} { "channel": "files", "type": "find-paths", "requestId": "req-5", "success": true, "entries": [ { "path": "/src/routes/users.ts", "type": "file" } ] } ``` | Field | Type | Description | | ------- | ------- | ------------------------------- | | `query` | string | Fuzzy query | | `limit` | integer | Optional max results, up to 200 | Each match has `path` and `type` (`"file"` or `"directory"`). ### `mkdir`, `delete`, `rename` ```json mkdir theme={"dark"} { "channel": "files", "type": "mkdir", "requestId": "req-6", "path": "/src/components" } ``` ```json delete theme={"dark"} { "channel": "files", "type": "delete", "requestId": "req-7", "path": "/src/old.ts" } ``` ```json rename theme={"dark"} { "channel": "files", "type": "rename", "requestId": "req-8", "oldPath": "/src/utils.ts", "newPath": "/src/helpers.ts" } ``` `mkdir` and `delete` take `path`; `rename` takes `oldPath` and `newPath`. Each replies with `success` and the relevant path field(s). ## Errors When an operation fails, the response `type` is `"error"` (not the request type) and `success` is `false`. It carries a human-readable `error`, a machine-readable `code`, and the offending `path` when applicable. ```json theme={"dark"} { "channel": "files", "type": "error", "requestId": "req-1", "success": false, "error": "File not found: /src/missing.ts", "code": "NOT_FOUND", "path": "/src/missing.ts" } ``` `code` is one of: | Code | Meaning | | ------------------- | ------------------------------------------ | | `NOT_FOUND` | Path does not exist | | `NOT_INITIALIZED` | File operations not ready yet | | `PERMISSION_DENIED` | Access denied | | `IS_DIRECTORY` | Expected a file, found a directory | | `IS_FILE` | Expected a directory, found a file | | `FILE_TOO_LARGE` | Exceeds the 100 MB limit | | `BINARY_FILE` | Operation invalid for a binary file | | `INVALID_PATH` | Path is malformed or escapes the workspace | | `PATH_EXISTS` | Target already exists | | `INTERNAL_ERROR` | Unexpected failure | ## Watcher Events The server pushes file-system changes in real time. These have no `requestId`. ### `change` ```json theme={"dark"} { "channel": "files", "type": "change", "action": "modify", "path": "/src/index.ts", "isDirectory": false } ``` | Field | Type | Description | | ------------- | -------------------------------------- | -------------------------------- | | `action` | `"create"` \| `"modify"` \| `"delete"` | What happened | | `path` | string | Affected path | | `isDirectory` | boolean | Whether the entry is a directory | ### `refresh` A signal to re-fetch the file tree, pushed after the agent or a [git restore/revert](/api-reference/websocket/git) rewrites files, and when the watcher overflows. ```json theme={"dark"} { "channel": "files", "type": "refresh" } ``` ## Ignored Paths The watcher and `list` skip these directory names: `node_modules`, `.git`, `.venv`, `.next`, `.cache`, `dist`, `build`, `.turbo`, `__pycache__`, `.pytest_cache`, `coverage`, `.nyc_output`. Listings also hide `.DS_Store` and `lost+found`. # Git Channel Source: https://docs.runaether.dev/api-reference/websocket/git Workspace diff, checkpoints, and restoring or reverting agent changes The `git` channel powers the workspace diff and checkpoint UI: it returns structured diffs, lists the checkpoints the agent creates as it works, and rolls files (and the conversation) back to a checkpoint or reverts a single file. Both the web app and the `aether` CLI use it. See the [WebSocket overview](/api-reference/websocket/overview) for connecting. Requests carry a `requestId`; responses echo it. Responses are discriminated by `success`, with an `error` string on failure. ## `diff` Get a structured diff of the workspace. ```json Request theme={"dark"} { "channel": "git", "type": "diff", "requestId": "req-1", "mode": "main" } ``` `mode` is optional: `"main"` (default) diffs against the base branch, `"lastCommit"` diffs against the last commit. Untracked files appear as added. ```json Response theme={"dark"} { "channel": "git", "type": "diff", "requestId": "req-1", "success": true, "diff": { "baseRef": "main", "files": [ { "oldPath": "src/index.ts", "newPath": "src/index.ts", "displayPath": "src/index.ts", "status": "modified", "kind": "text", "additions": 1, "deletions": 0, "hunks": [ { "header": "@@ -1 +1,2 @@", "oldStart": 1, "oldCount": 1, "newStart": 1, "newCount": 2, "lines": [ { "kind": "context", "text": "import express", "oldLine": 1, "newLine": 1 }, { "kind": "add", "text": "import cors", "oldLine": null, "newLine": 2 } ] } ] } ] } } ``` Each file has `status` (`added`, `modified`, `deleted`, `renamed`) and `kind` (`text`, `binary`, `too_large`). Text and binary results have `additions`/`deletions` and `hunks`; a binary type change can include text hunks. Each line has `kind` (`add`, `del`, `context`), `text`, and `oldLine`/`newLine`, with `null` on a side where the line does not exist. Untracked text larger than 262,144 bytes stays in the change list as `too_large`. It has a `maxBytes` limit and no counts or hunks. Other files remain available, and the whole-file revert operation still works. Genuine Git and file-read errors fail the request. ```json Oversized file theme={"dark"} { "oldPath": "/dev/null", "newPath": "generated/big.txt", "displayPath": "generated/big.txt", "status": "added", "kind": "too_large", "maxBytes": 262144 } ``` ## `snapshots` List the agent's checkpoints — points in the conversation you can restore to. ```json Request theme={"dark"} { "channel": "git", "type": "snapshots", "requestId": "req-2", "limit": 50 } ``` `limit` is optional, up to 200. ```json Response theme={"dark"} { "channel": "git", "type": "snapshots", "requestId": "req-2", "snapshots": [ { "messageId": "msg-9", "hash": "a1b2c3d", "timestamp": "2026-02-20T10:30:00Z" } ] } ``` ## `restore` Roll the workspace files **and** the conversation back to a checkpoint, identified by its `messageId`. History after that point is truncated. ```json Request theme={"dark"} { "channel": "git", "type": "restore", "requestId": "req-3", "messageId": "msg-9" } ``` ```json Response theme={"dark"} { "channel": "git", "type": "restore", "requestId": "req-3", "success": true } ``` Restore is destructive: it discards file changes and conversation history made after the checkpoint. After a restore the server also pushes a [`files` `refresh`](/api-reference/websocket/files#refresh) and a [`conversation.truncated`](/api-reference/websocket/agent#other-events) agent event. ## `revertFile` Revert a single file to its base state. The required fields depend on `fileStatus`. ```json Modified / added / deleted theme={"dark"} { "channel": "git", "type": "revertFile", "requestId": "req-4", "filePath": "src/index.ts", "fileStatus": "modified" } ``` ```json Renamed theme={"dark"} { "channel": "git", "type": "revertFile", "requestId": "req-5", "filePath": "src/new-name.ts", "fileStatus": "renamed", "oldPath": "src/old-name.ts" } ``` | Field | Type | Description | | ------------ | ----------------------------------------------- | --------------------------------------- | | `filePath` | string | File to revert | | `fileStatus` | `added` \| `modified` \| `deleted` \| `renamed` | Current status of the file | | `oldPath` | string | Required when `fileStatus` is `renamed` | | `ref` | string | Optional git ref to revert against | The response is `{ requestId, success }`, and the server pushes a `files` `refresh` afterward. ## `showFile` Read a file's contents at a specific git ref. ```json Request theme={"dark"} { "channel": "git", "type": "showFile", "requestId": "req-6", "filePath": "src/index.ts", "ref": "HEAD" } ``` ```json Response theme={"dark"} { "channel": "git", "type": "showFile", "requestId": "req-6", "success": true, "content": "import express ..." } ``` ## `checkpoint` (server push) When the agent creates a checkpoint, the server pushes a notification with its `messageId`. There is no request for this. ```json theme={"dark"} { "channel": "git", "type": "checkpoint", "messageId": "msg-10" } ``` # WebSocket Overview Source: https://docs.runaether.dev/api-reference/websocket/overview Connect to a running workspace over a single multiplexed WebSocket A running workspace exposes one realtime connection: a multiplexed WebSocket that carries terminal I/O, file operations, live agent events, port changes, and git diffs over a single socket. The web app and the `aether` CLI both speak this same protocol. ## Endpoint ``` GET /workspaces/{id}/ws ``` `{id}` is the workspace ID. This endpoint sits on the non-versioned router — it is **not** under `/v1` like the rest of the public API. The API authenticates the request, verifies you own the workspace, requires the workspace to be in the `Ready` display state, then reverse-proxies raw text frames to the workspace VM. Every other documented surface is REST under `/v1`; this is the only WebSocket. ## Connecting Open the socket through the connect handshake rather than guessing the URL. Call [`POST /v1/workspaces/{id}/connect`](/api-reference/v1/workspaces/post-workspaces-by-workspaceid-connect) first — it returns a state-discriminated result. **Connect never boots the VM unless you ask it to.** Omitting `start` attaches only: a workspace that is not already usable answers `409` instead of being started, so merely viewing or polling a workspace can never bring one up (or undo a suspend). Booting is an explicit act — `?start=true&task_id=...`, where `task_id` attributes the runtime to a task. Call `POST /v1/workspaces/{id}/connect`. Add `start=true&task_id=...` only if you intend to boot a stopped workspace — send it **once**, on the request that expresses that intent. While the VM boots, the response is `state: "connecting"` with `retry_after_ms` (1000). Wait that long and call again **without `start`**. The first accepted response has already created whatever start it was going to; repeating `start=true` can authorize a second one. A `transitional` 409 is different: it was refused, so nothing was started and an unspent `start=true` may be re-sent on the retry. Once the response is `state: "running"`, it includes `transport.websocket_path` (`/workspaces/{id}/ws`) and a `preview_token`. Connect to `websocket_path`, passing your token (see below). ### When connect refuses A `409` means the workspace is not attachable. The body is discriminated by `kind`, and the kind IS the answer — there is no flag to interpret: | `kind` | Meaning | What to do | | ----------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | `transitional` | A lifecycle operation is still settling; the same request answers differently once it lands. Carries `retry_after_ms`. | Wait `retry_after_ms` and call again. Bound your retries. | | `startable` | A connect with `start=true` would actually start this workspace. | Stop polling. This is the only refusal on which you may offer the user a start. | | `not_connectable` | Neither retrying this connect nor sending `start=true` can change it. Carries `display_state`. | Stop polling and surface `display_state`. Do not offer a start — it would be refused identically. | A workspace that is down can answer either arm. `startable` means this endpoint can bring it back — including resuming a suspended or stopped workspace whose run is still active; `not_connectable` means it cannot, because there is no machine left to start or the workspace failed. Either way that workspace is usually still recoverable by sending the agent a message, which goes through the task path rather than this endpoint. ```json theme={"dark"} { "kind": "not_connectable", "error": "Workspace is not running", "workspace_id": "5d1f...", "display_state": "suspended" } ``` The `running` response shape: ```json theme={"dark"} { "workspace_id": "5d1f...", "state": "running", "display_state": "ready", "transport": { "websocket_path": "/workspaces/5d1f.../ws", "preview_token": "ab12cd34ef56ab12cd34ef56ab12cd34" } } ``` The `preview_token` is a 32-character lowercase alphanumeric string used to build [preview URLs](/api-reference/websocket/ports#preview-urls). ## Authentication Pass an Aether [API key](/api-reference/authentication) (`aether_…`) or a session JWT one of three ways: | Method | How | | -------------------- | -------------------------------------------- | | Authorization header | `Authorization: Bearer YOUR_TOKEN` | | Subprotocol | `Sec-WebSocket-Protocol: bearer, YOUR_TOKEN` | | Query parameter | append `?token=YOUR_TOKEN` to the URL | Browsers can't set request headers on a WebSocket, so the web client uses the query parameter. When a subprotocol is sent, the server echoes `Sec-WebSocket-Protocol: bearer`. ```bash theme={"dark"} # Subprotocol wscat -c "wss://api.runaether.dev/workspaces/ws-123/ws" \ -s "bearer, YOUR_TOKEN" # Query parameter wscat -c "wss://api.runaether.dev/workspaces/ws-123/ws?token=YOUR_TOKEN" ``` ## Message Format Every message is JSON with a `channel` field identifying the target subsystem and a `type` field identifying the message within that channel. Six channels are multiplexed over the connection: | Channel | Direction | Purpose | | ----------------------------------------------- | ----------- | -------------------------------------------- | | [`terminal`](/api-reference/websocket/terminal) | both | PTY terminal sessions | | [`files`](/api-reference/websocket/files) | both | File operations and change notifications | | [`agent`](/api-reference/websocket/agent) | both | Subscribe to a task's live event stream | | [`ports`](/api-reference/websocket/ports) | both | Open-port snapshot/changes and process kill | | [`git`](/api-reference/websocket/git) | both | Workspace diff, checkpoints, restore, revert | | `error` | server only | Out-of-band workspace errors | Client messages are validated against strict schemas — extra or misnamed fields are rejected. The canonical shapes live in `packages/workspace-protocol/src/messages.ts`. ```json theme={"dark"} { "channel": "terminal", "type": "input", "sessionId": "cli-123", "data": "ls -la\n" } ``` ## The `error` Channel The server pushes errors on a dedicated channel that you never send to. It is emitted after the socket opens when the VM fails to initialize or an inbound message fails to parse. ```json theme={"dark"} { "channel": "error", "type": "error", "error": "Invalid workspace message: ..." } ``` If the API proxy cannot establish its connection to the VM, the browser WebSocket is not upgraded. The handshake returns HTTP `502` instead. The web client treats this as a failed connection attempt and re-runs the connect sequence so `/connect` can re-resolve the current workspace transport; other clients receive the failed handshake and can decide whether to retry. ## Connection Keepalive | Parameter | Value | | ---------------- | ----------- | | Ping interval | 15 seconds | | Pong timeout | 120 seconds | | Write timeout | 30 seconds | | Max message size | 1 MB | The API sends WebSocket ping frames every 15 seconds. If no pong arrives within 120 seconds, it closes the connection. ## Reconnection On reconnect, what comes back depends on the channel: * **Terminal** sessions keep running on the VM while you're disconnected; reattach by sending `create` with the same `sessionId`. * **Ports** sends a fresh `snapshot` of currently-open ports on connect. * **Agent** resumes only live `task_event` messages for tasks you re-`subscribe` to. Conversation history is **not** replayed over the socket — fetch [turn summaries](/api-reference/v1/tasks/get-tasks-by-taskid-conversation-turns) and per-turn activity over REST. * **Files** does not replay change events that occurred while disconnected; re-`list` or `read` to get current state. Prompting an agent, responding to its questions, and stopping a task all happen over REST, not the WebSocket. See the [agent channel](/api-reference/websocket/agent). # Ports Channel Source: https://docs.runaether.dev/api-reference/websocket/ports Open-port snapshots, change notifications, process kill, and preview URLs The `ports` channel reports which network ports are listening inside the workspace and lets you kill the process behind one. Port detection comes from a monitor sidecar in the VM, so you never poll. See the [WebSocket overview](/api-reference/websocket/overview) for connecting. ## Server to Client ### `snapshot` Sent on connect (and after a port refresh) with every currently-open port. Carries only port numbers. ```json theme={"dark"} { "channel": "ports", "type": "snapshot", "ports": [3000, 5173] } ``` ### `change` A single port opened or closed. There is one message type with an `action` discriminator — no separate `open`/`close` types, and no PID or process name. ```json theme={"dark"} { "channel": "ports", "type": "change", "action": "open", "port": 3000 } ``` | Field | Type | Description | | -------- | --------------------- | --------------------------------------------- | | `action` | `"open"` \| `"close"` | Whether the port started or stopped listening | | `port` | integer | Port number (1–65535) | ## Killing a Process ### `kill` (client to server) Terminate the process on a port. The request requires a `requestId` so you can match the response. ```json theme={"dark"} { "channel": "ports", "type": "kill", "requestId": "req-1", "port": 3000 } ``` ### `killResponse` (server to client) The response type is `killResponse` and echoes the `requestId`. ```json Success theme={"dark"} { "channel": "ports", "type": "killResponse", "requestId": "req-1", "success": true, "port": 3000 } ``` ```json Failure theme={"dark"} { "channel": "ports", "type": "killResponse", "requestId": "req-1", "success": false, "port": 3000, "error": "no process found on port 3000" } ``` ## Preview URLs Each open port is reachable through a public preview URL built from the workspace's project ID prefix and its `preview_token` (returned by the [connect handshake](/api-reference/websocket/overview#connecting)): ``` https://{port}-{projectIdPrefix}-{previewToken}.{previewDomain} ``` * `port` — the listening port * `projectIdPrefix` — the first 8 hex characters of the project ID * `previewToken` — the 32-character lowercase alphanumeric `preview_token` * `previewDomain` — the configured preview domain The token segment is required — preview URLs without a valid token are rejected. ``` https://3000-abc12345-ab12cd34ef56ab12cd34ef56ab12cd34.preview.runaether.dev ``` The gateway resolves the workspace by project-ID prefix, validates the token against that workspace, and proxies both HTTP and WebSocket (HMR) traffic into the VM. ## Example Flow ```mermaid theme={"dark"} sequenceDiagram participant Client participant Server as Aether Note right of Server: On connect Server->>Client: snapshot (ports: []) Note left of Client: Starts dev server on 3000 Server->>Client: change (action: "open", port: 3000) Note left of Client: Preview URL live Client->>Server: kill (requestId: "req-1", port: 3000) Server->>Client: killResponse (requestId: "req-1", success: true, port: 3000) Server->>Client: change (action: "close", port: 3000) ``` # Terminal Channel Source: https://docs.runaether.dev/api-reference/websocket/terminal Multiplexed PTY terminal sessions over the workspace WebSocket The `terminal` channel runs PTY sessions inside the workspace. Multiple sessions multiplex over one connection, keyed by `sessionId`. See the [WebSocket overview](/api-reference/websocket/overview) for how to connect and authenticate. A session spawns a shell resolved from `$SHELL`, falling back to `bash`. The PTY uses `TERM=xterm-256color`, starts at 80×24, and runs in the project working directory. ## Client to Server ### `create` Open a new PTY session. Carries only the session ID — sizing is set afterward with `resize`. Reusing an existing `sessionId` is an error. ```json theme={"dark"} { "channel": "terminal", "type": "create", "sessionId": "cli-123" } ``` | Field | Type | Description | | ----------- | ------ | ---------------------------------- | | `sessionId` | string | Unique identifier for this session | ### `input` Send keystrokes to a session. ```json theme={"dark"} { "channel": "terminal", "type": "input", "sessionId": "cli-123", "data": "ls -la\n" } ``` | Field | Type | Description | | ----------- | ------ | ------------------------------------------------ | | `sessionId` | string | Target session | | `data` | string | Raw input (include `\n` for Enter, `\t` for Tab) | ### `resize` Resize a session. Send when the terminal UI changes size. Both dimensions must be positive integers. ```json theme={"dark"} { "channel": "terminal", "type": "resize", "sessionId": "cli-123", "cols": 200, "rows": 50 } ``` | Field | Type | Description | | ----------- | ------- | -------------------- | | `sessionId` | string | Target session | | `cols` | integer | New width in columns | | `rows` | integer | New height in rows | ### `close` Close a session and terminate its shell. ```json theme={"dark"} { "channel": "terminal", "type": "close", "sessionId": "cli-123" } ``` | Field | Type | Description | | ----------- | ------ | ---------------- | | `sessionId` | string | Session to close | ## Server to Client ### `output` PTY output, including ANSI escape codes. ```json theme={"dark"} { "channel": "terminal", "type": "output", "sessionId": "cli-123", "data": "total 48\ndrwxr-xr-x 12 user staff 384 Feb 20 10:30 .\n" } ``` | Field | Type | Description | | ----------- | ------ | ------------------- | | `sessionId` | string | Source session | | `data` | string | Raw terminal output | ### `close` The session's shell process exited. The message carries only the session ID — there is no exit code on the wire. ```json theme={"dark"} { "channel": "terminal", "type": "close", "sessionId": "cli-123" } ``` | Field | Type | Description | | ----------- | ------ | ------------------ | | `sessionId` | string | Session that ended | ## Example Flow ```mermaid theme={"dark"} sequenceDiagram participant Client participant Server Client->>Server: create (sessionId: "cli-123") Note right of Server: Spawn PTY shell Server->>Client: output (shell prompt) Client->>Server: input ("ls -la\n") Server->>Client: output (directory listing) Client->>Server: resize (cols: 200, rows: 50) Client->>Server: input ("exit\n") Server->>Client: close (sessionId: "cli-123") ``` # aether auth Source: https://docs.runaether.dev/cli/auth Log in, log out, and manage CLI authentication # aether auth Manage CLI authentication. Logging in mints a platform API key named `cli-` and stores it in `~/.aether/config.json`. The key does not expire and nothing about it rotates — it is valid until revoked. You can also authenticate non-interactively with the `AETHER_TOKEN` environment variable. Revoking `cli-` under **Settings → API keys** logs that machine out. This is the same key type `aether token` manages, so a CLI login shows up in that list alongside your other keys. ## Usage ```bash theme={"dark"} aether auth [flags] ``` ## Subcommands | Subcommand | Description | | ---------- | ------------------------------------------------------ | | `login` | Authenticate with Aether | | `logout` | Revoke this machine's key and clear stored credentials | | `status` | Show current authentication state | | `token` | Print the stored credential to stdout | ## aether auth login Authenticate with the Aether API. The CLI opens the web login in your browser — sign in there with any method (Google, GitHub, Apple, or an emailed code) and the browser hands the session back. Without a TTY it prints the login URL and waits for the localhost callback. Every interactive path ends the same way: the CLI signs you in, exchanges that sign-in for a platform API key named `cli-`, and stores the key. The sign-in session itself is used for that one exchange and then discarded. Logging in again mints the replacement first and only then retires the key this installation already held, so an interrupted login never leaves the machine without a working credential. The key being retired is the exact one recorded at the last login — never one matched by name, since two machines can share a hostname and a name match would revoke the other machine's key. A consequence: if you delete `~/.aether/config.json` and log in again, the CLI no longer knows about the old key, so it stays valid until you revoke it under **Settings → API keys**. Retirement presents the old key rather than your new sign-in, so it works even when you log in as a different account on the same machine — the previous account's key is retired instead of being left live and forgotten. ```bash theme={"dark"} aether auth login [flags] ``` | Flag | Description | | --------- | --------------------------------------------------------- | | `--token` | Save an existing credential verbatim (for CI and scripts) | ```bash theme={"dark"} # Interactive — opens the web login in your browser aether auth login ``` ``` Logged in Config saved to: ~/.aether/config.json ``` ```bash theme={"dark"} # Save an existing credential (CI/scripts) aether auth login --token "aether_..." ``` `--token` stores the value exactly as given and mints nothing, so CI never grows a permanent key as a side effect of logging in. Pass either a platform API key or a JWT; an expired JWT surfaces later as a 401 telling you to log in again. If the machine is already logged in with a key the CLI minted, `--token` refuses rather than overwriting it: dropping the stored key id would leave that key valid with nothing able to revoke it. Run `aether auth logout` first. Browser login is supported in a non-TTY shell for coding agents: the command prints the URL before it waits, so the agent can give the URL to its human. The human completes the browser page while the command remains running. `--token` remains the explicit CI/script path. ## aether auth logout Revoke this machine's API key and clear the stored credentials. ```bash theme={"dark"} aether auth logout ``` ``` Logged out successfully. ``` Logout only clears the local credential once the key is confirmed gone. If the revoke cannot be confirmed — you are offline, or the API is down — the credential is **kept** and the command exits nonzero, so you can simply run logout again later. Discarding it would leave a key that is probably still live with nothing able to revoke it. To log out immediately regardless, revoke the key under **Settings → API keys** (find it by the prefix the command prints) and run logout again. A key that was already revoked in **Settings → API keys** is not an error; logout just clears the local config. If the config is damaged to the point that the credential cannot be read, logout still clears it and tells you which key id was named, so you can revoke it manually. A `--token` login owns no server-side key, so logout only clears the local config. ## aether auth status Show the current authentication state, the key in use, default project, and API URL. There is no expiry to report; the stored key is valid until revoked. `status` checks the stored credential or `AETHER_TOKEN` against the API, so an invalid or revoked key reads as logged out here rather than appearing to work until the next command fails. ```bash theme={"dark"} aether auth status ``` ``` Logged in API key: cli-my-laptop (aether_a1b2c...) Default project: a1b2c3d4 API URL: https://api.runaether.dev ``` When authenticated via the environment variable: ``` Authenticated via AETHER_TOKEN environment variable ``` When not authenticated: ``` Not logged in. Run 'aether auth login' to authenticate. ``` ## aether auth token Print the stored credential to stdout. Useful for piping into other tools. The credential does not expire, so this is safe to call from scripts at any time. ```bash theme={"dark"} aether auth token ``` ```bash theme={"dark"} # Use with curl curl -H "Authorization: Bearer $(aether auth token)" https://api.runaether.dev/v1/projects ``` ```bash theme={"dark"} # Set as an environment variable export AETHER_TOKEN=$(aether auth token) ``` # aether billing Source: https://docs.runaether.dev/cli/billing Manage billing, credits, checkout, and portal sessions # aether billing Manage billing state from the CLI. Credits are Aether's usage unit — they track how long your workspace VMs run; LLM tokens are captured but never billed. See [billing and credits](/concepts/billing-and-credits). ## Usage ```bash theme={"dark"} aether billing [flags] ``` ## Subcommands | Subcommand | Description | | --------------------------- | ------------------------------------------------------------------------------------------------- | | `summary` | Show plan, payment state, monthly credit grant, remaining or owed credits, and optional cycle end | | `catalog` | Show available tiers (price, monthly credits, warm slots) | | `balance` | Alias for `summary` | | `subscription` | Show subscription state | | `credit-packs` | List available credit packs | | `checkout` | Create a checkout session | | `portal` | Create a billing portal session | | `checkout-status ` | Show checkout session status | ## Examples ```bash theme={"dark"} aether billing summary aether billing balance --json aether billing credit-packs ``` The summary makes one subscription request. It does not list warm slots or itemize charges. ## Checkout Create a checkout session for either a subscription tier or a one-time credit pack — pass exactly one of `--tier` or `--credit-pack`. ```bash theme={"dark"} aether billing checkout --tier pro aether billing checkout --credit-pack credits_250 aether billing portal ``` | Flag | Description | | --------------- | --------------------------------------------- | | `--tier` | Subscription tier: `starter`, `pro`, or `max` | | `--credit-pack` | Credit pack ID (see below) | | `--open` | Open the checkout/portal URL in a browser | `--tier` and `--credit-pack` are mutually exclusive. Provide exactly one. ### Credit pack IDs | ID | Credits | | -------------- | ------- | | `credits_100` | 100 | | `credits_250` | 250 | | `credits_500` | 500 | | `credits_1000` | 1000 | Run `aether billing credit-packs` for the current list, prices, and checkout availability. Checkout and portal commands print the URL by default. Pass `--open` to launch it in your browser; if the URL cannot be opened, the command fails. # aether claude-code Source: https://docs.runaether.dev/cli/claude-code Connect Claude Code provider auth with a setup token Manage authentication for the Claude Code agent provider. Connecting runs `claude setup-token` locally and stores the resulting setup token so the Claude Code agent can run headless in Aether workspaces. See [connect agent providers](/guides/connect-agent-providers). `connect` requires the Claude Code CLI on your machine. If it is missing, install it with `npm install -g @anthropic-ai/claude-code`, then rerun the command. ## Usage ```bash theme={"dark"} aether claude-code [flags] ``` ## Subcommands | Subcommand | Description | | ------------ | -------------------------------------------- | | `connect` | Run `claude setup-token` and store the token | | `status` | Show Claude Code auth status | | `disconnect` | Remove Claude Code auth | ## aether claude-code connect Runs `claude setup-token`, captures the printed setup token, and uploads it. Follow the prompts from the Claude Code CLI to complete the flow. ```bash theme={"dark"} aether claude-code connect ``` ``` Claude Code setup token connected. ``` ## aether claude-code status Show whether a setup token is configured. ```bash theme={"dark"} aether claude-code status ``` ``` Claude Code: configured (setupToken) ``` When nothing is configured, the command prints `Claude Code: not configured` and points you to `connect`. ## aether claude-code disconnect Remove the stored Claude Code auth. ```bash theme={"dark"} aether claude-code disconnect [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | ## See also * [Connect agent providers](/guides/connect-agent-providers) * [Agents and models](/concepts/agents-and-models) # aether codex Source: https://docs.runaether.dev/cli/codex Connect Codex provider auth with a subscription or API key Manage authentication for the Codex agent provider. Connect a ChatGPT subscription with a device-login flow, or set an API key instead. Once connected, the Codex agent uses your account when it runs in workspaces. See [connect agent providers](/guides/connect-agent-providers). ## Usage ```bash theme={"dark"} aether codex [flags] ``` ## Subcommands | Subcommand | Description | | ------------------------- | ---------------------------------------------- | | `connect` (alias `login`) | Connect a Codex subscription with device login | | `api-key` | Set Codex API-key auth instead | | `status` | Show Codex auth status | | `logout` | Remove Codex auth | ## aether codex connect Start the device-authorization flow. The command prints a verification URL and a user code, opens the URL in your browser, and polls until you authorize (up to 10 minutes). If a subscription is already connected, it exits. If an API key is currently stored, connecting replaces it with subscription auth. ```bash theme={"dark"} aether codex connect ``` ``` Open this URL and enter the code: https://... Code: ABCD-1234 Waiting for Codex authorization... Codex subscription auth connected. Email: you@example.com Plan: plus ``` ## aether codex api-key Set API-key auth instead of a subscription. Omit `--api-key` to enter the key at a secure prompt. ```bash theme={"dark"} aether codex api-key ``` | Flag | Description | | ----------- | -------------------------------------------------- | | `--api-key` | Codex API key. Omit to enter it at a secure prompt | ## aether codex status Show the current Codex auth: type (`subscription` or `apiKey`), email, and plan when available. ```bash theme={"dark"} aether codex status ``` ``` Codex: configured (subscription) Email: you@example.com Plan: plus ``` When nothing is configured, the command prints `Codex: not configured` and points you to `connect` or `api-key`. ## aether codex logout Remove the stored Codex auth. ```bash theme={"dark"} aether codex logout [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | ## See also * [Connect agent providers](/guides/connect-agent-providers) * [Agents and models](/concepts/agents-and-models) # aether completion Source: https://docs.runaether.dev/cli/completion Generate shell completion scripts # aether completion Generate a shell completion script for tab completion of commands, subcommands, and flags. Exactly one shell argument is required. ## Usage ```bash theme={"dark"} aether completion [bash|zsh|fish|powershell] ``` ## Setup Load completions in the current session: ```bash theme={"dark"} source <(aether completion bash) ``` Install permanently: ```bash theme={"dark"} aether completion bash > /etc/bash_completion.d/aether ``` Load completions in the current session: ```bash theme={"dark"} source <(aether completion zsh) ``` Install permanently: ```bash theme={"dark"} aether completion zsh > "${fpath[1]}/_aether" ``` Load completions in the current session: ```bash theme={"dark"} aether completion fish | source ``` Install permanently: ```bash theme={"dark"} aether completion fish > ~/.config/fish/completions/aether.fish ``` Load completions in the current session: ```powershell theme={"dark"} aether completion powershell | Out-String | Invoke-Expression ``` Persist by adding to your PowerShell profile (`$PROFILE`): ```powershell theme={"dark"} aether completion powershell >> $PROFILE ``` # aether config Source: https://docs.runaether.dev/cli/config View and manage CLI configuration # aether config View and update CLI configuration. The CLI stores configuration in `~/.aether/config.json`. Only non-sensitive keys are readable and writable here — credentials are managed by [`aether auth`](/cli/auth). ## Usage ```bash theme={"dark"} aether config ``` ## Subcommands | Subcommand | Description | | ------------------- | ---------------------------------- | | `path` | Print the config file location | | `get ` | Print a non-sensitive config value | | `set ` | Set a non-sensitive config value | ## Config Keys | Key | Description | | ----------------- | -------------------------------------- | | `api_url` | Aether API base URL | | `default_project` | Default project for CLI commands | | `preview_domain` | Domain used for workspace preview URLs | ## aether config path Print the path to the configuration file. ```bash theme={"dark"} aether config path ``` ``` /Users/you/.aether/config.json ``` ## aether config get Print a config value. Unset keys print `(not set)`. ```bash theme={"dark"} aether config get api_url ``` ``` https://api.runaether.dev ``` ## aether config set Set a non-sensitive key. Unknown or sensitive keys are rejected. ```bash theme={"dark"} aether config set preview_domain preview.runaether.dev ``` ``` preview_domain updated. ``` # aether cursor Source: https://docs.runaether.dev/cli/cursor Connect Cursor provider auth by importing your local cursor-agent login Manage authentication for the Cursor agent provider. Connecting imports the OAuth tokens written by your local `cursor-agent login` and uploads them so the Cursor agent can run in Aether workspaces. See [connect agent providers](/guides/connect-agent-providers). Cursor is retired from new task selection; this command remains for existing Cursor tasks and connections, which keep working. Log in with `cursor-agent login` first. On macOS the tokens are read from the login keychain (`cursor-access-token` / `cursor-refresh-token`); on Linux from `${XDG_CONFIG_HOME:-~/.config}/cursor/auth.json`. Automatic import is supported only on macOS and Linux. ## Usage ```bash theme={"dark"} aether cursor [flags] ``` ## Subcommands | Subcommand | Description | | ------------ | ------------------------------------------------ | | `connect` | Import and upload your local Cursor OAuth tokens | | `status` | Show Cursor auth status | | `disconnect` | Remove Cursor auth | ## aether cursor connect Reads the OAuth tokens from your local Cursor installation and uploads them. ```bash theme={"dark"} aether cursor connect ``` ``` Cursor subscription connected (from macOS keychain (cursor-access-token, cursor-refresh-token)). ``` ## aether cursor status Show whether Cursor auth is configured. ```bash theme={"dark"} aether cursor status ``` ``` Cursor: configured (subscription) ``` When nothing is configured, the command prints `Cursor: not configured` and points you to `connect`. ## aether cursor disconnect Remove the stored Cursor auth. ```bash theme={"dark"} aether cursor disconnect [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | ## See also * [Connect agent providers](/guides/connect-agent-providers) * [Agents and models](/concepts/agents-and-models) # aether delegation Source: https://docs.runaether.dev/cli/delegation Configure or remove the hosted Aether integration for local Claude Code and Codex sessions For the full setup and native browser sign-in, run `aether setup` from your checkout. See [cloud delegation](/guides/cloud-delegation) for a first task and the supported client versions. `aether delegation` manages the integration's local files. It does not launch a coding client or run a local MCP server. Launch `claude` or `codex` normally after setup. ## Commands | Command | Purpose | | --------------------------- | --------------------------------------------------------------------------- | | `aether delegation install` | Write native HTTP MCP configuration, hooks, and the `aether-delegate` skill | | `aether delegation remove` | Remove that runtime's installed integration files and entries | | `aether delegation hook` | Process native lifecycle events; invoked by the installed hooks | ## Install Use this command when configuring one runtime or selecting an organization explicitly: ```bash theme={"dark"} aether delegation install --runtime claude-code --project ``` For Codex, use `--runtime codex-cli`. | Flag | Applies to | Description | | ----------- | --------------- | ----------------------------------------------------------------------------- | | `--runtime` | Install, remove | Required: `claude-code` or `codex-cli` | | `--root` | Install, remove | Installed checkout root; defaults to the current directory | | `--project` | Install | Full project UUID; otherwise uses the configured CLI project | | `--org` | Install | Organization UUID for a project outside the credential's default organization | Unlike commands that resolve a short project prefix or name, delegation installation requires a full project UUID. Run manual installation from the checkout root, or specify that root with `--root`. ```bash theme={"dark"} aether delegation install --runtime codex-cli \ --root /path/to/checkout \ --project \ --org ``` Install requires a supported native client in its default configuration directory. It writes configuration but does **not** complete OAuth login. Run `aether setup --project ` interactively from that checkout to finish native sign-in. Setup preserves an explicitly installed organization when reinstalling. Codex CLI 0.153.4 does not load checkout-only MCP configuration for a bare `codex mcp login aether_delegation`. Setup supplies the installed server as an invocation-only override, so it can authenticate without changing saved checkout trust. The normal project trust prompts still apply when you launch the client. ## Installed files | Runtime | MCP configuration | Hooks | Skill | | ----------- | -------------------- | ----------------------------- | ----------------------------------------- | | Claude Code | `.mcp.json` | `.claude/settings.local.json` | `.claude/skills/aether-delegate/SKILL.md` | | Codex CLI | `.codex/config.toml` | `.codex/hooks.json` | `.agents/skills/aether-delegate/SKILL.md` | Each runtime also gets `.aether/delegation-.json`, which records the installation identity. Task tracking and unread updates live on Aether. OAuth credentials stay in the native client's credential store, outside these project files. For Codex linked Git worktrees, `.codex/hooks.json` lives in the repository's main checkout, where Codex discovers it. The MCP configuration, skill, and installation identity stay in each checkout. Run setup in each worktree; the shared hooks select that checkout's binding from the native session's working directory. For repositories with separate Git metadata, Aether follows Codex's discovery rules: hooks stay local unless Codex recognizes a shared main checkout. Reinstalling updates Aether's owned entries and hook executable path while preserving the installation identity and unrelated configuration. Conflicting edits fail visibly. If installation is interrupted, rerun it to finish. ## Remove From the installed root: ```bash theme={"dark"} aether delegation remove --runtime claude-code # Or remove the Codex integration: aether delegation remove --runtime codex-cli ``` Use `--root /path/to/checkout` when running elsewhere. Removal does not stop cloud tasks or revoke native OAuth credentials. Stop any running task explicitly from its Aether task page or through your connected local agent before removing the integration if you want that work to end. Removing Codex delegation from one checkout preserves the shared hooks while another installed worktree still needs them. They are removed after the last checkout's integration is removed. Native MCP logout and `aether logout` manage separate logins: the former clears the client's saved MCP credentials, while the latter removes the CLI login used by hooks. Removing local credentials is not server-side grant revocation. # aether env Source: https://docs.runaether.dev/cli/env Manage project environment variables Manage environment variables for the current project. They are encrypted at rest and injected into workspaces when they start. See [environment variables](/guides/environment-variables) for the concept. ## Usage ```bash theme={"dark"} aether env [flags] ``` ## Subcommands | Subcommand | Description | | --------------- | ----------------------------------- | | `list` | List variable keys | | `get ` | Print a variable's value | | `set ` | Set a variable | | `delete ` | Delete a variable | | `import ` | Import variables from a `.env` file | Keys must match `[a-zA-Z_][a-zA-Z0-9_.]*`. ## aether env list List the variable keys for the current project. Values are not shown — only the key and when it was last updated. ```bash theme={"dark"} aether env list ``` ``` KEY UPDATED DATABASE_URL 2h ago REDIS_URL 2h ago API_SECRET 1d ago ``` ## aether env get Print the raw, decrypted value of a variable to stdout. The output is pipeable. ```bash theme={"dark"} aether env get DATABASE_URL ``` ``` postgres://user:pass@host:5432/db ``` ## aether env set Set a variable, creating it if it does not exist or updating it if it does. If you omit `--value`, the CLI prompts for the value without echoing it. ```bash theme={"dark"} aether env set [flags] ``` | Flag | Description | | --------- | ------------------------------------------- | | `--value` | The value to set. Omit for a secure prompt. | ```bash theme={"dark"} aether env set DATABASE_URL --value "postgres://user:pass@host:5432/db" ``` ``` DATABASE_URL saved. ``` Running workspaces must be restarted to pick up environment variable changes. ## aether env delete Delete a variable. ```bash theme={"dark"} aether env delete [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | ```bash theme={"dark"} aether env delete API_SECRET ``` ## aether env import Import variables from a `.env` file. Existing variables with the same key are overwritten. Pass `-` as the file to read from stdin. ```bash theme={"dark"} aether env import ``` ```bash theme={"dark"} aether env import .env.production ``` ``` Imported 5 environment variables. ``` ```bash theme={"dark"} # Read from stdin cat .env | aether env import - ``` The file follows standard `.env` conventions: ``` # Database DATABASE_URL=postgres://user:pass@host:5432/db REDIS_URL=redis://localhost:6379 # API API_SECRET=supersecret ``` # aether feedback Source: https://docs.runaether.dev/cli/feedback Submit product feedback from the CLI Send product feedback to the Aether team. The feedback is attached to a workspace (and optionally a specific task) so it carries context. ```bash theme={"dark"} aether feedback --message "The diff viewer is hard to read on small screens" ``` If you omit `--message`, the CLI prompts you for the feedback text interactively. If you omit `--workspace`, the CLI resolves your [default project](/cli/project) and uses its running workspace; pass `--workspace` to target a specific one. ## Flags The feedback message. If omitted, you are prompted for it. Workspace ID or short prefix. Defaults to the running workspace of your default project. Optional task ID or short prefix to associate with the feedback. # aether file Source: https://docs.runaether.dev/cli/file Read, write, and manage files in a running workspace Manage files in a running workspace. All file operations run over a WebSocket connection to the workspace. **Alias:** `files` ## Usage ```bash theme={"dark"} aether file [flags] ``` Every subcommand accepts the persistent `--workspace` flag to target a specific workspace. When omitted, the command uses the project's single running workspace. | Flag | Description | | ------------- | ---------------------------------------------------------- | | `--workspace` | Workspace ID (defaults to the project's running workspace) | ## Subcommands | Subcommand | Description | | -------------------- | -------------------------- | | `list [path]` | List files and directories | | `read ` | Read file contents | | `write ` | Write content to a file | | `mkdir ` | Create a directory | | `delete ` | Delete a file or directory | | `rename ` | Rename or move a file | | `stat ` | Show file metadata | ## aether file list List files and directories at the given path. Defaults to the workspace root. Directories are listed first, then files, alphabetically within each group. ```bash theme={"dark"} aether file list [path] ``` ```bash theme={"dark"} aether file list src/ ``` ``` TYPE NAME SIZE MODIFIED dir components — 2m ago file index.ts 1.2 KB 5m ago file config.ts 320 B 1h ago ``` ## aether file read Read the contents of a file and print it to stdout. ```bash theme={"dark"} aether file read [flags] ``` | Flag | Description | | ------------ | ------------------------------------------------------ | | `--encoding` | File encoding, `utf8` or `base64`. Defaults to `utf8`. | ```bash theme={"dark"} aether file read src/index.ts ``` ```bash theme={"dark"} # Read a binary file as base64 aether file read assets/logo.png --encoding base64 ``` ## aether file write Write content to a file, creating it if it does not exist or overwriting it if it does. Provide the content with `--content`, or pipe it on stdin. ```bash theme={"dark"} aether file write [flags] ``` | Flag | Description | | ------------ | --------------------------------------------------------- | | `--content` | File content. If omitted, content is read from stdin. | | `--encoding` | Content encoding, `utf8` or `base64`. Defaults to `utf8`. | ```bash theme={"dark"} aether file write src/config.ts --content 'export const PORT = 3000;' ``` ```bash theme={"dark"} # Pipe content from stdin cat new-file.ts | aether file write src/new-file.ts ``` ## aether file mkdir Create a directory. ```bash theme={"dark"} aether file mkdir src/components/ui ``` ## aether file delete Delete a file or directory. ```bash theme={"dark"} aether file delete [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | ```bash theme={"dark"} aether file delete src/old-module.ts ``` ## aether file rename Rename or move a file or directory. ```bash theme={"dark"} aether file rename src/utils.ts src/helpers.ts ``` ## aether file stat Show metadata for a file or directory: path, type, size, and modified time. ```bash theme={"dark"} aether file stat src/index.ts ``` # aether git Source: https://docs.runaether.dev/cli/git View diffs, revert files, and manage workspace snapshots View diffs, revert files, and manage snapshots for the repository in your workspace. All commands run over the workspace WebSocket connection. ## Usage ```bash theme={"dark"} aether git [flags] ``` Every subcommand accepts the persistent `--workspace` flag. When omitted, the command uses the project's single running workspace. | Flag | Description | | ------------- | ---------------------------------------------------------- | | `--workspace` | Workspace ID (defaults to the project's running workspace) | ## Subcommands | Subcommand | Description | | --------------------- | --------------------------------------------- | | `diff` | Show changes against the base branch | | `revert ` | Revert a single file to its base branch state | | `revert-all` | Revert all changes to the base branch state | | `snapshots` | List git snapshots | | `restore ` | Restore the workspace to a snapshot | ## aether git diff Show a per-file summary of changes against the base branch. Each line shows a status letter, the file path, and the lines added and deleted — not a unified patch. ```bash theme={"dark"} aether git diff ``` ``` Diff against main (2 files changed, +12 -3): M src/auth/login.ts +9 -1 A src/auth/errors.ts +3 -0 ``` The status letter is `A` (added), `M` (modified), `D` (deleted), or `R` (renamed). ## aether git revert Revert a single changed file to its state on the base branch. ```bash theme={"dark"} aether git revert src/auth/login.ts ``` ``` Reverted src/auth/login.ts. ``` ## aether git revert-all Revert all changed files to the base branch state. ```bash theme={"dark"} aether git revert-all [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | This discards all of the agent's uncommitted modifications in the workspace. ## aether git snapshots List the git snapshots the agent created during its run. ```bash theme={"dark"} aether git snapshots [flags] ``` | Flag | Description | | --------- | ------------------------------------------------------ | | `--limit` | Maximum number of snapshots to show. Defaults to `20`. | ```bash theme={"dark"} aether git snapshots --limit 5 ``` ``` MESSAGE ID HASH TIMESTAMP msg_a1b2c3d4 9f3c1a2 10m ago msg_e5f6a7b8 1b7e4d8 18m ago ``` ## aether git restore Restore the workspace to a snapshot, identified by its message ID. ```bash theme={"dark"} aether git restore [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | ```bash theme={"dark"} aether git restore msg_a1b2c3d4 ``` Restoring discards all changes made after the chosen snapshot. # aether github Source: https://docs.runaether.dev/cli/github Connect GitHub, import a repository into a project, and push changes back Manage the GitHub integration. Connect the Aether GitHub App, import a repository into a project, push workspace changes back, and inspect the linked repo. Linking a repo to a project happens through `import` (a set-once binding); `link` only shows what is already linked. **Alias:** `gh` ## Usage ```bash theme={"dark"} aether github [flags] ``` ## Subcommands | Subcommand | Description | | ------------ | ------------------------------------------------------------- | | `connect` | Install the Aether GitHub App and connect your account | | `disconnect` | Disconnect GitHub and remove all installations and repo links | | `status` | Show connection status and installations | | `repos` | List repositories for an installation | | `import` | Import a repository into a project | | `export` | Push workspace changes to the linked repository | | `link` | Show the repository linked to a project | | `git-status` | Show git status for a workspace | | `tree` | Fetch the linked repository's tree at a ref | ## aether github connect Prints the Aether GitHub App installation URL, attempts to open it in your browser, then polls until the installation is connected (up to 5 minutes). The URL is always printed, including in a non-TTY shell. ```bash theme={"dark"} aether github connect ``` ``` Connect GitHub in your browser. Install the Aether GitHub App to grant repository access. URL: https://github.com/apps/aether/installations/new Waiting for connection... GitHub connected to my-org. ``` ## aether github status Show whether GitHub is connected and list each installation. ```bash theme={"dark"} aether github status ``` ``` GitHub: connected Installation: 12345678 Account: my-org Type: Organization Connected: Jan 2, 2026 ``` If no installation is connected, the command prints `GitHub: not connected` and points you to `aether github connect`. ## aether github repos List repositories accessible through a GitHub App installation. ```bash theme={"dark"} aether github repos --installation-id 12345678 ``` | Flag | Description | | ------------------- | ----------------------------------------- | | `--installation-id` | GitHub App installation ID (**required**) | | `--page` | Page number for pagination (default `1`) | | `--search` | Filter repositories by name | Run `aether github status` to find your installation IDs. The output lists `REPO`, `PRIVATE`, `DEFAULT BRANCH`, and `UPDATED`; when more results exist the command tells you which `--page` to request next. ## aether github import Import a GitHub repository into a project. This is the set-once repo binding for the project — see [GitHub integration](/guides/github-integration). ```bash theme={"dark"} aether github import --repo owner/repo ``` | Flag | Description | | ------------------- | ---------------------------------------------------------------------------------------------------------- | | `--repo` | Repository in `owner/repo` format. Optional — omit it to pick interactively (requires `--installation-id`) | | `--installation-id` | GitHub App installation ID. Required for private repos and for the interactive picker | | `--workspace` | Also clone into this workspace | Omit `--repo` to choose a repository from a picker. The picker needs `--installation-id` so it can list that installation's repos: ```bash theme={"dark"} aether github import --installation-id 12345678 ``` ``` Linked repo: owner/repo (main) Clone URL: https://github.com/owner/repo.git ``` ## aether github export Commit and push a workspace's changes to the linked repository. ```bash theme={"dark"} aether github export --workspace abc12345 --commit-message "Add feature" ``` | Flag | Description | | ------------------ | ---------------------------------------------------------------- | | `--workspace` | Workspace ID. Defaults to the project's single running workspace | | `--commit-message` | Commit message for the push | | `--force` | Force push | If the remote has changes your project doesn't (a `409` non-fast-forward), the command stops and offers to force push. Pass `--force` to skip that prompt. ``` Pushed to: https://github.com/owner/repo ``` ## aether github link Show the repository currently linked to a project. This command is read-only — it does not select or change the link. Link a repo with `aether github import`. ```bash theme={"dark"} aether github link ``` ``` Linked: owner/repo Clone URL: https://github.com/owner/repo.git ``` ## aether github git-status Show the git repository and branch status for a workspace. ```bash theme={"dark"} aether github git-status --workspace abc12345 ``` | Flag | Description | | ------------- | ---------------------------------------------------------------- | | `--workspace` | Workspace ID. Defaults to the project's single running workspace | ``` Workspace: abc12345 Git repo: true Branch: main ``` ## aether github tree Fetch the linked repository's git tree at a ref. The output lists each entry's `TYPE`, `PATH`, and `SHA`. ```bash theme={"dark"} aether github tree --ref main ``` | Flag | Description | | ------- | ------------------------------ | | `--ref` | Tree SHA or ref (**required**) | ## aether github disconnect Disconnect GitHub. This removes all installations and repo links for your account. ```bash theme={"dark"} aether github disconnect [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | ## See also * [GitHub integration guide](/guides/github-integration) * [GitHub API reference](/api-reference/account/github/get-github-status) # Installation Source: https://docs.runaether.dev/cli/installation Install the Aether CLI on macOS, Linux, or Windows Prebuilt `aether` binaries are published for macOS, Linux, and Windows on `amd64` and `arm64`. Every download is verified against the release's `checksums.txt` before it is installed. ## macOS and Linux ```bash theme={"dark"} curl -fsSL https://app.runaether.dev/install.sh | sh ``` The script detects your OS and architecture, verifies the checksum, and installs `aether` to `/usr/local/bin` (using `sudo` only if that directory isn't writable). To install somewhere else, set `AETHER_INSTALL_DIR`: ```bash theme={"dark"} curl -fsSL https://app.runaether.dev/install.sh | AETHER_INSTALL_DIR="$HOME/.local/bin" sh ``` To upgrade, run the script again — it always installs the latest release. ## Homebrew (macOS) ```bash theme={"dark"} brew install --cask Aether-Runtime/aether/aether ``` This taps `Aether-Runtime/aether` and installs the `aether` cask in one step. The cask is macOS-only; on Linux, use the install script above. To upgrade later: ```bash theme={"dark"} brew upgrade --cask Aether-Runtime/aether/aether ``` Use the fully qualified name in both commands. Since Homebrew 6.0, content from non-official taps must be trusted before it loads, and naming the cask in full grants that trust for this cask alone. The short form `brew upgrade aether` fails with `Refusing to load cask ... from untrusted tap`. ## Windows ```powershell theme={"dark"} irm https://app.runaether.dev/install.ps1 | iex ``` The script detects your architecture, verifies the checksum, installs `aether.exe` to `%LOCALAPPDATA%\Aether\bin`, and adds that directory to your user `PATH`. To install somewhere else (skipping the `PATH` update), set `AETHER_INSTALL_DIR` first: ```powershell theme={"dark"} $env:AETHER_INSTALL_DIR = "C:\tools\aether" irm https://app.runaether.dev/install.ps1 | iex ``` To upgrade, run the script again — it always installs the latest release. Windows binaries ship starting with the first CLI release after v0.1.8. Until that release is published, this command fails with a download error. ## Verify Installation ```bash theme={"dark"} aether version ``` ``` aether version 0.1.0 (a1b2c3d) go: go1.26.4 os/arch: darwin/arm64 ``` The version string reflects the release tag you installed. ## Shell Completion Set up tab completion for your shell. See the [completion](/cli/completion) reference for full setup instructions. ```bash theme={"dark"} source <(aether completion bash) ``` To persist across sessions, add to your `~/.bashrc`: ```bash theme={"dark"} echo 'source <(aether completion bash)' >> ~/.bashrc ``` ```bash theme={"dark"} aether completion zsh > "${fpath[1]}/_aether" ``` Then restart your shell or run `compinit`. ```bash theme={"dark"} aether completion fish | source ``` To persist across sessions: ```bash theme={"dark"} aether completion fish > ~/.config/fish/completions/aether.fish ``` ```powershell theme={"dark"} aether completion powershell | Out-String | Invoke-Expression ``` To persist, add the above line to your PowerShell profile. ## Next Steps After installing, run the interactive setup: ```bash theme={"dark"} aether setup ``` See the [quickstart](/quickstart#step-2-run-aether-setup) for each step in the setup flow. # aether linear Source: https://docs.runaether.dev/cli/linear Connect Linear and route issues to Aether projects as tasks Manage the Linear integration. Connect your Linear organization, then create routes that map a Linear team (optionally a specific Linear project) to an Aether project. Matching Linear issues spawn Aether tasks, and results post back to the issue as comments. The Linear integration is available only when the deployment is configured with Linear OAuth credentials. If `aether linear connect` reports the integration is unavailable, it is not enabled in your deployment. ## Usage ```bash theme={"dark"} aether linear [flags] ``` ## Subcommands | Subcommand | Description | | -------------------- | ------------------------------------------------------ | | `connect` | Connect your Linear organization | | `disconnect` | Disconnect Linear and remove all routes | | `status` | Show connection status | | `teams` | List Linear teams | | `projects` | List Linear projects | | `routes list` | List Linear routes | | `routes create` | Create a route from a Linear team to an Aether project | | `routes update ` | Enable or disable a route | | `routes delete ` | Delete a route | ## aether linear connect Opens your browser for OAuth authorization, then polls until connected (up to 5 minutes). If Linear is already connected, it prints the organization and exits. ```bash theme={"dark"} aether linear connect ``` ``` Opening browser to connect Linear... Authorize Aether to access your Linear organization. Waiting for connection... Linear connected (organization: My Org). ``` ## aether linear status Show whether Linear is connected and the connected organization. ```bash theme={"dark"} aether linear status ``` ``` Linear: connected Organization: My Org ID: org_12345 ``` ## aether linear teams List Linear teams in the connected organization. The output lists `ID`, `KEY`, and `NAME`. ```bash theme={"dark"} aether linear teams ``` ## aether linear projects List Linear projects. The output lists `ID`, `TEAM`, and `NAME`. ```bash theme={"dark"} aether linear projects [flags] ``` | Flag | Description | | ----------- | --------------------------------- | | `--team-id` | Filter projects by Linear team ID | ## aether linear routes list List Linear routes. The output lists `ID`, `TEAM`, `LINEAR PROJECT` (`all` when the route covers the whole team), `AETHER PROJECT`, and `ENABLED`. ```bash theme={"dark"} aether linear routes list ``` ## aether linear routes create Create a route mapping a Linear team to an Aether project. Omit `--team-id` to pick a team interactively. Omit `--aether-project` to use your selected project. The Aether project must have a linked GitHub repository. ```bash theme={"dark"} aether linear routes create ``` | Flag | Description | | --------------------- | ------------------------------------------------------------------------------------ | | `--team-id` | Linear team ID. Omit to pick interactively (run `aether linear teams` to look it up) | | `--linear-project-id` | Optional Linear project ID. Omit to cover all projects in the team | | `--aether-project` | Aether project ID or prefix. Defaults to your selected project | | `--enabled` | Enable the route, `true` or `false` (default `true`) | ```bash theme={"dark"} aether linear routes create --team-id TEAM_123 --aether-project a1b2c3d4 --enabled true ``` ``` Created Linear route 9f8e7d6c for team Backend -> project a1b2c3d4. ``` ## aether linear routes update Enable or disable a route. ```bash theme={"dark"} aether linear routes update --enabled false ``` | Flag | Description | | ----------- | -------------------------------- | | `--enabled` | `true` or `false` (**required**) | ## aether linear routes delete Delete a Linear route. ```bash theme={"dark"} aether linear routes delete [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | ## aether linear disconnect Disconnect Linear. This removes all Linear routes. ```bash theme={"dark"} aether linear disconnect [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | # aether models Source: https://docs.runaether.dev/cli/models List the agent types and models accepted by --agent-type and --model List every agent type accepted by `--agent-type` and every model accepted by `--model`, grouped by agent, with each agent's default model marked. This command reads the catalog compiled into your CLI build. It makes no API call and works without authentication. ## Usage ```bash theme={"dark"} aether models ``` ## Output ``` Codex --agent-type codex gpt-5.6-sol GPT-5.6 Sol (default) gpt-5.6-terra GPT-5.6 Terra gpt-5.5 GPT-5.5 Claude Code --agent-type claude-code claude-opus-5 Claude Opus 5 (default) claude-sonnet-5 Claude Sonnet 5 claude-haiku-4-5 Claude Haiku 4.5 ``` Pass `--json` for a machine-readable list of `{agent_type, label, default_model, models}` objects. ## Notes A model belongs to exactly one agent type. Passing `--model` without a matching `--agent-type` is rejected by the API. The OpenCode agent additionally accepts any `openrouter//` string, which the list does not enumerate. Because the list ships with the CLI binary, a model added to the platform after your release appears only after you [upgrade](/cli/installation). It still works if you pass it — the API is the validator. ## Examples ```bash theme={"dark"} # Pick an agent and one of its models aether run "Fix the login bug" --agent-type claude-code --model claude-sonnet-5 # Every model slug, one per line aether models --json | jq -r '.[].models[].slug' ``` # aether notification Source: https://docs.runaether.dev/cli/notification List, dismiss, and configure delivery of notifications # aether notification Manage notifications and how they're delivered to you. **Alias:** `notifications` ## Usage ```bash theme={"dark"} aether notification [flags] ``` ## Subcommands | Subcommand | Description | | ----------------- | ----------------------------------------------------- | | `list` | List notifications | | `dismiss ` | Dismiss a notification | | `preferences` | Show delivery preferences (same as `preferences get`) | | `preferences get` | Show delivery preferences | | `preferences set` | Update one delivery preference | ## List ```bash theme={"dark"} aether notification list aether notification list --status all --limit 50 ``` | Flag | Default | Description | | ---------- | -------- | --------------------------------------------------------------------- | | `--status` | `active` | Status filter: `active`, `resolved`, `dismissed`, `archive`, or `all` | | `--limit` | `25` | Maximum notifications to return | | `--before` | — | Pagination cursor (the `Next cursor` from a previous page) | ## Dismiss ```bash theme={"dark"} aether notification dismiss ``` ## Preferences Each notification kind has independent email, Slack DM, and mobile push delivery toggles. ```bash theme={"dark"} aether notification preferences aether notification preferences set --kind task_failed --email false aether notification preferences set --kind question_requested --slack-dm true aether notification preferences set --kind task_failed --push false ``` | Flag | Description | | ------------ | ------------------------------------------------------------------------------------------------------------- | | `--kind` | Notification kind: `question_requested`, `plan_requested`, `task_response_ready`, or `task_failed` (required) | | `--email` | Enable or disable email delivery (`true`/`false`) | | `--slack-dm` | Enable or disable Slack DM delivery (`true`/`false`) | | `--push` | Enable or disable mobile push delivery (`true`/`false`) | `set` reads the existing preference first, then updates only the channels you pass. Provide at least one of `--email`, `--slack-dm`, or `--push`. # aether onboarding Source: https://docs.runaether.dev/cli/onboarding Complete CLI-driven onboarding after connecting its required integrations Manage the stored onboarding state used by Aether's web and CLI experiences. ## Usage ```bash theme={"dark"} aether onboarding ``` ## Subcommands | Subcommand | Description | | ---------- | ----------------------------------------------------------------- | | `complete` | Mark onboarding complete after server-verified prerequisites pass | ## aether onboarding complete Complete onboarding without launching a task. The server requires a connected GitHub installation and at least one connected coding-agent provider for self-serve accounts. ```bash theme={"dark"} aether onboarding complete ``` A new transition prints: ```text theme={"dark"} Onboarding completed. ``` The command is idempotent. A replay prints: ```text theme={"dark"} Onboarding already completed. ``` If prerequisites are missing, the command exits nonzero with the server's exact requirements message and HTTP status: ```text theme={"dark"} Error: onboarding completion requirements missing: github_installation,agent_provider_credential (403) ``` Use `--json` for the response object: ```json theme={"dark"} { "onboarding_completed": true, "already_completed": false } ``` See the [agent-driven setup guide](/agent-setup) for the complete non-TTY flow. # aether opencode Source: https://docs.runaether.dev/cli/opencode Connect OpenCode with local auth or an OpenRouter API key Manage authentication for the OpenCode agent provider. Connect with an OpenRouter API key, or upload the `auth.json` that your local `opencode auth login` wrote, so the OpenCode agent can run in Aether workspaces. See [connect agent providers](/guides/connect-agent-providers). For `connect`, log in with `opencode auth login` first. The command reads `${XDG_DATA_HOME:-~/.local/share}/opencode/auth.json` and uploads its contents verbatim. ## Usage ```bash theme={"dark"} aether opencode [flags] ``` ## Subcommands | Subcommand | Description | | ------------ | -------------------------------------- | | `api-key` | Save an OpenRouter API key | | `connect` | Upload your local OpenCode `auth.json` | | `status` | Show OpenCode auth status | | `disconnect` | Remove OpenCode auth | ## aether opencode connect Reads your local OpenCode `auth.json` and uploads its bytes. ```bash theme={"dark"} aether opencode connect ``` ``` OpenCode connected (uploaded 512 bytes from /home/you/.local/share/opencode/auth.json). ``` ## aether opencode api-key Save an OpenRouter key without installing OpenCode locally. The command prompts securely when `--api-key` is omitted. Saving a key preserves any other providers already imported into the OpenCode connection. ```bash theme={"dark"} aether opencode api-key ``` | Flag | Description | | ----------- | --------------------------------------------- | | `--api-key` | OpenRouter API key (omit for a secure prompt) | ## aether opencode status Show whether OpenCode auth is configured. ```bash theme={"dark"} aether opencode status ``` ``` OpenCode: configured (authFile) ``` When nothing is configured, the command prints `OpenCode: not configured` and points you to `connect`. ## aether opencode disconnect Remove the stored OpenCode auth. ```bash theme={"dark"} aether opencode disconnect [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | ## See also * [Connect agent providers](/guides/connect-agent-providers) * [Agents and models](/concepts/agents-and-models) # CLI Overview Source: https://docs.runaether.dev/cli/overview The aether command-line interface for projects, tasks, workspaces, billing, and integrations The Aether CLI manages projects, tasks, and cloud workspaces from your terminal. It connects to the Aether API, follows long-running agent tasks, and exposes every part of the platform that the web app does. Install it with the [install script or Homebrew](/cli/installation), then run the interactive setup: ```bash theme={"dark"} aether setup ``` ## Global Flags These persistent flags are available on every command. | Flag | Short | Description | | ------------ | ----- | -------------------------------------------------------- | | `--project` | `-p` | Override the default project ID or name for this command | | `--json` | | Output results as JSON | | `--no-color` | | Disable colored output | | `--api-url` | | Override the Aether API URL | | `--debug` | | Print debug information to stderr | ## Configuration The CLI stores configuration in `~/.aether/config.json`, created on first login. It holds your default project, API URL, and authentication tokens. Set `AETHER_CONFIG_DIR` to use a different directory. Manage non-sensitive config keys with [`aether config`](/cli/config). ### Environment Variables Environment variables take precedence over the config file. | Variable | Description | | ----------------------- | -------------------------------------------------------------------------- | | `AETHER_TOKEN` | Platform API key used as a bearer token (skips the interactive login flow) | | `AETHER_PROJECT` | Default project ID or name (overrides the config file) | | `AETHER_API_URL` | API base URL (overrides the config file) | | `AETHER_PREVIEW_DOMAIN` | Domain used when printing workspace preview URLs | | `AETHER_DEBUG=1` | Enable debug output to stderr | | `NO_COLOR` | Disable colored output (any non-empty value) | ## Command Groups ### Core | Command | Description | | ----------------------------------------------------- | ------------------------------------------------------ | | [`aether setup`](/quickstart#step-2-run-aether-setup) | Interactively prepare a fresh machine for Aether | | [`aether run`](/cli/run) | Create a task and watch the agent work | | [`aether models`](/cli/models) | List agent types and their models | | [`aether project`](/cli/project) | Manage projects | | [`aether task`](/cli/task) | Manage tasks and agent runs | | [`aether usage`](/cli/usage) | Show a credits, tokens, and VM-uptime summary | | [`aether billing`](/cli/billing) | Manage billing, credits, checkout, and portal sessions | | [`aether warm-slot`](/cli/warm-slot) | Manage warm slots | | [`aether notification`](/cli/notification) | Manage notifications and delivery preferences | | [`aether feedback`](/cli/feedback) | Submit product feedback | ### Workspace | Command | Description | | ------------------------------------ | ------------------------------------------ | | [`aether workspace`](/cli/workspace) | Manage cloud workspaces | | [`aether file`](/cli/file) | Read, write, and manage workspace files | | [`aether env`](/cli/env) | Manage project environment variables | | [`aether git`](/cli/git) | Git operations on the workspace repository | ### Integrations | Command | Description | | -------------------------------------- | ------------------------------------------------------------------- | | [`aether delegation`](/cli/delegation) | Configure cloud delegation for local Claude Code and Codex sessions | | [`aether github`](/cli/github) | Connect and sync with GitHub repositories | | [`aether sentry`](/cli/sentry) | Connect to Sentry and manage repo links | | [`aether slack`](/cli/slack) | Manage the Slack integration and routes | | [`aether linear`](/cli/linear) | Manage the Linear integration and routes | ### Configuration | Command | Description | | ------------------------------------------------------------------ | ------------------------------------------------ | | [`aether auth`](/cli/auth) | Log in, log out, and manage authentication | | [`aether opencode api-key`](/cli/opencode#aether-opencode-api-key) | Manage LLM provider (BYOK) API keys | | [`aether token`](/cli/token) | Manage platform API keys for the REST API | | [`aether settings`](/cli/settings) | Manage account-level agent and model preferences | | [`aether secret`](/cli/secret) | Manage project secret files | | [`aether skill`](/cli/skill) | Push, pull, and sync saved agent skills | | [`aether codex`](/cli/codex) | Manage Codex provider authentication | | [`aether claude-code`](/cli/claude-code) | Manage Claude Code provider authentication | | [`aether cursor`](/cli/cursor) | Manage Cursor (legacy) provider authentication | | [`aether opencode`](/cli/opencode) | Manage OpenCode provider authentication | | [`aether profile`](/cli/overview#profile) | Show the current user profile | | [`aether config`](/cli/config) | View and manage CLI configuration | | [`aether version`](/cli/overview#version) | Print version information | | [`aether completion`](/cli/completion) | Generate shell completion scripts | Three commands sound alike but manage three different kinds of credential. Don't mix them up: | Command | Manages | Format | | ------------------------------------------------------------------ | --------------------------------------------------------------------------- | ---------- | | [`aether token`](/cli/token) | Platform API keys for programmatic REST access (`AETHER_TOKEN` bearer keys) | `aether_…` | | [`aether opencode api-key`](/cli/opencode#aether-opencode-api-key) | Bring-your-own LLM provider keys (OpenRouter) | `sk-or-…` | | [`aether auth token`](/cli/auth) | Prints the credential the CLI logged in with, for piping to other tools | `aether_…` | ## Resolving IDs (Short Prefixes) Most commands that take an `` argument — `project`, `task`, `workspace`, and `token` — accept a short prefix instead of the full UUID. For projects, tasks, and workspaces the prefix is matched against the first characters of the ID (the listings print the first 8 characters); for tokens, any unique prefix works. ```bash theme={"dark"} # These are equivalent when the prefix is unique aether task get a1b2c3d4-e5f6-7890-abcd-ef1234567890 aether task get a1b2c3d4 ``` Resolution fails loudly: an unknown prefix returns "no … found matching", and a prefix that matches more than one record returns an "ambiguous prefix" error asking for a longer prefix. Values containing a `-` or 32+ characters are treated as full IDs and used as-is. ## Profile Print the current user's profile (email, ID, display name, onboarding status). ```bash theme={"dark"} aether profile ``` ## Version Print version, commit, Go version, and OS/arch. ```bash theme={"dark"} aether version ``` ``` aether version 0.1.0 (a1b2c3d) go: go1.26.4 os/arch: darwin/arm64 ``` The CLI docs are hand-written and verified against the source commands. They are not generated from the binary. # aether profile Source: https://docs.runaether.dev/cli/profile Show the current authenticated user's profile # aether profile Print the profile of the currently authenticated user. Useful for confirming which account the CLI is acting as. ## Usage ```bash theme={"dark"} aether profile ``` ``` Email: user@example.com ID: 11111111-2222-3333-4444-555555555555 Name: Ada Lovelace Onboarding: true ``` The display name line is shown only when a name is set. Pass `--json` for the machine-readable form. To check or change which account you're logged in as, see [`aether auth`](/cli/auth). # aether project Source: https://docs.runaether.dev/cli/project Create, list, and manage Aether projects Manage your Aether projects. A project groups source code, environment variables, an optional GitHub link, task defaults, and the history of agent runs. See the [Projects guide](/guides/projects) for concepts. ## Usage ```bash theme={"dark"} aether project [flags] ``` `projects` is an alias for `project`. ## Subcommands | Subcommand | Description | | -------------- | ---------------------------------------- | | `list` | List all projects | | `get ` | Show project details | | `create` | Create a new project | | `update ` | Update project metadata or task defaults | | `archive ` | Archive a project | | `select [id]` | Set the default project | Most `` arguments accept a [short prefix](/cli/overview#resolving-ids-short-prefixes). ## aether project list ```bash theme={"dark"} aether project list ``` ``` ID NAME REPO HARDWARE UPDATED a1b2c3d4 my-app github.com/acme/my-app medium 2 days ago e5f6g7h8 backend-api — small 1 week ago ``` Projects with no linked repository show `—` in the REPO column. ## aether project get Show full details for one project. ```bash theme={"dark"} aether project get a1b2c3d4 ``` ``` Name: my-app ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890 Description: Main application project Repo: github.com/acme/my-app Task defaults: Agent: codex Model: gpt-5.5 Mode: default Auto-fix CI: true Created: Jan 15, 2025 Updated: 2 days ago ``` ## aether project create ```bash theme={"dark"} aether project create [flags] ``` | Flag | Description | | --------------- | ---------------------------------- | | `--name` | Project name (prompted if omitted) | | `--description` | Project description | ```bash theme={"dark"} aether project create --name "my-app" --description "Main application" ``` ``` Project created: my-app (a1b2c3d4) ``` ## aether project update Update metadata or task defaults. Only the flags you pass are changed. ```bash theme={"dark"} aether project update [flags] ``` | Flag | Description | | ----------------------------- | ----------------------------------------------------------------------- | | `--name` | Project name | | `--description` | Project description | | `--agent-type` | Default task agent type | | `--model` | Default task model | | `--interaction-mode` | Default task interaction mode | | `--reasoning-effort` | Default task reasoning effort, or `none` to clear | | `--auto-fix-ci` | Default CI auto-fix (`true` or `false`) | | `--auto-fix-pr-comments` | Default PR-comment auto-fix (`true` or `false`) | | `--auto-rebase` | Default auto-rebase (`true` or `false`) | | `--pr-review-trigger` | PR review auto trigger: `disabled`, `on_pr_created`, or `on_every_push` | | `--pr-review-comment-trigger` | Enable the `aether review` PR-comment trigger (`true` or `false`) | | `--pr-review-respond` | Respond to replies on review comments (`true` or `false`) | | `--pr-review-prompt` | Custom PR review prompt; pass `""` to restore the default | ```bash theme={"dark"} aether project update a1b2c3d4 \ --agent-type codex \ --model gpt-5.5 \ --interaction-mode plan \ --auto-fix-ci true ``` ```bash theme={"dark"} aether project update a1b2c3d4 \ --pr-review-trigger on_pr_created \ --pr-review-respond true ``` Running `update` with no flags returns an error — provide at least one. ## aether project archive Archive a project. Live activity stops and the project is hidden from normal views, while task, run, and billing history is retained. ```bash theme={"dark"} aether project archive [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | Archiving destroys the project's active workspaces and volumes, disables its automations, and removes its environment variables, secret files, and integration routes. Task, run, and billing history is kept. Archiving cannot be undone, but you can create a new project from the same repository. ## aether project select Set the default project for all commands. With no argument, opens an interactive picker. ```bash theme={"dark"} # Interactive picker aether project select # Set directly aether project select a1b2c3d4 ``` ``` Default project set to: my-app (a1b2c3d4) ``` Once set, commands like [`aether run`](/cli/run) and [`aether env`](/cli/env) target this project automatically. # aether run Source: https://docs.runaether.dev/cli/run Create a task from a prompt and watch the agent work Create a task and watch the agent's progress. This is the primary CLI workflow — equivalent to `aether task create` followed by `aether task watch`. ## Usage ```bash theme={"dark"} aether run [prompt] [flags] ``` The prompt is required unless you pass `--stdin`. Multiple arguments are joined into one prompt. The task runs against your [default project](/cli/project#aether-project-select), or the one you pass with `-p`. ## Flags `aether run` accepts every task-create option flag plus `--no-watch` and the canonical environment-bootstrap prompt flag. | Flag | Description | | ------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `--no-watch` | Create the task without watching. Prints the task ID and exits. | | `--bootstrap-environment` | Use the CLI's canonical embedded environment-bootstrap prompt; cannot be combined with prompt arguments or `--stdin` | | `--stdin` | Read the prompt from standard input (cannot be combined with a prompt argument) | | `--size` | VM size: `small`, `medium`, or `large` | | `--agent-type` | Override the project default agent type; see [`aether models`](/cli/models) | | `--model` | Override the project default model; see [`aether models`](/cli/models) | | `--interaction-mode` | Override the project default interaction mode | | `--reasoning-effort` | Override reasoning effort, or pass `none` to clear it | | `--auto-fix-ci` | Auto-fix CI (`true` or `false`) | | `--auto-fix-pr-comments` | Auto-fix PR comments (`true` or `false`) | | `--auto-rebase` | Auto-rebase (`true` or `false`) | | `--base-branch` | Base branch for the task branch and pull request | | `--context-file` | Workspace file path to include in prompt context; repeatable | | `--attach` | Local image file to attach; repeatable | Global flags are also available. See the [CLI Overview](/cli/overview). Every task carries an explicit VM size. If you omit `--size`, the size is resolved server-side — the project's default task size, else `small` — and the CLI prints `size: project default (use --size to override)`. A task starts warm only when its project has a ready machine at the requested size. See [VM sizes and warm starts](/guides/tasks#vm-size). With `--json`, the size is still resolved server-side the same way; the CLI only suppresses the notice so stdout remains valid JSON. ## Examples ### Run a quick task ```bash theme={"dark"} aether run "Fix the login bug" --size medium ``` The CLI creates the task and watches its progress, polling the API every \~3 seconds for status updates (workspace startup, pull-request creation) and printing the agent's summary when it finishes its turn. The watch exits when the task completes, fails, or stops. For a live stream of the agent's text, reasoning, and tool calls, open the task in the web or mobile app. ### Create a task without watching ```bash theme={"dark"} aether run "Add dark mode" --no-watch ``` ``` Task created: a1b2c3d4 Run 'aether task watch a1b2c3d4' to watch progress. ``` Reattach later with [`aether task watch`](/cli/task#aether-task-watch). ### Bootstrap a repository's Aether environment ```bash theme={"dark"} aether run --bootstrap-environment ``` This uses the same embedded environment-bootstrap prompt as the guided first-task flow. The prompt has one canonical source in the CLI; callers select it with the flag rather than copying it into scripts or documentation. ### Target a specific project ```bash theme={"dark"} aether run "Update the README" -p my-web-app ``` ### Read the prompt from stdin with context ```bash theme={"dark"} cat prompt.txt | aether run --stdin --context-file src/auth/login.ts --attach screenshot.png ``` `--context-file` references a path inside the workspace; `--attach` uploads a local image file (PNG, JPEG, GIF, WebP, or AVIF, up to 5 MiB each). ### Branch from a specific base ```bash theme={"dark"} aether run "Cut the release branch" --base-branch develop ``` ## Interactive Watch When attached, the watch is interactive on a TTY. The agent can pause to ask a question or propose a plan, and you answer inline. See [interactive watch](/cli/task#interactive-watch) for the full key bindings and behavior. # aether secret Source: https://docs.runaether.dev/cli/secret Manage project secret files materialized into the workspace Manage a project's secret files. Unlike [environment variables](/cli/env), which are single key/value pairs, secret files hold full file contents (such as a service-account JSON or a `.env` file). They are encrypted at rest and materialized into the workspace at the given path. **Aliases:** `secrets`, `secret-file`, `secret-files` ## Usage ```bash theme={"dark"} aether secret [flags] ``` ## Subcommands | Subcommand | Description | | ------------------- | --------------------------------------- | | `list` | List secret files | | `get ` | Print a secret file's decrypted content | | `upload ` | Upload or replace a secret file | | `delete ` | Delete a secret file | | `bulk-upload ` | Upload every file in a directory | ## aether secret list List the secret files for the current project, showing the path and when each was last updated. ```bash theme={"dark"} aether secret list ``` ``` PATH UPDATED .env 2h ago config/service-account.json 1d ago ``` ## aether secret get Print the decrypted content of a secret file to stdout. ```bash theme={"dark"} aether secret get .env ``` ## aether secret upload Upload a secret file at the given workspace path, replacing it if it already exists. By default, the CLI reads the local regular file at ``. Use `--from ` for a different local path or `--stdin` for piped content. Files must be valid UTF-8 and at most 1 MiB; symlinks and special files are rejected. ```bash theme={"dark"} aether secret upload [flags] ``` | Flag | Description | | --------- | ----------------------------------- | | `--from` | Local file to read the content from | | `--stdin` | Read content from standard input | ```bash theme={"dark"} aether secret upload .env ``` ```bash theme={"dark"} # Pipe content from stdin cat service-account.json | aether secret upload config/service-account.json --stdin ``` ## aether secret delete Delete a secret file. ```bash theme={"dark"} aether secret delete [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | ```bash theme={"dark"} aether secret delete .env ``` ## aether secret bulk-upload Upload every file in a local directory as secret files. Each file's path is taken relative to the directory. Pass `--prefix` to prepend a path prefix to every uploaded file. ```bash theme={"dark"} aether secret bulk-upload [flags] ``` | Flag | Description | | ---------- | -------------------------------------------- | | `--prefix` | Path prefix to prepend to each uploaded file | ```bash theme={"dark"} aether secret bulk-upload ./secrets --prefix config ``` # aether sentry Source: https://docs.runaether.dev/cli/sentry Connect Sentry and link Sentry projects to GitHub repos for auto-fix Manage the Sentry integration. Connect your Sentry organization, then link Sentry projects to GitHub repositories. When a link has auto-fix enabled, new Sentry errors create Aether tasks that fix them on the linked branch. Connect GitHub before creating links — `aether sentry links create` requires a connected GitHub account so it can map a Sentry project to a repository. Run [`aether github connect`](/cli/github) first. ## Usage ```bash theme={"dark"} aether sentry [flags] ``` ## Subcommands | Subcommand | Description | | ------------------- | ----------------------------------------- | | `connect` | Connect your Sentry organization | | `disconnect` | Disconnect Sentry and remove all links | | `status` | Show connection status | | `projects` | List Sentry projects in the connected org | | `links list` | List Sentry-to-GitHub links | | `links create` | Link a Sentry project to a GitHub repo | | `links update ` | Update a link's branch or auto-fix | | `links delete ` | Delete a link | ## aether sentry connect Opens your browser for OAuth authorization, then polls until connected (up to 5 minutes). If Sentry is already connected, it prints the current org and exits. ```bash theme={"dark"} aether sentry connect ``` ``` Opening browser to connect Sentry... Authorize Aether to access your Sentry organization. Waiting for connection... Sentry connected (org: my-org). ``` ## aether sentry status Show whether Sentry is connected and the connected organization. ```bash theme={"dark"} aether sentry status ``` ``` Sentry: connected Org Slug: my-org Org ID: 12345 ``` ## aether sentry projects List Sentry projects in the connected organization. The output lists `ID`, `SLUG`, `NAME`, and `PLATFORM`. ```bash theme={"dark"} aether sentry projects ``` ## aether sentry links list List links between Sentry projects and GitHub repositories. The output lists `ID`, `SENTRY PROJECT`, `GITHUB REPO`, `BRANCH`, and `AUTO-FIX`. ```bash theme={"dark"} aether sentry links list ``` ## aether sentry links create Link a Sentry project to a GitHub repository and target branch. Any value you omit is collected through an interactive prompt: omit the Sentry project flags to pick from a list, omit `--github-repo` to pick a repo (requires `--github-installation-id`), and an empty `--branch` or `--auto-fix` prompts you for that value. ```bash theme={"dark"} aether sentry links create ``` | Flag | Description | | -------------------------- | ------------------------------------------------------------------------------------------------ | | `--sentry-project-id` | Sentry project ID. Omit to pick interactively | | `--sentry-project-slug` | Sentry project slug. Omit to pick interactively | | `--sentry-project-name` | Sentry project display name. Omit to pick interactively | | `--github-repo` | GitHub repo in `owner/repo` format. Omit to pick interactively | | `--github-installation-id` | GitHub App installation ID, used for the interactive repo picker when `--github-repo` is omitted | | `--branch` | Target branch for fixes. Empty prompts you for it (no default) | | `--auto-fix` | `true` or `false`. Empty prompts you to enable auto-fix | Run `aether github status` to find an installation ID. To create a link non-interactively, provide all three Sentry project fields plus the repo: ```bash theme={"dark"} aether sentry links create \ --sentry-project-id 12345 \ --sentry-project-slug backend-api \ --sentry-project-name "Backend API" \ --github-repo owner/backend-api \ --branch main \ --auto-fix true ``` ``` Created link: Backend API -> owner/backend-api (branch: main, auto-fix: true) ``` ## aether sentry links update Update a link's target branch and/or auto-fix setting. At least one flag is required. ```bash theme={"dark"} aether sentry links update [flags] ``` | Flag | Description | | ------------ | ----------------- | | `--branch` | New target branch | | `--auto-fix` | `true` or `false` | ```bash theme={"dark"} aether sentry links update 1 --auto-fix false ``` ## aether sentry links delete Delete a Sentry project link. ```bash theme={"dark"} aether sentry links delete [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | ## aether sentry disconnect Disconnect Sentry. This removes all Sentry project links. ```bash theme={"dark"} aether sentry disconnect [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | ## See also * [aether github](/cli/github) * [Sentry API reference](/api-reference/account/sentry/get-sentry-status) # aether settings Source: https://docs.runaether.dev/cli/settings View and set account-level agent and model preferences # aether settings Manage your account-level agent and model preferences. Per-project [task defaults](/guides/projects#task-defaults) control the settings that new tasks inherit. ## Usage ```bash theme={"dark"} aether settings [flags] ``` ## Subcommands | Subcommand | Description | | ---------- | ----------------------------- | | `get` | Show current account defaults | | `set` | Update account defaults | ## Get ```bash theme={"dark"} aether settings get ``` Prints your default agent and model. ## Set Provide at least one flag — only the settings you pass are changed. ```bash theme={"dark"} aether settings set --agent-type codex --model gpt-5.5 ``` | Flag | Description | | -------------- | -------------------- | | `--agent-type` | Default coding agent | | `--model` | Default model | For the list of selectable agents and models, see [agents and models](/concepts/agents-and-models). # aether skill Source: https://docs.runaether.dev/cli/skill Manage saved user skills and install the public teleport skill Saved user skills are reusable `SKILL.md` instruction packs that the agent can load into any task. Use this command to push local skill folders to your account, pull them back, and keep them in sync. It can also install Aether's public teleport skill locally. For the concepts behind saved skills, see [Skills](/concepts/skills); for an end-to-end walkthrough, see the [Skills guide](/guides/skills). ## Subcommands | Command | Description | | -------------------------------------- | ------------------------------------------------ | | `aether skill list` | List your saved user skills | | `aether skill get ` | Show a saved skill (optionally its files) | | `aether skill push ` | Upload a local skill folder as a saved skill | | `aether skill pull ` | Download a saved skill to a local directory | | `aether skill sync` | Discover local skills and upload them | | `aether skill delete ` | Delete a saved skill | | `aether skill install aether-teleport` | Install the public Aether teleport skill locally | ## `install` ```bash theme={"dark"} aether skill install aether-teleport aether skill install aether-teleport --dir /path/to/agent/skills/aether-teleport ``` Installs the [teleport skill](/guides/teleport) for local Claude Code and Codex sessions. The only supported name is `aether-teleport`, and installing this public static bundle does not require an Aether login. Without an override, the command installs into `~/.claude/skills/aether-teleport` when `~/.claude` exists and into `~/.agents/skills/aether-teleport` when `~/.codex` exists. Both detections may fire. Install into this exact directory and skip local-agent detection. No `skills/aether-teleport` suffix is appended. ## `list` ```bash theme={"dark"} aether skill list ``` Lists every saved user skill with its slug, source, and description. ## `get` ```bash theme={"dark"} aether skill get my-skill aether skill get my-skill --files aether skill get my-skill --out ./my-skill ``` Show the skill's file list. Write the skill's files to this directory. ## `push` ```bash theme={"dark"} aether skill push ./skills/code-review aether skill push ./skills/code-review --slug code-review --force ``` The skill folder must contain a `SKILL.md` manifest. By default the slug is the basename of ``. Slug override (default: basename of ``). Skip the overwrite confirmation if the slug already exists. ## `pull` ```bash theme={"dark"} aether skill pull code-review ./skills/code-review aether skill pull code-review ./skills/code-review --force ``` Overwrite the target directory if it is non-empty. ## `sync` ```bash theme={"dark"} aether skill sync aether skill sync --dry-run aether skill sync --source ./skills --force ``` Discovers local skill folders and uploads them. Override the local source directories. Repeatable. List what would be uploaded without uploading. Overwrite existing skills without prompting. ## `delete` ```bash theme={"dark"} aether skill delete code-review --force ``` Skip the confirmation prompt. # aether slack Source: https://docs.runaether.dev/cli/slack Connect Slack and route channels to Aether projects as tasks Manage the Slack integration. Connect your Slack workspace, then create routes that map a Slack channel to an Aether project. Messages in a routed channel spawn Aether tasks, and task updates post back to the channel thread. The Slack integration is available only when the deployment is configured with Slack OAuth credentials. If `aether slack connect` reports the integration is unavailable, it is not enabled in your deployment. ## Usage ```bash theme={"dark"} aether slack [flags] ``` ## Subcommands | Subcommand | Description | | -------------------- | -------------------------------------------------------- | | `connect` | Connect your Slack workspace | | `disconnect` | Disconnect Slack and remove all routes | | `status` | Show connection status | | `channels` | List Slack channels | | `routes list` | List Slack routes | | `routes create` | Create a route from a Slack channel to an Aether project | | `routes update ` | Enable or disable a route | | `routes delete ` | Delete a route | ## aether slack connect Opens your browser for OAuth authorization, then polls until connected (up to 5 minutes). If Slack is already connected, it prints the workspace and exits. ```bash theme={"dark"} aether slack connect ``` ``` Opening browser to connect Slack... Authorize Aether to access your Slack workspace. Waiting for connection... Slack connected (workspace: My Team). ``` ## aether slack status Show whether Slack is connected and the connected workspace. ```bash theme={"dark"} aether slack status ``` ``` Slack: connected Workspace: My Team Team ID: T012AB3CD ``` ## aether slack channels List Slack channels visible to the app. The output lists `ID`, `NAME`, and `PRIVATE`. ```bash theme={"dark"} aether slack channels ``` ## aether slack routes list List Slack routes. The output lists `ID`, `CHANNEL`, `AETHER PROJECT`, and `ENABLED`. ```bash theme={"dark"} aether slack routes list ``` ## aether slack routes create Create a route mapping a Slack channel to an Aether project. Omit the channel flags to pick a channel interactively. Omit `--aether-project` to use your selected project. The Aether project must have a linked GitHub repository. ```bash theme={"dark"} aether slack routes create ``` | Flag | Description | | ------------------ | ------------------------------------------------------------------------------------------------ | | `--channel-id` | Slack channel ID. Must be passed together with `--channel-name`; omit both to pick interactively | | `--channel-name` | Slack channel name. Must be passed together with `--channel-id` | | `--private` | Mark the channel as private when using `--channel-id`/`--channel-name` | | `--aether-project` | Aether project ID or prefix. Defaults to your selected project | | `--enabled` | Enable the route, `true` or `false` (default `true`) | Run `aether slack channels` to look up channel IDs. ```bash theme={"dark"} aether slack routes create --channel-id C012AB3CD --channel-name eng --aether-project a1b2c3d4 ``` ``` Created Slack route 9f8e7d6c for #eng -> project a1b2c3d4. ``` ## aether slack routes update Enable or disable a route. ```bash theme={"dark"} aether slack routes update --enabled false ``` | Flag | Description | | ----------- | -------------------------------- | | `--enabled` | `true` or `false` (**required**) | ## aether slack routes delete Delete a Slack route. ```bash theme={"dark"} aether slack routes delete [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | ## aether slack disconnect Disconnect Slack. This removes all Slack routes. ```bash theme={"dark"} aether slack disconnect [flags] ``` | Flag | Description | | --------- | ---------------------------- | | `--force` | Skip the confirmation prompt | # aether task Source: https://docs.runaether.dev/cli/task Create, monitor, and manage agent tasks Manage agent tasks. A task is one agent run that reads files, executes commands, and writes code in an isolated workspace. See [tasks and lifecycle](/concepts/tasks-and-lifecycle) for concepts. ## Usage ```bash theme={"dark"} aether task [flags] ``` `tasks` is an alias for `task`. Most `` arguments accept a [short prefix](/cli/overview#resolving-ids-short-prefixes). ## Subcommands | Subcommand | Description | | ------------------------------------- | --------------------------------------------------- | | `list` | List tasks for the current project | | `create` | Create a new task | | `get ` | Show task details and the event timeline | | `status ` | Show compact task status | | `update ` | Update the task's agent settings | | `respond ` | Send a follow-up message or answer the agent inline | | `stop ` | Stop a running task and park it in `awaiting_input` | | `archive ` | Archive a task | | `unarchive ` | Unarchive a task | | `remove-from-queue ` | Remove one queued follow-up message | | `watch ` | Watch the agent's progress live | ## aether task list List tasks in the current project. ```bash theme={"dark"} aether task list [flags] ``` | Flag | Description | | ------------------- | -------------------------------------------------------------- | | `--status` | Filter by display status, comma-separated (see below) | | `--needs-you` | Only tasks waiting on you — mutually exclusive with `--status` | | `--started-at-op` | Started-at operator: `before`, `after`, or `equal` | | `--started-at-date` | Started-at date (`YYYY-MM-DD`) | | `--ended-at-op` | Ended-at operator: `before`, `after`, or `equal` | | `--ended-at-date` | Ended-at date (`YYYY-MM-DD`) | | `--order` | Order: `created_at` or `agent_sidebar` | | `--limit` | Maximum tasks to return | | `--page` | Page number | `--status` takes one or more **display statuses**, comma-joined: `running`, `awaiting_input`, `awaiting_ci`, `merged`, `unmerged`, `errored`, `archived`. It also takes `review_attention` — a virtual filter token, not a display status, that matches a task whose pull-request review needs your judgment. Pass `all` to clear the filter. `--needs-you` is the shorthand for the same set the web app's "Needs you" filter sends: `--status awaiting_input,review_attention,errored`. ```bash theme={"dark"} aether task list ``` ``` ID STATUS PR CREATED NAME a1b2c3d4 running 2m ago Fix the login bug e5f6g7h8 awaiting_input 1h ago Add dark mode i9j0k1l2 errored 3h ago Deploy to production q7r8s9t0 done #131 awaiting_ci 5h ago Add rate limiting m3n4o5p6 done #128 merged 2d ago Ship the API docs ``` The **STATUS** column is the task's own state — `running`, `awaiting_input`, `errored`, `done`, or `archived`. Pull-request lifecycle lives in the **PR** column, so `--status merged` returns rows whose STATUS reads `done` with the merge shown under PR. The PR cell names the same bucket `--status` filters on: `merged`, or `awaiting_ci` for an open PR whose checks are still running. `unmerged` is a catch-all over open and closed PRs, so those rows show the pull request's own state (`open`, `closed`) instead. `--json` is unaffected: it prints the server's envelope, including the raw `display_status`. ```bash theme={"dark"} # Multiple statuses aether task list --status running,awaiting_input # Everything that blocks on you aether task list --needs-you ``` ## aether task create Create a task with a prompt. If `--prompt` is omitted (and not reading `--stdin`), you are prompted for it interactively. ```bash theme={"dark"} aether task create [flags] ``` | Flag | Description | | ------------------------ | ------------------------------------------------------------ | | `--prompt` | The task description | | `--stdin` | Read the prompt from standard input | | `--size` | VM size: `small`, `medium`, or `large` | | `--agent-type` | Override the project default agent type | | `--model` | Override the project default model | | `--interaction-mode` | Override the project default interaction mode | | `--reasoning-effort` | Override reasoning effort, or `none` to clear | | `--auto-fix-ci` | Auto-fix CI (`true` or `false`) | | `--auto-fix-pr-comments` | Auto-fix PR comments (`true` or `false`) | | `--auto-rebase` | Auto-rebase (`true` or `false`) | | `--base-branch` | Base branch for the task branch and pull request | | `--context-file` | Workspace file path to include in prompt context; repeatable | | `--attach` | Local image file to attach; repeatable | ```bash theme={"dark"} aether task create --prompt "Add input validation to the signup form" --size medium ``` ``` Task created: a1b2c3d4 Run 'aether task watch a1b2c3d4' to watch progress. ``` [`aether run`](/cli/run) does `create` plus `watch` in one step and is usually what you want. Every task carries an explicit VM size. If you omit `--size`, the size is resolved server-side — the project's default task size, else `small` — and the CLI prints `size: project default (use --size to override)`. See [VM sizes and warm starts](/guides/tasks#vm-size). With `--json`, the size is still resolved server-side the same way; the CLI only suppresses the notice so stdout remains valid JSON. ## aether task get Show task details, summary, and the event timeline. ```bash theme={"dark"} aether task get a1b2c3d4 ``` When the agent has a plan, `get` prints it as a numbered checklist after the status block — `[x]` done, `[>]` in progress, `[ ]` pending: ``` Plan: Show the agent's plan in the CLI 1. [x] Add the plan section to task get 2. [>] Print the step line in task watch 3. [ ] Update the CLI reference docs 1/3 done ``` The plan persists after the task settles, so a finished task still shows the checklist it ended on. Tasks whose harness never sent a plan have no `Plan` section. With `--json`, the plan is the optional `plan` field. ## aether task status Show a compact status line — runtime status, display status, workspace, PR/CI, and tokens. ```bash theme={"dark"} aether task status a1b2c3d4 ``` ## aether task update Update the task's model and behavior settings. Its harness is fixed at creation. Provide at least one flag. ```bash theme={"dark"} aether task update [flags] ``` | Flag | Description | | ------------------------ | ---------------------------------------- | | `--model` | Model | | `--interaction-mode` | Interaction mode | | `--reasoning-effort` | Reasoning effort, or `none` | | `--auto-fix-ci` | Auto-fix CI (`true` or `false`) | | `--auto-fix-pr-comments` | Auto-fix PR comments (`true` or `false`) | | `--auto-rebase` | Auto-rebase (`true` or `false`) | ## aether task respond Continue a task: send a follow-up message, or answer the agent inline when it is waiting on you. ```bash theme={"dark"} aether task respond [flags] ``` | Flag | Description | | -------------------- | -------------------------------------------------------------------------------------------- | | `--message` | Follow-up message text | | `--stdin` | Read the message from standard input | | `--answer` | Answer a structured question: `: