# AirCode Ø public documentation This document contains public user guides only. Configure private instance credentials separately; never send them to the documentation website. Source: https://aircodezero.com/docs/ai-gateway # AI gateway Use the AI accounts connected to your own AirCode Ø instance from your personal applications. Supply your instance's **Base URL**, a separate **application token**, and an exact **model ID**. The instance invokes its connected provider CLI and returns the answer through a text-only Chat Completions API. ```text Your application's backend → your instance URL + application token → a connected native provider account → validated answer + available usage counters ``` The website hosts documentation only. API requests go directly to your instance; aircodezero.com does not receive your instance address, token or prompts. Use this service only for your own applications and accounts. Provider terms, subscription access and quotas still apply. ## Set up the instance 1. Connect a compatible native provider account in **AI Usage**. The gateway lists the account models supported by the installed instance; API-key and managed-credit connections are excluded. 2. Open **Settings → AI gateway** on desktop/web, or **More → AI gateway** on native mobile, then turn on **Enable AI gateway**. 3. Copy the displayed **Base URL**. It ends in `/api/ai-gateway/v1`. 4. In **Application tokens**, enter your application's name, select **Create token**, and save the token before dismissing its one-time reveal. 5. Configure these server-side environment variables in your application: | Variable | Value | | --- | --- | | `AIRCODE_AI_BASE_URL` | The full Base URL copied from the instance, without an extra `/v1` | | `AIRCODE_AI_TOKEN` | The application token you just created | | `AIRCODE_AI_MODEL` | An exact `id` from **Compatible models** or `GET /models` | Keep tokens in your backend's secret configuration, not in browser code, a mobile app bundle, a repository, a URL or a shared conversation. The examples read environment variables instead of embedding credentials. Never use the instance password or an owner login token in the API-key field. The instance must remain running and reachable from your application's backend. `localhost` means the computer running that backend. Use the instance's HTTPS address for remote access, or your trusted local/private-network connection. The gateway does not create a public origin. A provider not listed by `/models` cannot be selected by inventing an ID. ## Authentication and model discovery Send `Authorization: Bearer YOUR_APPLICATION_TOKEN` on each call. Send JSON with `Content-Type: application/json`. No query parameters or cookie authentication are supported. Cross-origin browser calls are not supported; use your backend. `GET {BASE_URL}/models` returns an object with `object: "list"` and a `data` array. Each model provides an `id`, display `label`, `engine`, `engineLabel`, `provider`, `connectionLabel` and `efforts`. Pass the returned `id` unchanged; it may already contain percent encoding. The list can change when accounts, provider access or installed models change. For an OpenAI-compatible application's configuration, select **Chat Completions**, use the full Base URL, put the application token in its **API key** field, and copy one of the returned model IDs. An application that requires Responses, tools, images or unsupported sampling parameters is not compatible with the current gateway. ## Generate a response `POST {BASE_URL}/chat/completions` accepts the following fields. Unknown fields are refused rather than silently ignored. | Field | Supported value | | --- | --- | | `model` | Required exact model ID returned by `/models` | | `messages` | Required array of 1–100 messages; at least one non-empty user message | | `messages[].role` | `system`, `developer`, `user` or `assistant` | | `messages[].content` | A string, or an array of `{ "type": "text", "text": "..." }` blocks | | `reasoning_effort` | Optional value from the selected model's `efforts`; omit it for the default | | `stream` | Optional boolean; defaults to `false` | | `stream_options` | Only `{ "include_usage": true }` or `false` for that flag, with `stream: true` | | `n` | Omit it or use `1` | | `store` | Omit it or use `false` | Messages contain only `role` and `content`. Text blocks are joined with newlines. Null characters are refused. The normalized message array must fit within 64 KiB of UTF-8 JSON; the full HTTP body is limited to 80 KiB. The validated answer is limited to 49,152 JavaScript string code units. These are gateway limits, not promises about a provider's context window or output capacity. Supply the complete relevant conversation on every call. To continue a conversation, append the previous assistant answer and the next user message. The gateway does not create or retain a conversation, session or completion history. A token grants no access to instance projects, files, terminal, sessions or owner settings. A normal response has `object: "chat.completion"`; read the text from `choices[0].message.content`. It also includes `id`, `created`, the requested `model`, and `finish_reason: "stop"`. Native counters appear in `usage` when complete enough to report. The `aircode_usage` extension preserves accounting details and incomplete estimates. Missing counters or prices are unknown, never zero. API-equivalent estimates are not extra subscription charges. ## Streaming Set `stream: true` to receive `Content-Type: text/event-stream`. The current implementation uses **buffered streaming**: it sends an initial waiting comment and keepalive comments while the provider runs, then sends the complete answer after validation. It does **not** stream words or tokens as they are generated. A successful stream contains: 1. SSE comments beginning with `:` while waiting; ignore them. 2. A `data:` JSON `chat.completion.chunk` with the assistant role and complete text in `choices[0].delta.content`. 3. A chunk with an empty delta and `finish_reason: "stop"`. 4. An optional chunk with `choices: []`, `usage` (possibly `null`) and `aircode_usage` when `stream_options.include_usage` is true. 5. `data: [DONE]`. After a stream starts, failures arrive as an SSE `data:` event containing `error`; do not treat HTTP 200 alone as success. A failed stream closes without the success marker. SSE frames may be split across transport chunks; use an SDK or an SSE parser rather than parsing each network chunk as JSON. Closing a connection cancels its active request. Revoking a token, disabling the gateway or stopping the instance also cancels matching work. There is no automatic replay or resume. Long-running requests need an appropriate client and reverse-proxy timeout; keepalives cannot override a network's absolute request-duration limit. ## Quotas, token lifecycle and errors The gateway allows two active calls instance-wide, one active call per token, and 30 admitted generation requests per token per minute. Calls are not queued. These local limits do not replace provider quotas. Tokens expire after 90 days; create a replacement, update your application, then revoke the previous token. Disabling the gateway retains tokens but suspends their use. Restoring instance state disables the gateway and clears token authority; create fresh tokens. Errors use `error.message`, `error.type`, `error.code` and `error.param`. Read the code as well as the HTTP status. Do not automatically retry a generation whose outcome is unknown: it may already have consumed provider quota. The examples disable SDK retries. | Status/code | What to do | | --- | --- | | `401`, `AI_GATEWAY_AUTH_REQUIRED` | Check the token, expiry, revocation and target instance | | `403`, `AI_GATEWAY_DISABLED` | Enable the gateway on the target instance | | `403`, `AI_GATEWAY_ORIGIN_FORBIDDEN` | Call from your backend, not a foreign browser origin | | `AI_GATEWAY_ACCESS_REFUSED` | Check the instance's access/subscription requirements | | `400`, unsupported parameter or effort | Remove unsupported fields; use the selected model's advertised efforts | | `404`/`409`, `AI_GATEWAY_MODEL_UNAVAILABLE` | Refresh `/models` and explicitly select an available ID | | `413`, `AI_GATEWAY_INPUT_TOO_LARGE` | Reduce the supplied history; an oversized HTTP body is also refused | | `429`, `AI_GATEWAY_BUSY` | Wait for the active call to finish before trying again | | `429`, `AI_GATEWAY_RATE_LIMIT` | Stop immediate retries and wait for the local minute window | | `SAFE_STRUCTURED_AI_QUOTA_EXHAUSTED` | Wait for provider quota recovery; `error.resets_at` may be `null` | | `409`, cancellation | Treat the call as cancelled; do not resume automatically | | `502`/`503`, provider/unavailable error | Check the account connection and instance health before an explicit retry | An unknown provider reset time stays unknown. Never derive one from a model name or an unrelated provider's quota schedule. ## Using another instance from the application An external application can call another **own** instance with that instance's Base URL and a token created there. Each token belongs to its issuing instance. For interactive coding sessions, create a token with **Allow remote sessions** and follow [Remote sessions](/docs/remote-sessions). Save that connection in Settings, then choose it under **New session → Run on**. Agents work on the remote instance's authorized project and send live transcript/terminal events. Ordinary tokens remain text-only. Neither token signs the client in as the remote owner; Responses, Anthropic Messages and Chat Completions tool calling remain outside the text API's supported subset. For AI-assisted integration, see [Documentation for AI](/docs/ai-integrations), the [raw Markdown guide](/docs/ai-gateway.md), the [OpenAPI schema](/docs/ai-gateway/openapi.json), and the [integration skill](/skills/aircode-ai-gateway/SKILL.md). ## Code examples Set AIRCODE_AI_BASE_URL to the base URL above, AIRCODE_AI_TOKEN to your application token, and AIRCODE_AI_MODEL to an ID copied from Compatible models. Run these examples in your application’s backend or terminal. ### cURL Bash, curl and jq. List models first, then use the exact model ID you selected. ```bash set -euo pipefail # The token is read from standard input, not a command-line argument. curl --fail-with-body --silent --show-error --location --max-redirs 0 \ "$AIRCODE_AI_BASE_URL/models" --header @- <=3,<4". ```python import os from openai import OpenAI, DefaultHttpx2Client client = OpenAI( base_url=os.environ["AIRCODE_AI_BASE_URL"], api_key=os.environ["AIRCODE_AI_TOKEN"], max_retries=0, http_client=DefaultHttpx2Client(follow_redirects=False), ) for model in client.models.list(): print(model.id) response = client.chat.completions.create( model=os.environ["AIRCODE_AI_MODEL"], messages=[{"role": "user", "content": "Hello!"}], ) print(response.choices[0].message.content) ``` ### JavaScript streaming Node.js and the OpenAI SDK: npm install openai. This is buffered SSE: the complete validated answer arrives at the end. ```javascript import OpenAI from "openai"; function required(name) { if (!process.env[name]) throw new Error("Set " + name + " first."); return process.env[name]; } const client = new OpenAI({ baseURL: required("AIRCODE_AI_BASE_URL"), apiKey: required("AIRCODE_AI_TOKEN"), maxRetries: 0, fetchOptions: { redirect: "error" }, }); const stream = await client.chat.completions.create({ model: required("AIRCODE_AI_MODEL"), messages: [{ role: "user", content: "Hello!" }], stream: true, stream_options: { include_usage: true }, }); try { for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content ?? ""); if (chunk.usage) console.error("Tokens:", chunk.usage.total_tokens); } process.stdout.write("\n"); } finally { stream.controller.abort(); } ``` --- Source: https://aircodezero.com/docs/ai-integrations # Documentation for AI Give an AI assistant the links below to help it integrate your personal application with the AI gateway. The assistant still needs a tool that can make HTTP requests or run code, network access to your instance, and your explicit authorization to use its token. Reading documentation alone does not grant access, install a skill, or make a model capable of calling tools. | Resource | Purpose | | --- | --- | | [AI gateway guide](/docs/ai-gateway) | Human-readable setup, exact request fields, streaming, quotas and recovery | | [AI gateway Markdown](/docs/ai-gateway.md) | The same guide and runnable examples, without page chrome or scripts | | [OpenAPI 3.1](/docs/ai-gateway/openapi.json) | Machine-readable inference endpoints, bearer authentication, requests, responses and errors | | [Remote sessions](/docs/remote-sessions.md) | Project-scoped session launches, live SSE, terminal, inputs, permissions and intention recovery | | [Session OpenAPI](/docs/remote-sessions/openapi.json) | Exact session protocol and authentication | | [Integration skill](/skills/aircode-ai-gateway/SKILL.md) | A portable `SKILL.md` entrypoint explaining model discovery and safe calls | | [Documentation index](/llms.txt) | Links to the public human/Markdown documentation | | [Full public documentation](/llms-full.txt) | The published user guides in one plain-text document | Every public guide has a Markdown address: append `.md` to its canonical documentation URL. The full document contains public user guides, not private instance data or internal operator files. The API guide and examples are built from the same sources used by the human page and application. ## Give an assistant the right context Copy this instruction into an assistant that has the necessary tools: ```text Read https://aircodezero.com/skills/aircode-ai-gateway/SKILL.md and https://aircodezero.com/docs/ai-gateway.md. Integrate my personal application with my own AI gateway. Read AIRCODE_AI_BASE_URL and AIRCODE_AI_TOKEN from its secret environment. Do not display those values or send them to the documentation website. List the available models and use the exact model I select. Use text-only Chat Completions and disable automatic generation retries. Explain the current buffered-streaming behavior accurately. ``` For an assistant supporting skill folders, save the linked file as `aircode-ai-gateway/SKILL.md` in the skill directory supported by that assistant. No automatic installation is performed by this website. Other assistants can read it directly as instructions without installing anything. ## Required integration decisions The base URL and token must come from the same instance. Query `/models` before selecting a model; never invent an engine ID or promise access to every model in a subscription. The user chooses the model and any non-default effort. Keep credentials in the calling backend. This website has no API playground, token form or proxy to the owner's instance. Never place a credential in the OpenAPI server URL, a query string or a shared skill file. Configure OpenAPI tools with the user's instance URL and bearer secret in their private settings. The current API supports text-only Chat Completions and buffered SSE. It does not expose project tools, interactive coding sessions, Anthropic Messages or Responses. Do not configure a coding agent that requires those protocols as though this text gateway were a complete provider endpoint. --- Source: https://aircodezero.com/docs/configuration # Configuration For a managed instance, precedence is **environment variables → instance config → global user config → defaults**. `aircode start` options are persisted into the instance config before the service starts. Foreground `aircode serve` options override the environment for that process only. The global file is `~/.config/aircode/config.json`. Managed instance files live at `~/.config/aircode/instances/.json`; they are not placed in project directories. Secret-bearing files use mode 0600. The native Windows foreground options are listed in the [Windows CLI reference](/docs/windows-cli). ## Per-instance launch configuration ```bash aircode start --projects-root /srv/workspaces --host 127.0.0.1 --port 7860 \ --password-file ~/.config/aircode/workspaces-password ``` Dedicated flags cover root, name, host, port, public URL, every password input mode, and Relay. Any additional schema-backed value can be persisted with a repeatable `--set key=value`; values use JSON when valid: ```bash aircode start --set maxSessions=64 aircode start --set workflows.maxConcurrent=2 aircode start --set 'browserAllowedHosts=["docs.example.com","*.internal.test"]' ``` Use `--json` for unattended provisioning. Its output never reveals an existing password; it includes a password only when this invocation generated it. ## Common settings | Env | config.json | Default | Purpose | |---|---|---|---| | `AIRCODE_HOST` | `host` | Tailscale IP, else `127.0.0.1` | Bind address (never `0.0.0.0` by default) | | `AIRCODE_PORT` | `port` | `7860` (auto-scan for a new instance) | Port. A persisted or explicitly set busy port fails instead of moving | | `AIRCODE_AUTH_PASSWORD` | `authPassword` | generated | Access password. Empty string disables auth (dev only) | | `AIRCODE_PROJECTS_ROOT` | `projectsRoot` | launch cwd | Directory whose subfolders are selectable projects | | `AIRCODE_MAX_SESSIONS` | `maxSessions` | `32` | Concurrent session cap | | `AIRCODE_SESSION_IDLE_TTL_MS` | `sessionIdleTtlMs` | `21600000` | Hibernate completed sessions after 6 idle hours; `0` also disables capacity eviction | | `AIRCODE_DEFAULT_PERMISSION_MODE` | `defaultPermissionMode` | `bypass` | Default agent permission mode | | `AIRCODE_DEFAULT_EXECUTION_MODE` | `defaultExecutionMode` | `local` | Default session environment (`local` or `docker`) | | `AIRCODE_DOCKER_ENABLED` | `dockerEnabled` | `true` | Expose the optional Docker execution backend | | `AIRCODE_DOCKER_IMAGE` | `dockerImage` | `aircode-session-runtime:local` | Optional Docker worker image | | `AIRCODE_DOCKER_NETWORK` | `dockerNetwork` | `aircode-runtime` | Dedicated worker bridge | | `AIRCODE_DOCKER_CONTROL_PORT` | `dockerControlPort` | AirCode Ø port + 1000 | Stable capability-scoped worker callbacks | | — | `updateChannel` | `stable` | Release channel (`stable` / `beta`) | Session hibernation stops only detached runtimes whose latest turn has a durable completion marker and a resumable transcript. Sessions that are working, awaiting input, remotely controlled, or owned by a workflow stay running. At the session cap, AirCode Ø hibernates the oldest eligible runtime immediately. The conversation remains in History and resumes normally. Engine binaries (`claudeBin`, `codexBin`, …) and per-feature settings (browser, knowledge, orchestration, token optimization) are documented in the in-product settings and accept the same env/file precedence. See [Docker sandbox](/docs/docker-sandbox) for worker limits, networking and the complete Docker configuration. ## Files & directories | Path | Contents | |---|---| | `~/.aircode/versions/` | the product (immutable, checksummed) | | `~/.aircode/current` | symlink to the active version | | `~/.config/aircode/config.json` | global user defaults | | `~/.config/aircode/instances/.json` | per-instance config, including its password (0600) | | `~/.config/aircode/providers.env` | provider credentials shared by the Unix user (0600) | | `~/.config/systemd/user/aircode@.service` | one generated systemd template for every managed instance | | `~/.local/state/aircode/instances//` | isolated runtime, tunnel, and session state | | `~/.local/state/aircode/relay-account.json` | managed Relay account credential shared by the Unix user (0600) | Updates never touch instance config or state. ## Multiple instances The canonical root determines the default identity. Different roots naturally produce different instances; `--name` disambiguates multiple instances for one root. Each receives its own port, password/no-password policy, tmux namespace, state, and tunnel identity. ```bash aircode start --projects-root /srv/acme --name staff --port 7860 --generate-password aircode start --projects-root /srv/acme --name demo --host 127.0.0.1 --port 7861 --no-password --relay private aircode ps -a ``` Lifecycle commands accept the name, full or short ID, canonical root, or the current directory. A root selector is intentionally rejected as ambiguous when several named instances use it; select the name or ID. ## Multi-user machines Each OS user owns a separate registry, global defaults, provider credentials, managed Relay authorization, and set of systemd user services. Within that user, every path-scoped instance still has its own port, password, state, and sessions. `aircode start` creates and enables the correct template instance; the older `aircode service install` command is retained only for compatibility. ## License ```bash aircode license activate aircode license status aircode license deactivate # frees the seat for another machine ``` Offline machines stay fully functional (30-day validation grace; perpetual licenses never phone home). See [pricing](/pricing) for what restricted mode means — short version: launching new sessions pauses, nothing else. --- Source: https://aircodezero.com/docs/docker-sandbox # Docker sandbox > Preview: Codex sessions on Linux and macOS are the first certified path. > Local execution remains available and stays the default. Audited cycles keep > their existing Local runtime during this preview. AirCode Ø keeps its control plane on your computer or server. Choosing **Docker sandbox** moves only that direct agent session into a short-lived worker container. This is designed for development with bypass permissions: the agent can write to the selected cloned project without receiving the rest of your home directory or the Docker socket. ```text Web / Desktop / mobile ↓ AirCode Ø API + auth + tunnel (host) ↓ Local tmux / Windows ConPTY OR one Docker worker per session ``` This is an execution option, not a second containerized AirCode Ø installation. Authentication, session state, notifications, WebSockets, remote access and the UI stay on the host. ## Where the choice appears - **Web:** New session → **Execution environment**. - **Desktop:** the same launcher, because Desktop embeds the web workbench. - **Native mobile:** New session → **Execution environment**, persisted per saved server connection. - **API:** `POST /api/sessions` accepts `executionMode: "local" | "docker"`. Session details show the effective environment and image. The server validates the daemon, image and engine; it never silently falls back to Local. ## Use the source preview today Docker Engine or Docker Desktop must be running: ```bash git clone https://github.com/spartaquant/aircode.git cd aircode npm run docker:runtime:build npm run docker:runtime:check npm run docker:session:smoke ``` Restart AirCode Ø, create a session, then choose **Docker sandbox** under **Execution environment**. If Docker or the image is unavailable, the launcher shows the exact reason and does not fall back to Local. During the preview the image tag is `aircode-session-runtime:local`. Tagged product releases build and verify both `linux/amd64` and `linux/arm64`, publish an immutable Docker Hub version tag, and record its digest in the signed AirCode Ø release manifest. The separate manual Docker workflow publishes only the `preview` channel. The public repository and release credentials are configured, but a tag becomes pullable only after its first successful publication. When a public tag is available, no AirCode Ø rebuild will be required: ```bash AIRCODE_DOCKER_IMAGE=aircodezero/session-runtime: aircode ``` ## What the worker can access AirCode Ø does not copy the repository into the image. It mounts the already cloned project at the **same absolute path** inside the worker. Edits therefore land in the real checkout and remain after the container exits. | Mount | Access | Why | |---|---|---| | Selected project | Read-write | Code, files and Git checkout | | Selected engine profile | Read-write | Authentication, history and native resume | | Per-session `/home/aircode` | Read-write | Private temporary home | | `/run/aircode` launch contract | Read-only | Command and initial prompt | | `/tmp` | In-memory read-write | Temporary files | It does **not** receive your complete home directory, the Docker socket or host Linux capabilities. The root filesystem is read-only, and AirCode Ø applies PID, memory and CPU controls. AirCode Ø's loopback API remains private. A stable, capability-protected control port accepts only the worker's own completion hooks, including after the Desktop server restarts. The engine profile contains credentials and is shared read-write in the preview. Container isolation protects unrelated host files; it does not make engine credentials unreadable to the engine or to malicious code running as the same container user. ## What happens during a session 1. The client reads the runtime catalog from the host. 2. The host validates Docker, the image contract and the selected engine. 3. AirCode Ø creates one labelled worker and a capability-scoped callback URL. 4. The worker starts tmux in the mounted project. 5. The existing terminal WebSocket drives that tmux session through Docker. 6. Normal exit or Kill session removes the worker, but leaves project changes and engine history on the host. 7. If AirCode Ø restarts, it rediscovers the worker and restores completion callbacks. ## VPN and dev-server traffic Container egress normally follows Docker's host routing and NAT rules. A host VPN carries it only when that VPN includes Docker bridge traffic. The managed Hetzner/AirCode Ø tunnel is for inbound access to the AirCode Ø control plane; it does not automatically route worker egress. | Traffic | Route | |---|---| | Phone/browser → AirCode Ø | LAN, VPN or Hetzner tunnel → host | | Terminal → worker | host → Docker API → worker tmux | | Agent API/Git traffic | Docker bridge/NAT → host VPN policy → Internet | | Worker completion hook | worker → protected host callback port | No worker port is published yet. Authenticated dev-server forwarding through AirCode Ø and the Hetzner route is the next networking increment. Local AirCode Ø sessions and existing remote access continue to work unchanged. ## Preview limits - Codex is the first certified engine. The image contains the other pinned CLIs for compatibility testing, but the launcher keeps them unavailable in Docker. - Direct sessions are covered. Audited cycles continue to use Local execution. - Linux and macOS hosts are targeted. Windows Desktop keeps native ConPTY Local sessions; Docker path semantics are not certified there yet. - Host browser capabilities and external token-optimization wrappers are not injected into workers yet. ## Configuration | Environment variable | `config.json` | Default | Purpose | |---|---|---|---| | `AIRCODE_DEFAULT_EXECUTION_MODE` | `defaultExecutionMode` | `local` | Default launcher environment | | `AIRCODE_DOCKER_ENABLED` | `dockerEnabled` | `true` | Docker feature switch | | `AIRCODE_DOCKER_BIN` | `dockerBin` | `docker` | Docker CLI | | `AIRCODE_DOCKER_IMAGE` | `dockerImage` | `aircode-session-runtime:local` | Worker image | | `AIRCODE_DOCKER_NETWORK` | `dockerNetwork` | `aircode-runtime` | Dedicated bridge | | `AIRCODE_DOCKER_CONTROL_PORT` | `dockerControlPort` | AirCode Ø port + 1000 | Capability-scoped hook bridge | | `AIRCODE_DOCKER_ENGINES` | `dockerEngines` | `codex` | Fallback certified-engine list | | `AIRCODE_DOCKER_PIDS_LIMIT` | `dockerPidsLimit` | `512` | Per-worker PID limit | | `AIRCODE_DOCKER_MEMORY` | `dockerMemory` | `8g` | Per-worker memory limit | | `AIRCODE_DOCKER_CPUS` | `dockerCpus` | unset | Optional CPU limit | Keep the callback port stable while workers are running. See [Troubleshooting](/docs/troubleshooting) for the runtime error codes. The Docker daemon remains a trusted host component. Anyone who controls that daemon can bypass container isolation. --- Source: https://aircodezero.com/docs/focus-plans # Focus Plans A Focus Plan is a small execution workspace: today's priorities, a release checklist, a hotfix, one bounded investigation. It lives on your AirCode Ø server, not in your repository, so it never adds noise to `TODO.md` and never shows up in a diff. A project can have several active at once. Open them from **Focus Plans** in the activity bar, or from **More → Focus Plans** on mobile. | Space | Lives in | Use it for | | --- | --- | --- | | Project TODO | Your repository's `TODO.md` | Durable direction that contributors should see and review with the code | | **Focus Plan** | **The AirCode Ø server** | **Today, a sprint, a hotfix, an investigation — bounded work** | | Session plan | One agent conversation | That agent's internal steps for the current request | ## Keep one plan at the top Open a plan's actions and choose **Pin Focus Plan**. The plan gains a visible **Pinned** marker and stays above every unpinned plan, even when you change the sort to oldest update, completion, or title. Choose **Unpin Focus Plan** to put it back under the ordinary sort. On desktop and responsive web, **Overview** places pinned plans in one section before the priority groups. The native app keeps the same plans at the top of its gallery. The action and result are the same on all three surfaces. Restore an archived plan before changing its pin. ## Build one from a conversation The fastest way to create a Focus Plan is to stop writing it yourself. At the end of a working session, when the agent already knows what is left to do, just ask: > **you** — Now create a Focus Plan based on this discussion. The agent creates the plan in the current project, seeded with the tasks it just worked out with you, and answers with the plan id. Refresh the Focus Plans view and it is there, with `created by agent` in its activity history. From then on, in that conversation or any other session on the same project: > **you** — Add "Rebuild the Android APK" to the Android rollout plan. > > **you** — Mark the Store listing task as done, and note that the screenshots > are still missing. > > **you** — Reorder the plan so the signature check comes first. > > **you** — Rename it to "Android rollout", then archive it, we shipped. You never have to name the plan by id. Asking by title is enough — the agent lists the plans of the project and picks the one you mean. ### What an agent can do | Ask for | What happens | | --- | --- | | Create a plan | A new active plan in the session's project, with the tasks you agreed on | | Add, update, delete, reorder tasks | Task-level edits, including status, notes, and due dates | | Rename, describe | Plan-level edits | | Pin or unpin | Keep the plan at the top, or return it to the ordinary sort | | Complete, archive, reopen | Lifecycle changes; completing does not archive, archiving does not delete | | Delete permanently | Only after the plan is archived, and only when you ask for that exact plan | ### What an agent will not do - **Nothing happens unless you ask.** An agent never turns its internal session plan into a Focus Plan on its own, and never tidies up your plans in the background. - **It cannot leave the project.** A session only reaches the Focus Plans of its own project. - **It cannot cross into your repository.** Focus Plan tools never write `TODO.md`; the Project TODO remains human-owned. - **Archived plans are read-only** until you reopen them, and permanent deletion requires the plan to be archived first. - **It cannot silently overwrite you.** Every agent edit is attributed to its session in the plan's activity history, and an edit based on a stale version is rejected rather than applied on top of a newer one of yours. Focus Plan tools are available in local sessions. A session running in the Docker sandbox receives no Focus Plan tools, so a plan cannot be attached to one; resuming an older session in the sandbox detaches its stale plan scope instead of failing. ## Attach a screenshot to a task A task can carry one or several files — a screenshot is the common case. Use **Attach a file** next to the task, paste a screenshot straight into the field, or drop the file on it; on the phone app, the **+** button in the task dialog offers Photos, Files, and Camera. Attachments appear as chips, images open full screen, and everything stays with the task after a reload. Attaching an image here works exactly like attaching one to a prompt: the file is uploaded next to your project, outside the repository, and travels with the task. When you start a session on that task, the agent receives the screenshot with it — nothing else to do. ## Run a task The play control on a task opens a short run panel — engine, model, effort, permissions — and starts. There is no prompt to write: the task title and its notes are the prompt. Choose **Edit the prompt…** when you want the full launch form instead, to amend the scope or attach a file. These sessions run on the local runtime. Starting a task moves it to **In progress** on its own. A task you already marked blocked, done, or in progress keeps the state you gave it. While a session is running on a task, the row shows what it is doing — waiting for you, working, or finished — and the same control opens that session instead of starting a second one. Agents on the same project share one working copy, so running several tasks at once means they will overwrite each other; the panel says so before you start. When the session finishes its turn, the row offers **Mark complete**. It is an offer: an agent stopping is not proof the work is done, so the plan waits for your click. **Start session** on the plan itself runs one session on the whole plan, with its remaining tasks in order. It does not fan out into one session per task. The conversation shows a compact Focus Plan banner while it carries that work. It gives you the real completed-task count, one segment per task, the current or next task, and an honest state such as **Working**, **Needs your input**, **Blocked**, **Paused**, or **Complete**. Open the banner to review every task or jump to the full plan. It follows saved task changes while the agent works; finishing a turn pauses the banner but never marks the work complete. The **Unprompted plan updates** switch is about initiative, not permission: turn it on and the agent also records verified progress on the attached scope as it works, without being asked each time. Leave it off and the plan changes only when you ask. ## Sync progress with AI Use the prominent **Sync with AI** action when work may have landed outside the Focus Plan. Choose an engine and model; AirCode Ø then runs a read-only background analysis of the unfinished tasks against the repository, its tests, and relevant Git evidence. The result is a review, not an automatic edit. Each task receives a supported verdict such as likely done, partial, no trace, or not verifiable. AirCode Ø marks nothing complete until you accept the suggestion. The analysis appears under **Runs**, never as a conversation in **Sessions**, and it can be cancelled from the Focus Plan while it is active. ## Where the data lives Focus Plans are stored in SQLite inside your AirCode Ø state directory, on the machine running the server. They are never written into your project directory, committed to Git, or embedded in `TODO.md`. Include the state directory in your server backup to keep them. --- Source: https://aircodezero.com/docs/install # Install ## Web app for iPhone, iPad and Android The AirCode Ø web app is the permanent no-store client. It is served by your own running instance, not packaged in the signed download channel, so it remains available independently of Google Play, the App Store and TestFlight. 1. Start an AirCode Ø instance and give it a stable HTTPS address. See [Quickstart](/docs/quickstart) and [Remote Access](/docs/remote-access). 2. Open the dedicated [Web app setup](/web-app), enter the HTTPS instance origin, and select **Open install page**. The same setup is also available on [Download](/download#web-app). The website validates the value locally and never submits or stores it. 3. Sign in on the instance. Chromium exposes its own install prompt; iPhone and iPad use the guided **Share → Add to Home Screen** flow. 4. Launch the installed icon. Enable **Push notifications** only if you want closed-app alerts; iOS and iPadOS offer that control only from the Home Screen web app. Installation is origin-scoped. An HTTP LAN address is not a complete mobile PWA target, and changing the HTTPS origin requires a separate installation and may require authentication again. The web app updates from the instance through its service worker; it is not an APK, IPA, or Store package. The web app carries the responsive core workspace. Native mobile remains the better choice when you need secure in-app server switching, encrypted managed offline files, reliable bounded background tasks, native sharing/scanning, haptics, notification-channel priority, or operating-system deep links. ## Linux server (beta) The installer is published only when the matching beta manifest, checksums and signing checks have passed. Confirm that the server build is marked available on the [download page](/download) before running the command. ```bash curl -fsSL https://dl.aircodezero.com/install.sh | bash -s -- --channel beta ``` `--channel beta` is required while the product is in private beta: the installer targets the `stable` channel by default, and that channel has no published manifest yet. The installer: 1. detects your platform (`linux-x64`, `linux-arm64`), 2. downloads the release tarball and verifies its **sha256 checksum and Ed25519 signature**, 3. installs to `~/.aircode/versions/` with a `~/.aircode/current` symlink, 4. links `aircode` into `~/.local/bin`, 5. checks system dependencies (`git`, `tmux`) and tells you exactly what to install if something is missing. No root required. Everything lives in your home directory. Re-running the installer repairs or updates the installation. If `~/.local/bin` is not in the current shell's `PATH`, the final instructions use the immediately runnable `~/.aircode/current/bin/aircode` path and also show the optional `PATH` export. Then start the instance from the directory that should contain the selectable projects: ```bash cd /srv/workspaces aircode start ``` On Linux, `aircode start` creates the path-scoped configuration, chooses a free port, generates and prints an access password, starts a systemd user service, and enables recovery after crashes and reboots. It also attempts to enable systemd linger and prints one exact repair command if the host requires administrator approval. No hand-written unit file is needed. If this installation still owns the historical `aircode.service` unit, the first `aircode start` safely removes that unit before creating the path-scoped service, so both servers cannot compete for the same port. Units owned by a different checkout or installation are never modified. Installer options: `--channel beta` (or `stable`, the default, once the first stable release is published), `--version x.y.z`, and `--no-modify-path`. The historical `--service` option remains compatible with the old single-instance unit; new installations should use `aircode start`. ### System requirements | Requirement | Notes | |---|---| | Linux x64 / arm64 | Ubuntu 22.04+, Debian 12+, Fedora (recent) — glibc | | `git`, `tmux` | `sudo apt-get install -y git tmux` | | Agent CLIs | Installed and authenticated with **your** accounts; use the [tested versions in Quickstart](/docs/quickstart#7-install-the-agent-clis) for version-sensitive TUI integrations | | Docker (optional) | Docker Engine or Docker Desktop for per-session sandbox execution | | systemd | required for persistent `aircode start`; not required for foreground `aircode serve` | The Node.js runtime is embedded — no system Node required. ## macOS **Desktop app** (recommended): download the `.dmg` from the [download page](/download). It embeds the full server (local sessions) and connects to remote servers too. **Headless / CLI**: the same one-liner as Linux works on macOS 13+ (`darwin-arm64`, `darwin-x64`). Install `tmux` via Homebrew first, then use `aircode serve`; persistent `aircode start` currently requires Linux systemd. ## Windows Install the desktop app from the [download page](/download). Local sessions run natively on Windows through **ConPTY**. The installer embeds the server and its Node.js runtime, so WSL, a Linux distribution, and a system Node installation are not required. It also adds `aircode` to the current user's `PATH`, allowing the embedded server to run from PowerShell or Command Prompt. Open a new terminal after setup and see the [Windows command-line reference](/docs/windows-cli). The app can also connect to remote AirCode Ø servers. ### Desktop operating modes | Goal | Start | Result | |---|---|---| | Remote client only | Open AirCode Ø and choose **Connect to a server** without configuring Local | The Electron window loads the selected remote AirCode Ø server | | Local client + server | Open AirCode Ø, choose a projects folder, then choose **Run on this machine** | Electron starts and manages the embedded native server, then loads its workspace | | Server only | Run `aircode serve --projects-root "D:\Projects"` in PowerShell or Command Prompt | The bundled server runs in the foreground without an Electron window | Do not launch the desktop executable with `--serve`: it opens Electron and does not provide a server-only mode. Use the `aircode` command installed in `PATH` for a terminal-only server. After Local has been configured, normal desktop startup ensures that the embedded server is running, even while a window is connected to a remote server. The desktop-managed server listens only on `127.0.0.1`. To connect from another device, use [Remote access](/docs/remote-access) or start a CLI server with an explicit private-network bind address. On Windows, closing every desktop window leaves AirCode Ø and its embedded server running in the notification area. Choose **Quit** from that icon to stop the embedded server. A CLI server is a separate process and can coexist on a different port; see [Desktop app coexistence](/docs/windows-cli#desktop-app-coexistence). ## Heavy optional components Nothing big is downloaded implicitly: ```bash aircode browser install # pinned Chromium for the agent browser aircode doctor browser # verify it ``` Knowledge remains lexical until you explicitly install a local embeddings model from its settings. The model is then verified and used offline; indexing never triggers an implicit model download. The Docker session image is also opt-in. During the source preview, build and validate it from the checkout: ```bash npm run docker:runtime:build npm run docker:session:smoke ``` See [Docker sandbox](/docs/docker-sandbox) for Web/Desktop/mobile selection, mounts, resource limits and the public-image status. --- Source: https://aircodezero.com/docs/quickstart # Quickstart ## 1. Start a persistent Linux instance ```bash cd /srv/workspaces aircode start ``` First run output: ``` Generated password: k6uu3U6UG8AXgTnvNgNj Started workspaces (1a2b3c4d5e6f) on http://127.0.0.1:7860. Service: aircode@workspaces-1a2b3c4d5e6f.service — restart after crashes and automatic boot: enabled. ``` AirCode Ø picks a free port automatically (7860 upward), generates an access password, and saves a private instance config outside the projects directory. If Tailscale is present, it binds your tailnet IP so your phone can reach it; otherwise it binds localhost only. `aircode start` is persistent by default on Linux: systemd restarts the server after a crash and starts it again at boot. For a foreground process tied to the current terminal, use `aircode serve` instead. ## 2. Open the cockpit Open the printed URL on any device that can reach the machine and log in with the password. For an installable phone or tablet client, first give the instance a stable HTTPS address through [Remote Access](/docs/remote-access), then choose one of these paths: - On [Web app setup](/web-app), enter only that HTTPS instance address and select **Open install page**. The same setup remains available on [Download](/download#web-app). The address is validated in your browser and is never sent to or stored by the marketing website. - On the instance itself, sign in and choose **Install web app** from **More** or **Settings → Web app**. - In Chromium, accept the browser-owned install prompt. On iPhone or iPad, follow the in-product guide to choose **Share → Add to Home Screen**, keep **Open as Web App** enabled when shown, and launch the new Home Screen icon. The web app is always available as the store-free client; it does not depend on Google Play, the App Store, TestFlight, or the downloadable release manifest. Plain HTTP LAN addresses can still open in a browser where allowed, but mobile installation, service workers and Web Push require HTTPS. ## 3. Launch your first session 1. Pick a project directory. 2. Pick the execution environment: - **Local** runs through host tmux or Windows ConPTY. - **Docker sandbox** runs one supported direct session in a worker while mounting the selected project. 3. Pick an engine (Claude Code, Codex, Antigravity CLI, Grok Build, Kimi Code) and a permission mode. 4. Launch. The agent keeps running on the AirCode Ø machine — close the tab, take the train, come back: it's still working. Notifications ping you when the agent needs input or finishes. The Web, Desktop and native mobile launchers expose the same environment choice. Docker is currently a Codex source preview; see [Docker sandbox](/docs/docker-sandbox) before enabling bypass permissions. ## 4. Operate it like a container ```bash aircode ps # running instances aircode ps -a # include stopped instances aircode inspect # current directory's instance aircode logs --follow aircode restart aircode stop # stop now and disable startup at boot ``` Run lifecycle commands from the instance root, or pass its name, full ID, short ID, or root path. Stopping or updating the server **never kills running tmux sessions**. A whole-machine reboot necessarily stops operating-system processes, but durable session history remains available for recovery. ## 5. Choose ports, passwords, and multiple roots Every value can be supplied on the launch command: ```bash aircode start --projects-root /srv/client-a --port 7860 --generate-password aircode start --projects-root /srv/client-b --port 7861 --password-file ~/.secrets/client-b aircode start --projects-root /srv/demo --host 127.0.0.1 --port 7862 --no-password --relay private ``` Password files must be private (`chmod 600`). `--password-stdin` is available for provisioning systems. `--password ` works too, but exposes the secret to shell history and potentially process inspection. Disabling auth must be explicit with `--no-password`. One canonical path normally maps to one instance. Add names when the same path needs several independent ports or policies: ```bash aircode start --projects-root /srv/client-a --name internal --port 7860 --generate-password aircode start --projects-root /srv/client-a --name demo --host 127.0.0.1 --port 7861 --no-password --relay private ``` ## 6. Sign in before using managed Relay ```bash aircode login aircode whoami aircode start --relay managed ``` The account authorization is shared by this Unix user's instances. Ports, passwords, tunnel identities, and Relay enabled state remain isolated per instance. Managed Relay refuses an instance without a password. Use `aircode logout` to remove the machine credential, `--relay private` for a VPN without a public tunnel, or `--relay off` to disable a tunnel without forgetting its selected transport. ## 7. Install the agent CLIs Packaged AirCode Ø releases already carry their pinned CLIs. For a source checkout, install and authenticate the CLIs under the same Unix user. The current distribution targets are: ```bash curl -fsSL https://claude.ai/install.sh | bash -s 2.1.211 claude curl -fsSL https://chatgpt.com/codex/install.sh | bash -s -- --release 0.153.4 codex login curl -fsSL https://x.ai/cli/install.sh | bash -s 0.2.118 grok login curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash -s -- --version 0.32.0 kimi login ``` An older or unreadable version is blocked. A newer release can launch and keeps screen-verified controls enabled unless a known incompatibility is recorded. `aircode doctor` commands report what is available. Antigravity CLI uses Google’s official installer: ```bash curl -fsSL https://antigravity.google/cli/install.sh | bash agy ``` The native engines keep their vendor authentication model: | Engine | Account/subscription path | API path | |---|---|---| | Claude Code | Claude plan sign-in | Anthropic API credentials | | Codex | ChatGPT plan sign-in | OpenAI API credentials | | Antigravity CLI | Google account with baseline access, Google AI Pro/Ultra, or a Google Cloud project | Native account catalogue | | Grok Build | `grok login` with a Grok subscription or enterprise SSO | `XAI_API_KEY` | | Kimi Code | Kimi Code OAuth | Moonshot API credentials | Google retired Gemini CLI for individual accounts on June 18, 2026 and directs that plan at the Antigravity suite. AirCode Ø follows that move: Antigravity CLI is the Google engine, and Gemini CLI can no longer be selected. Sessions created with it before the change stay readable in History. See the [official transition notice](https://github.com/google-gemini/gemini-cli/discussions/28017). An Antigravity subscription is not required to connect: a Google account gets the baseline plan, while Pro and Ultra increase model access and limits. AirCode Ø uses the account-specific catalogue returned by `agy models`. See Google’s [Antigravity plans](https://antigravity.google/docs/plans?app=cli) and [model catalogue](https://antigravity.google/docs/models?app=antigravity). For bundled API provider profiles, open **AI Usage**, choose the provider, and click **Connect**. AirCode Ø validates the submitted key, stores it locally in `~/.config/aircode/providers.env` with private permissions, and activates it for new sessions immediately. The same screen can replace or disconnect keys. Keys supplied through the server environment remain supported and are shown as externally managed instead of being editable in the UI. DeepSeek, Z.AI and MiniMax are selectable as Claude Code provider profiles; Meta Muse Spark is selectable through Kimi Code. DeepSeek uses `DEEPSEEK_API_KEY`; Z.AI uses a GLM Coding Plan key in `ZAI_API_KEY`; MiniMax accepts either a Token Plan Subscription Key or a pay-as-you-go key in `MINIMAX_API_KEY`; Meta uses `META_MODEL_API_KEY` and also recognizes Meta's official `MODEL_API_KEY` alias. AirCode Ø injects each credential only into a session using that provider and never returns it through the API. Grok uses a stored browser session before `XAI_API_KEY`; run `grok logout` when you explicitly want the API-key path. --- Source: https://aircodezero.com/docs/remote-access # Remote access AirCode Ø is built to be driven from anywhere while the application, projects, terminals and agents keep running on your own machine. **Never expose it unauthenticated to the public internet.** ## Built-in Remote Access The desktop app and server include an outbound-only tunnel. You do not need to open a router port or configure dynamic DNS: 1. Open **Remote access** from the broadcast icon in the activity bar. 2. Open **Settings** and select **AirCode Ø relay**. 3. Return to Remote access and enable the tunnel. 4. Open the generated HTTPS URL, or select **Generate pairing QR code** and scan it from **Add server → Scan QR code** on the mobile app. Your AirCode Ø password still protects the server, and the application, projects and sessions remain on the host machine. The tunnel refuses to start if no password is configured. A pairing QR never contains that password: it contains a random capability that expires after five minutes and stops working after its first successful scan. The server then gives that phone its own revocable token. ### If Windows Security blocks the desktop tunnel AirCode Ø bundles `frpc.exe`, the client that creates the outbound tunnel. A Windows Security policy may classify proxy software as a potentially unwanted application and refuse to start it. When AirCode Ø detects that refusal, Remote access shows **The tunnel client could not start** with **View instructions** and **Retry** actions. 1. Select **View instructions**, then **Open Windows Security**. 2. Open **Virus & threat protection → Protection history** and expand the latest potentially unwanted app entry. 3. Verify that the affected file belongs to the AirCode Ø installation you trust and that its path ends with `\resources\payload\vendor\frpc\frpc.exe`. A matching detection may be named `PUA:Win32/FRProxy`. 4. Only when both the installation and path match, choose **Allow on device**. 5. Return to AirCode Ø and select **Retry**. If the detection name or file path differs, leave the item blocked and inspect it separately. Never disable Windows Security or add an exclusion for the whole AirCode Ø installation folder. Microsoft explains the available actions in its [Protection history guidance](https://support.microsoft.com/en-us/windows/security/windows-security-protection-history-in-the-windows-security-app). ### Command-line setup on a Linux server Authorize the managed Relay account before requesting it for an instance: ```bash aircode login aircode whoami aircode start --projects-root /srv/workspaces --relay managed --generate-password ``` The device authorization is shared by all AirCode Ø instances owned by the same Unix user. Each instance keeps its own port, password, public slug, tunnel identity, and enabled state. First service boot completes enrollment and the tunnel reconnects with the server after crashes and reboots. Managed Relay refuses to start when the account is signed out, the account has no active entitlement, or the instance has no password. `aircode logout` removes the shared machine credential. Existing managed connections can remain alive until their current lease expires, so stop or switch those instances to private mode before signing out when immediate disconnection matters. Other launch choices are explicit: ```bash aircode start --relay private aircode start --relay custom --relay-url https://relay.example.com \ --relay-enrollment-token-file /run/secrets/aircode-relay-enrollment aircode start --relay off ``` `private` selects VPN/private-network-only mode. `off` closes the public tunnel without forgetting a previously selected managed or custom transport. ### Managed-relay trust boundary The current managed relay terminates TLS before forwarding authenticated traffic to your AirCode Ø host. Relay operators can therefore technically access traffic while it passes through the relay, even though application and session data are not stored there as part of the normal service. End-to-end encryption that prevents relay inspection is not yet available. Use **Private network only** with Tailscale or another VPN, or operate a custom relay, when that trust boundary does not meet your requirements. ### Fast preview and Shared browser The Browser screen offers two different transports: - **Fast preview** is the default for a localhost development URL. The page, assets, live reload and HMR WebSockets render directly on the viewing device; Chromium does not run on the server. With Remote Access enabled, AirCode Ø creates a revocable preview origin through the relay. Opening it once exchanges the access link for an HttpOnly cookie. You can add a per-preview password as a second layer; the private link is still required, the password is stored only as a local hash, and failed attempts are rate-limited. - **Shared browser** runs Chromium on the server and streams the same interactive session to people and agents. Choose it when an agent needs browser control, diagnostics, screenshots or traces. Closing a Fast preview revokes its relay origin. Preview links are access capabilities even when a password is enabled: do not publish them. ## Tailscale or your own VPN For peer-to-peer access without the AirCode Ø relay, open **Remote access → Settings** and select **Private network only**. AirCode Ø then opens no public tunnel. Install [Tailscale](https://tailscale.com) on the server and your devices. AirCode Ø detects and binds the tailnet IP automatically; open `http://:7860` from another device. `tailscale serve` can add HTTPS with a real certificate: ```bash sudo tailscale serve --bg "http://$(tailscale ip -4):7860" ``` The first run may print an admin-console link to enable Serve/HTTPS on your tailnet (a one-time, one-click switch). WireGuard, ZeroTier or an SSH tunnel (`ssh -L 7860:localhost:7860 server`) work the same way. ## Custom relay Organizations can run the three-container Caddy + FRP + registry appliance from the AirCode Ø source tree. It keeps application and project data on employee hosts, closes new-machine enrollment by default, and gives every instance an independent tunnel identity. On the Relay host: ```bash cd infra/relay ./aircode-relay init \ --control-domain relay.example.com \ --tunnel-domain tunnel.example.com \ --tls-mode provided # Add a certificate covering relay.example.com and *.tunnel.example.com: # tls/fullchain.pem # tls/privkey.pem ./aircode-relay up ./aircode-relay doctor ./aircode-relay enrollment create --expires 15m --uses 1 --label alice-laptop ``` Put the displayed one-time value in a mode-`0600` file on the AirCode Ø host, then use the custom Relay command shown above. AirCode Ø consumes and removes the pending token after successful enrollment. FRPS requires TLS, and the host pins the appliance's internal CA and server name from the authenticated HTTPS enrollment response. Many employees can share one Relay. That does not turn one AirCode Ø instance into a safe multi-user server: use one Unix account, container, or VM and one instance per employee until same-instance roles and execution isolation are implemented. ## The desktop and mobile apps Add a connection manually with the server URL + password, or generate a single-use QR code under **Settings → Security & access → Pair a phone**. Connections and paired-device tokens are stored in the platform's secure credential storage. The desktop app shows the active server in the bottom-left status indicator — click it to switch or add servers (local or remote). The native mobile app is only a client of an already reachable server address. It does not configure the built-in tunnel, manage an AirCode Ø account for the managed Relay, or enable and disable public exposure. Those host-side controls remain in the Web and Desktop server surfaces. The mobile address may be a LAN or VPN address, a user-operated reverse proxy, or a public Relay URL. A current QR imports that address and exchanges its one-time capability for a token owned by that phone; older address-only QR codes still fall back to password entry. Scanning a QR does not activate or require a Relay. Paired phones can be reviewed and revoked individually under **Security & access** on web, responsive web, or native mobile. Pairing uses HTTPS by default. When the server is already reachable through an HTTP LAN or private-VPN address, enable **Allow pairing on this local connection** before generating the QR. That choice applies only to the next five-minute code and turns itself off after generation. ## Install the web app from the HTTPS instance The store-free AirCode Ø client is installed from the exact HTTPS origin that serves your instance. Once the managed Relay, a custom Relay, or your own trusted HTTPS reverse proxy is active: 1. open the HTTPS instance address on the phone or tablet and sign in; 2. choose **Install web app** from **More** or **Settings → Web app**; 3. accept the Chromium install prompt, or follow the iPhone/iPad guide for **Share → Add to Home Screen**; 4. launch the installed icon, then enable **Push notifications** explicitly if you want closed-app alerts. On iPhone and iPad, Web Push is offered only after the web app is launched from its Home Screen icon. The browser and operating system remain responsible for installation and permission prompts. One installed web app belongs to one origin; moving an instance to a different origin means installing that new origin and signing in again. You can start the same handoff from the dedicated [Web app setup](/web-app) or from [Download](/download#web-app). The marketing site validates the address locally, discards credentials, paths, queries and fragments, and opens only `/?pwa-install=1` on the resulting HTTPS origin. It does not receive or retain the instance address. The responsive web app covers the core workspace, sessions, attention requests, terminal, files, Git, Browser/Applications, Focus Plans, Prompt Queue, Scheduled Tasks, settings and notification-device management. Native mobile still has deeper operating-system integration: encrypted local storage, secure multi-server profiles, bounded background work, native file/camera/share flows, haptics, notification channels and `aircode://` links. Browser storage can be evicted, background execution is not guaranteed, and iPhone/iPad installation is manual. ## Firewalls The built-in tunnel requires no inbound port. It makes outbound connections to the relay over HTTPS and FRP. Private-network mode opens no additional connection; your VPN determines reachability. Fast previews use authenticated ephemeral ports on the same interface as the AirCode Ø server, so a restrictive host firewall must allow traffic from the private network to those ports. For a [Docker sandbox](/docs/docker-sandbox) session, this route still ends at the host AirCode Ø control plane. Terminal traffic then crosses the Docker API to the worker. The tunnel does not automatically carry worker egress and does not publish development ports from the container. --- Source: https://aircodezero.com/docs/remote-sessions # Remote sessions Save the URL and a scoped token from another personal AirCode Ø instance, then choose that connection in **New session → Run on**. The remote instance invokes its own connected Claude Code, Codex or another available native provider. Your current client displays the conversation and terminal as the remote agent works. The project, files, provider credentials, permissions and usage accounting belong to the **remote instance**. This feature does not synchronize projects or make remote models operate tools on your current computer. Both instances need a version supporting remote sessions, and the remote address must be reachable from your current instance's server. ## Connect your two instances 1. On the remote instance, connect your native provider account in **AI Usage**. 2. Open **Settings → AI gateway**, or **More → AI gateway** on mobile, and enable the gateway. Under **Application tokens**, enable **Allow remote sessions**, choose the **Authorized project**, name the token, and select **Create token**. 3. Copy the token from its one-time reveal and the displayed **Base URL**. 4. On your current instance, open **Settings → AI gateway → Remote instances**. Enter a connection name, the remote instance URL or Base URL, and the token; select **Save connection**. You can also add it from **New session → Run on → Add remote instance…**. 5. In **New session**, choose the saved connection under **Run on**. Check the remote project, choose **Engine → Model → Effort → Permissions**, enter your prompt, and select **Start session**. The token is sent once to your current server for encrypted storage. Web and native clients subsequently use a connection identifier. The website never receives the URL or token. Use HTTPS for Internet connections or your trusted private network. Gateway tokens are personal credentials, not share links. ## Work with the remote agent **Chat** shows new transcript events and current permission/question dialogs. Send follow-up messages, answer the dialog, or use **Stop** to interrupt the current response. **Terminal** attaches to the remote agent's actual terminal, including input and resize. A dialog that cannot be represented in Chat has an **Open Terminal to continue** action. The terminal transports live output over WebSocket. Chat uses a continuous SSE response and publishes events as the provider writes its transcript; some providers journal a completed message rather than every token. This does not promise token-level Chat updates for every CLI. The separate text-only `/chat/completions` API still uses **buffered** SSE. A network reconnection reads from the last transcript cursor. It never starts another agent or resends a message automatically. After an uncertain launch, reopen **New session**, select the connection and look under **Remote conversations**. An explicit retry of the same request keeps its intention id. Do not change the id just to bypass an uncertain result. Closing the view or removing its saved connection detaches the client and leaves the remote conversation in place. **Close session** stops its agent; its transcript remains on the remote instance and can be reopened for reading. Start a new session to run an agent again after closing it. Saved remote conversations are listed under their connection in New session. ## Scope, revocation and limits An ordinary application token grants text generation only. **Allow remote sessions** adds access to one project and only the conversations created with that token. It does not grant owner login, settings, other conversations, arbitrary owner file APIs or token management. The agent's tools use the permissions selected at launch; choosing a project is not an OS sandbox. Tokens expire after 90 days. Revoking the token or disabling the remote gateway removes its session authority and stops its agents. Expiry is checked on every operation and by a bounded background check, including after a server restart. Removing a saved connection does not revoke the original token: revoke it on the issuing instance if you want to withdraw authority. There are at most 20 active gateway sessions per issuing instance and four live readers per token, with 20 readers across the gateway. Each opened Chat uses one reader; opening Terminal can add another. These are local resource limits, not subscription allowances. Provider quotas and compatibility requirements still apply; no provider CLI is installed or updated by connecting. ## Session API for a personal backend or AI tool The [session OpenAPI schema](/docs/remote-sessions/openapi.json) describes this protocol. Use `Authorization: Bearer TOKEN` on every call to the issuing instance's `/api/ai-gateway/v1` Base URL. Refuse redirects; never use URL/query credentials. Do not put the token in prompts, source code or browser storage. | Endpoint | Meaning | | --- | --- | | `GET /session-catalog` | Protocol version, authorized project and live engine/model/effort/permission catalogue | | `POST /sessions` | Start a native agent; required UUID `intention`, `engine`, `model`, `permissionMode`, `initialPrompt`; optional `effort`, `name` | | `GET /sessions` | Up to 100 recent conversations created by this token | | `GET /sessions/{id}` | Current summary of a conversation owned by this token | | `POST /sessions/{id}/events` | Continuous SSE; JSON `{ "offset": null }` initially, then the last returned offset on reconnect | | `POST /sessions/{id}/transcript` | Read a named page; send `offset` for newer events or `before` for earlier messages | | `POST /sessions/{id}/input` | Follow-up: UUID `intention` and non-empty `text` | | `POST /sessions/{id}/interaction` | UUID `intention`, current dialog `id`, and the supported option/text/dismiss answer | | `POST /sessions/{id}/command` | UUID `intention` and `action`: `interrupt`, `close`, `model`, `effort` or `permission`; selection changes require `arg` from the catalogue | | `GET /sessions/{id}/terminal` | Authenticated WebSocket upgrade; raw terminal output, JSON input `{ "t": "i", "d": "text" }`, resize `{ "t": "r", "c": 100, "r": 30 }` | An SSE `update` contains `{ id, project, session, transcript }`. `transcript.events` is the new event page, `offset` is its opaque integer cursor, and `interaction` or `terminalAttention` describes current input needs. Keep complete SSE frames across network chunks. Ignore comment keepalives. On an `error` event, retain the conversation and reconnect for reading. Never infer that an operation did not happen from a lost network response. Permission modes, model ids and efforts come from `/session-catalog`, not the text API's composite model ids. Session creation and actions reject unsupported fields. Initial prompts and follow-ups are bounded to 64 KiB. Launch intentions are scoped to the token; action intentions are scoped to a session. Reusing an intention with different content is rejected. Once runtime delivery was attempted, an unknown result remains `AI_GATEWAY_SESSION_UNCERTAIN` and cannot be replayed automatically. For a quota wall, wait for the provider's actual recovery; an unknown reset time stays unknown. For `AI_GATEWAY_SESSION_ATTENTION`, answer the current dialog. For revoked/expired access, create a fresh scoped token on the issuing instance. For missing models, refresh the catalogue. Existing owner conversations cannot be adopted with a new token. ## Live session example ```javascript import { randomUUID } from 'node:crypto'; // Node.js 22+. Configure these privately on your application's server. const base = process.env.AIRCODE_REMOTE_BASE_URL; const token = process.env.AIRCODE_REMOTE_TOKEN; if (!base || !token) throw new Error('Configure the remote Base URL and token'); const url = new URL(base); if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== '/api/ai-gateway/v1') { throw new Error('Use the instance Base URL without a trailing slash'); } async function call(path, body, signal) { const response = await fetch(base + path, { method: body === undefined ? 'GET' : 'POST', redirect: 'error', signal, headers: { authorization: 'Bearer ' + token, 'content-type': 'application/json' }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); if (!response.ok) throw new Error('Remote request refused: HTTP ' + response.status); return response; } const catalog = await (await call('/session-catalog')).json(); const engine = process.env.AIRCODE_REMOTE_ENGINE; const model = process.env.AIRCODE_REMOTE_MODEL; const permissionMode = process.env.AIRCODE_REMOTE_PERMISSION; if (!engine || model === undefined || !permissionMode) { console.log(JSON.stringify(catalog, null, 2)); throw new Error('Choose engine, model and permissions from this catalogue'); } // Keep this intention for an explicit retry of this exact launch request. const intention = process.env.AIRCODE_REMOTE_INTENTION || randomUUID(); const session = await (await call('/sessions', { intention, engine, model, permissionMode, effort: process.env.AIRCODE_REMOTE_EFFORT || '', initialPrompt: 'Describe the project without changing its files.', })).json(); console.log('Remote conversation:', session.id); // Demonstrate a bounded live reader. Disconnecting leaves the agent running. const signal = AbortSignal.timeout(30_000); const stream = await call('/sessions/' + session.id + '/events', { offset: null }, signal); const decoder = new TextDecoder(); try { for await (const chunk of stream.body) process.stdout.write(decoder.decode(chunk, { stream: true })); } catch (error) { if (!signal.aborted) throw error; } ``` --- Source: https://aircodezero.com/docs/troubleshooting # Troubleshooting ## First reflexes ```bash aircode ps -a # every path-scoped instance and its boot state aircode inspect [instance] # root, port, auth, Relay, service and state paths aircode logs [instance] --follow aircode doctor packaging # install integrity: checksums, runtime, symlink aircode doctor browser # agent browser stack aircode doctor tokens # token-optimization stack ``` ## Common cases **I lost the password.** Run `aircode password reset [instance]`. A persistent Linux instance restarts automatically with the new password. **The URL doesn't load from my phone.** The server binds Tailscale-or-localhost by default. Check `aircode status` for the bound address; make sure the phone is on the same tailnet/VPN. See [Remote access](/docs/remote-access). **Port already in use.** AirCode Ø auto-scans from 7860 unless you pinned a port explicitly — a pinned busy port is an error by design. Unpin it or free it. **The instance restarts after a crash but not until I log in after reboot.** `aircode inspect` reports `Boot: after-login` when the unit is enabled but systemd linger is unavailable. Run the exact command printed by `aircode start`, normally `sudo loginctl enable-linger $USER`, then confirm that `loginctl show-user $USER --property=Linger` reports `Linger=yes`. **The service is in a restart loop.** Check `aircode logs ` first. A port pinned in the instance config may now be occupied, a projects root may no longer exist, or a required system dependency may be unavailable. Fix the reported cause and run `aircode restart `. **A root selector is ambiguous.** Several named instances use the same path. Run `aircode ps -a` and target the unique name or short ID. **Managed Relay says login is required.** Authorize the Unix user once with `aircode login`, verify with `aircode whoami`, and retry `aircode start --relay managed`. Managed Relay also requires a non-empty instance password. **`tmux` or `git` missing.** Install them (`sudo apt-get install -y git tmux`) and re-run. The installer and doctor list exactly what's missing. **An engine isn't detected.** The CLIs must be installed and authenticated for the same user that runs AirCode Ø. Try running `claude` (or `codex`, …) in a shell as that user first. **Docker sandbox is unavailable.** The launcher shows the authoritative reason. Start Docker, then run `npm run docker:runtime:build` for the current source preview or pull the configured public image once it exists. Confirm with `npm run docker:runtime:check`. **`DOCKER_ENGINE_UNAVAILABLE`.** The image is valid, but that engine is not certified in its `io.aircode.engines` label. Choose Codex in the current preview or switch the session to Local. **`DOCKER_CONTROL_UNAVAILABLE`.** The worker callback bridge could not bind its stable port. Free `AIRCODE_DOCKER_CONTROL_PORT` or configure another fixed port, then restart AirCode Ø before launching a new worker. **My VPN does not carry agent traffic.** Docker egress uses bridge/NAT traffic, which some split-tunnel VPNs exclude. Add the Docker subnet to the VPN/firewall policy. The managed AirCode Ø tunnel is inbound control-plane access, not an outbound worker VPN. **A dev server started in Docker is not reachable.** Worker ports are not published in the current preview. Authenticated forwarding through AirCode Ø and the Hetzner route is a planned networking increment. **Update says my version is too old.** Very old installs must reinstall via the one-liner instead of updating in place — the message says so explicitly. **The install looks corrupted.** `aircode doctor packaging` pinpoints altered files; re-run the installer to repair (`curl -fsSL …/install.sh | bash -s -- --channel beta`). Your config, sessions and data are untouched — they live outside the version directory. **Restricted mode (license).** `aircode license status` explains why. Activation takes effect immediately — no restart needed. ## Still stuck? Grab `aircode ps -a --json`, `aircode inspect --json`, `aircode doctor packaging --json`, and the last 100 log lines, then use the support channel supplied with your beta invitation. Review the output before sharing it; diagnostic logs are designed not to contain code or prompts. --- Source: https://aircodezero.com/docs/updates # Updates & channels ```bash aircode update --check # is there something new on my channel? aircode update # download, verify, install, switch aircode update --rollback # instant switch back to the previous version ``` ## What an update actually does 1. Fetches the **signed channel manifest** and verifies its Ed25519 signature — an unsigned or tampered manifest is rejected outright. 2. Downloads the release tarball, checks its sha256 (and signature). 3. Installs to `~/.aircode/versions/` **next to** the current version. 4. Atomically switches the `current` symlink. 5. Restarts every running managed instance owned by this packaged installation — **running agent sessions are not touched**. They live in tmux, and every `aircode@.service` unit uses `KillMode=process`: only each server process restarts. Stopped instances remain stopped and use the new version the next time they start. Because versions sit side by side, `--rollback` is a symlink switch, not a reinstall. The two most recent versions are kept; older ones are purged. ## Channels - **stable** — the default. - **beta** — earlier features, same signing and update mechanics: `aircode update --channel beta`, or set `"updateChannel": "beta"` in the config file. ## Update notifications The server checks its channel once a day and surfaces available updates in the UI and in lifecycle status. It **never applies updates by itself** — updating a server you operate is your call, always. Use `aircode ps -a` to inventory instances after an update. ## Data compatibility Data migrations are forward-only. Rolling back across a schema-changing release is flagged in the release notes — check them before `--rollback` after a major update. --- Source: https://aircodezero.com/docs/usage-accounting # Usage accounting Session statistics reconstruct activity from usage records retained by the agent CLIs. AirCode Ø does not re-tokenize your prompts, and this view is not a provider invoice or an account-quota meter. It is an audit of the activity the local transcripts can support. ## Reading the token breakdown | Field | What AirCode Ø counts | |---|---| | **Total tokens** | The provider's native total when available; otherwise the sum of non-overlapping categories exposed by that engine | | **Input** | Prompt and context tokens processed without a cache discount | | **Cached input** | Reused or newly cached prompt/context tokens; cache reads and cache writes are grouped in the UI | | **Output** | Tokens generated by the model, including text and structured tool-call output as reported by the CLI | | **Reasoning** | Reasoning or thinking tokens when the engine exposes them separately | | **Unpriced** | Recorded tokens that cannot be mapped safely to a known model and price category; the field is shown only when a session has some | | **Model calls** | Distinct native usage events after duplicate and cumulative records have been normalized | Input is the complete prompt seen by the model on every call. It can include conversation history, tool results, system instructions and injected context, not only the latest user message. A long session can therefore process the same context more than once. > **Do not add Reasoning to the other rows automatically.** Some providers > include reasoning inside output or the native total; others report it as a > separate billable category. AirCode Ø preserves the diagnostic count but adds > it to totals and cost only when the provider has not already included it. Some CLIs report cached tokens as part of their input count. AirCode Ø separates the cached portion first, so `Input + Cached input` represents the complete prompt without counting cached tokens twice. ## Where the numbers come from The persisted CLI transcript or usage journal is the source of truth. AirCode Ø normalizes each engine's format before it adds anything: - duplicate Claude assistant blocks with the same message ID count once; - cumulative Codex counters are converted into per-call deltas, and compaction-only bookkeeping events are ignored; - rewritten or rewound Gemini messages contribute only their final logical state; - Kimi cache reads and cache writes are kept distinct for pricing, even though the dialog displays them together; - detailed Grok/ACP prompt updates use the latest value for each prompt. A total-only snapshot remains recorded, but may be unpriced if its categories are unknown. Resumed processes that share the same engine and native session ID are treated as one logical session. AirCode Ø uses the strongest available transcript for that session instead of summing duplicate copies. ## Client platform attribution New launches record a coarse client platform: **Web**, **Mobile app**, **Desktop app**, **Automation**, or **API**. The value is stored locally with the session metadata in `sessions.json`, included in the structured launch log, and exposed as `local.clientPlatforms` by `GET /api/usage`. AirCode Ø does not persist a raw user-agent for this purpose. The **Usage by app** section in AI Usage shows sessions, tokens, token share, and API-equivalent cost for the selected 7-day, 30-day, custom, or all-time range. Tokens and costs are attributed to the first known launch platform of a logical session. This keeps a resumed transcript from being counted once per device. The session metadata also retains the platform of each launch, so a later analysis can identify cross-device resumes. Sessions created by older clients appear as **Unknown** when their platform cannot be inferred safely. ## How cost is calculated When a CLI reports a cost, AirCode Ø retains that value and labels it **Provider-reported cost**. Otherwise, it calculates an **Estimated API-equivalent cost** from the public list-price catalog shipped with the installed AirCode Ø version: ```text cost = (uncached input × input rate + cache reads × cached-input rate + cache writes × cache-write rate + billable output × output rate) / 1,000,000 ``` Reasoning reported outside output is priced as output exactly once. When a provider has a long-context tier, AirCode Ø selects it per model call from the complete prompt size (`Input + Cached input`). The labels under a cost have precise meanings: - **Provider-reported** — the CLI supplied the amount. - **Estimated API-equivalent** — AirCode Ø applied public API list prices. - **Reported and estimated** — the total combines both sources. - **Partial** or **Unpriced** — at least one token-bearing record could not be priced safely. Unknown models never inherit a guessed family rate. An API-equivalent estimate is not necessarily what you pay. Subscription plans, credits, negotiated rates, free tiers, provider-side adjustments and taxes are outside this calculation. AirCode Ø sums costs at full precision before display. The dialog normally shows two decimal places, up to four for values below one dollar, and up to six below one cent. Summing the rounded text from individual rows can therefore differ slightly from the displayed session total. ## Per-question attribution Question tracking uses boundaries, not prompt-size estimates: 1. Immediately before a prompt is successfully delivered, AirCode Ø captures the session's cumulative usage as a baseline. 2. When the turn completes, the process exits, or a follow-up prompt starts, AirCode Ø captures the counters again. 3. The question owns the non-negative difference between those snapshots. A follow-up sent while work is still running closes the preceding question at that boundary with the outcome **Closed by follow-up**. Counter differences are clamped at zero so a rewritten or truncated transcript cannot create negative usage. **Unattributed session activity** is: ```text complete session total − sum of tracked questions ``` It commonly contains startup activity, usage recorded before question tracking was available, or activity that could not be tied safely to one prompt. It still remains in the session total. The accounting journal stores the prompt preview and hash needed to identify a question; it does not duplicate the full prompt already held by the native transcript. ## Run time and live updates Run time measures wall-clock turn activity from prompt delivery until completion. Idle time between turns is excluded. A live turn includes elapsed time up to the current snapshot, so its duration and token counters can change while the dialog is open. Old sessions may have complete token totals but no per-question history because tracking is not reconstructed retroactively. Missing or deleted native transcripts can also make statistics unavailable. For billing disputes or account limits, use the provider's own dashboard as the authoritative source. ## Account limit gauges The gauges at the top of a provider card in **AI Usage** are account quotas, not transcript accounting. AirCode Ø reads them from each provider's account API and caches the answer for a few minutes, so several open devices share one call instead of each polling the endpoint. That cache is why a screen left open can lag behind a heavy run. Each provider card carries a refresh button next to its status: it asks the server to call that account API again straight away, and answers with the values it gets back. The **Refresh** button in the header does the same for every connected account, as does pull-to-refresh on mobile. A refresh that runs into a rate-limited endpoint keeps the last known values and states the reason on the card. Repeated presses fall back to the cached answer rather than hammering the provider, and they never lengthen the automatic retry delay that protects background polling. --- Source: https://aircodezero.com/docs/windows-cli # Windows command line The AirCode Ø Windows setup installs an `aircode` command for PowerShell and Command Prompt. It uses the Node.js runtime and ConPTY support already embedded in the desktop application; WSL and a system Node.js installation are not required. Open a **new terminal** after installing or updating AirCode Ø so it receives the updated user `PATH`, then verify the command: ```powershell aircode version aircode serve --help ``` The installer resolves the command relative to its own installation directory, so choosing a custom directory in setup is supported. ## Desktop executable or server command? | If you want to… | Use | |---|---| | Connect an Electron window to a local or remote server | Open the AirCode Ø desktop app | | Run the embedded local server and use it from Electron | Open the desktop app and choose **Run on this machine** | | Run a server without an Electron window | Open a terminal and run `aircode serve` | The desktop executable is not a supported `--serve` entry point: it always opens Electron. The `aircode` command uses the runtime and server payload installed with the desktop app, but starts a separate foreground process. See [Desktop operating modes](/docs/install#desktop-operating-modes) for the complete lifecycle summary. ## Start a server `serve` is the default command, so these forms are equivalent: ```powershell aircode serve --projects-root "D:\Projects" aircode --projects-root "D:\Projects" ``` The server runs in the foreground and writes logs to that terminal. Press `Ctrl+C` to stop it cleanly. Each projects root gets its own isolated runtime state and session namespace. When the preferred port is already used by another root, AirCode Ø selects the next available port automatically. ## Serve options | Option | Default | Purpose and equivalent setting | |---|---|---| | `--projects-root ` | current directory | Root whose subfolders AirCode Ø presents as projects. The directory must already exist. Env: `AIRCODE_PROJECTS_ROOT`; JSON: `projectsRoot` | | `--host
` | Tailscale IP, otherwise `127.0.0.1` | Network interface on which the HTTP server listens. Env: `AIRCODE_HOST`; JSON: `host` | | `--port ` | `7860` | HTTP port from 1 to 65535. An explicit busy port fails instead of silently changing. Env: `AIRCODE_PORT`; JSON: `port` | | `--public-url ` | none | Public HTTP(S) base URL used in links and notifications. Env: `AIRCODE_PUBLIC_URL`; JSON: `publicUrl` | | `--config ` | user config file | Alternate JSON configuration file. Env: `AIRCODE_CONFIG` | | `--state-dir ` | user state directory | Alternate runtime-state directory. Env: `AIRCODE_STATE_DIR` | | `--password ` | generated or configured | Access password for this process. Convenient but visible in shell history and potentially process inspection. Env: `AIRCODE_AUTH_PASSWORD`; JSON: `authPassword` | | `--password-file ` | none | Read the access password from a private file | | `--no-password` | off | Disable authentication explicitly; use only on an intentionally trusted interface | | `-h`, `--help` | — | Show command help without starting the server | The standard Windows config file is `%USERPROFILE%\.config\aircode\config.json`; runtime state defaults to root-specific directories beneath `%USERPROFILE%\.local\state\aircode\instances`. Paths containing spaces must be quoted. Both `--option value` and `--option=value` are accepted. Command-line options take precedence over environment variables, which take precedence over `config.json`, then defaults. On first run AirCode Ø generates a password, stores it in the selected configuration file, and prints it once. `--password` is available when complete command-line provisioning is required, but its value can be exposed through process inspection and shell history. Prefer `--password-file`, a protected config file, or a securely supplied `AIRCODE_AUTH_PASSWORD`. Disabling auth must be explicit with `--no-password`. ## Examples ### Multiple roots at the same time Open one terminal in the first root: ```powershell cd "$HOME\Desktop" aircode ``` Then open another terminal in a different root: ```powershell cd "$HOME\Documents" aircode ``` If Tailscale assigned `100.84.12.30` to the computer, the two banners can report `http://100.84.12.30:7860` and `http://100.84.12.30:7861`. The instances do not share runtime state or sessions. Running `aircode` again from a root that is already active reports its existing URL instead of starting a duplicate. ### Local machine only ```powershell aircode serve ` --projects-root "D:\Projects" ` --host 127.0.0.1 ` --port 7860 ``` Open `http://127.0.0.1:7860`. ### Tailscale or a private VPN Bind the exact VPN address assigned to the Windows machine: ```powershell aircode serve ` --projects-root "D:\Projects" ` --host 100.84.12.30 ` --port 7860 ``` Other devices on the same private network can then open `http://100.84.12.30:7860`. Prefer an exact VPN address over `0.0.0.0`. ### Local network ```powershell aircode serve ` --projects-root "D:\Projects" ` --host 192.168.1.25 ` --port 7860 ``` Windows Firewall may ask whether to allow the connection. Only allow the network profiles you intend to use. ### Reverse proxy or tunnel URL ```powershell aircode serve ` --projects-root "D:\Projects" ` --host 127.0.0.1 ` --port 7860 ` --public-url "https://aircode.example.com" ``` `--public-url` only tells AirCode Ø which address to place in generated links. It does **not** open a firewall port, start a reverse proxy, or create a tunnel. Use [Remote access](/docs/remote-access) for the built-in outbound tunnel, Tailscale, or private-network guidance. ### Separate configuration and state ```powershell aircode serve ` --projects-root "D:\Projects" ` --config "D:\AirCodeData\config.json" ` --state-dir "D:\AirCodeData\state" ``` If another terminal needs to run a lifecycle command against this instance, set the same state directory first: ```powershell $env:AIRCODE_STATE_DIR = "D:\AirCodeData\state" aircode status aircode stop ``` ## Desktop app coexistence The foreground CLI server and the local server managed by the desktop window are separate processes. `aircode status` and `aircode stop` only target the foreground CLI server; they do not report or control the desktop-managed server. Before reusing the same host and port, quit AirCode Ø from its notification-area icon, not only by closing its window. Alternatively, give the CLI server another port. Starting from a terminal exposes the AirCode Ø web application and its authenticated API. It does not turn the projects directory into a public file share: file access remains scoped to projects beneath `projectsRoot` and is enforced by the server. ## Additional settings Agent executable paths, session limits, browser controls, knowledge, orchestration, notifications, and token optimization remain available through environment variables and `config.json`. See [Configuration](/docs/configuration) for the complete configuration precedence and common settings.