{"_id":"@airterm/cli","_rev":"4-ebe794429eb70e5502be0726bcf14142","name":"@airterm/cli","dist-tags":{"latest":"0.5.0"},"versions":{"0.2.0":{"name":"@airterm/cli","version":"0.2.0","keywords":["terminal","remote-terminal","cli"],"license":"MIT","_id":"@airterm/cli@0.2.0","maintainers":[{"name":"nazmulpcc","email":"n@zmul.dev"}],"homepage":"https://airterm.app","bugs":{"url":"https://github.com/nazmulpcc/airterm-cli/issues"},"bin":{"airterm":"dist/bin.js"},"dist":{"shasum":"0e7c01613b8b68449fe846a2e6fd1ef22f77a2af","tarball":"https://registry.npmjs.org/@airterm/cli/-/cli-0.2.0.tgz","fileCount":115,"integrity":"sha512-0N37fzfIjaBXiTQcldHYCerUugJjx32AU4/MVlTFngPRIJ492v0Acq2BV9+PIihC9tzDILw5yEVs45WzStIkrA==","signatures":[{"sig":"MEQCIFO3QfPIohQ/6HqFnYgwPQ6h5/5ijC+peOJDLz5mLH7kAiAfEJ9CTcgwk0XPp8Bonb9P8gksDCSBXw+kxDFwusviKA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":305590},"type":"module","engines":{"node":">=22.12"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"63ac841baa2482c2802d7847609afbc002635443","scripts":{"test":"npm run build && vitest run","build":"node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json","prepack":"npm run build --workspace @airterm/protocol && npm run build","types:check":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"nazmulpcc","email":"n@zmul.dev"},"repository":{"url":"git+https://github.com/nazmulpcc/airterm-cli.git","type":"git","directory":"packages/cli"},"_npmVersion":"11.19.0","description":"Share local terminal sessions securely through AirTerm","directories":{},"_nodeVersion":"26.7.0","dependencies":{"ws":"8.21.3","qrcode":"1.5.4","node-pty":"1.1.0","commander":"15.0.0","@airterm/protocol":"0.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/ws":"^8.18.1","@types/qrcode":"^1.5.5"},"_npmOperationalInternal":{"tmp":"tmp/cli_0.2.0_1788610090474_0.11404924098051228","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@airterm/cli","version":"0.3.0","keywords":["terminal","remote-terminal","cli"],"license":"MIT","_id":"@airterm/cli@0.3.0","maintainers":[{"name":"nazmulpcc","email":"n@zmul.dev"}],"homepage":"https://airterm.app","bugs":{"url":"https://github.com/nazmulpcc/airterm-cli/issues"},"bin":{"airterm":"dist/bin.js"},"dist":{"shasum":"a15c40d37ae58fa555b545feb68f656d49a2eb9d","tarball":"https://registry.npmjs.org/@airterm/cli/-/cli-0.3.0.tgz","fileCount":131,"integrity":"sha512-eM27G6hrK7qMtGcEfd5+XlDq1NMOkNOHiTdH0+fCGU1OAHNiVVRNvErxB2S5l5n7sKiEBoxekS/GFlBov6iOpg==","signatures":[{"sig":"MEQCIHYktKim5mmWsJJwb4yyVBqB+SiDanJPhKFVuQ4Gvhw+AiBGHdLMQrZcWkL+6Igjl3b58pYvrztibGKPJkf+rHEyYw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":342513},"type":"module","engines":{"node":">=22.12"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"d25a956113570a8de90c5452a9f096254482880d","scripts":{"test":"npm run build && vitest run","build":"node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json","prepack":"npm run build --workspace @airterm/protocol && npm run build","types:check":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"nazmulpcc","email":"n@zmul.dev"},"repository":{"url":"git+https://github.com/nazmulpcc/airterm-cli.git","type":"git","directory":"packages/cli"},"_npmVersion":"11.19.0","description":"Share local terminal sessions securely through AirTerm","directories":{},"_nodeVersion":"26.7.0","dependencies":{"ws":"8.21.3","qrcode":"1.5.4","node-pty":"1.1.0","commander":"15.0.0","@airterm/protocol":"0.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/ws":"^8.18.1","@types/qrcode":"^1.5.5"},"optionalDependencies":{"node-datachannel":"0.33.2"},"_npmOperationalInternal":{"tmp":"tmp/cli_0.3.0_1788617871675_0.7363910575744825","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@airterm/cli","version":"0.4.0","keywords":["terminal","remote-terminal","cli"],"license":"MIT","_id":"@airterm/cli@0.4.0","maintainers":[{"name":"nazmulpcc","email":"n@zmul.dev"}],"homepage":"https://airterm.app","bugs":{"url":"https://github.com/nazmulpcc/airterm-cli/issues"},"bin":{"airterm":"dist/bin.js"},"dist":{"shasum":"07ee39367a08dfc624e824ac7eb7fe378e322f0b","tarball":"https://registry.npmjs.org/@airterm/cli/-/cli-0.4.0.tgz","fileCount":171,"integrity":"sha512-aT2UgvYiiwx/n2AS0Nt22uN7qP/GuPEn4miztO8Ds3AN50GGgzGkD3Da8VKUCEHwFAP9QUDFXuQ2d9V4hjc2aw==","signatures":[{"sig":"MEUCIEWrIgV7ozvrEJ4s2JSxWnK5rrWMgsVKbRezuBw1kVpsAiEA8j2E8Tj8CxLftPPtpWgZF9tRUTGTdbIyE2zyrcqxHBQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":462235},"type":"module","engines":{"node":">=22.12"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"9e34d8b9ede6450319f0b6af684fe357c5a9fcf1","scripts":{"test":"npm run build && vitest run","build":"node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json","prepack":"npm run build --workspace @airterm/protocol && npm run build","types:check":"tsc -p tsconfig.json --noEmit"},"_npmUser":{"name":"nazmulpcc","email":"n@zmul.dev"},"repository":{"url":"git+https://github.com/nazmulpcc/airterm-cli.git","type":"git","directory":"packages/cli"},"_npmVersion":"11.19.0","description":"Share local terminal sessions securely through AirTerm","directories":{},"_nodeVersion":"26.7.0","dependencies":{"ws":"8.21.3","qrcode":"1.5.4","node-pty":"1.1.0","commander":"15.0.0","@airterm/protocol":"0.4.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/ws":"^8.18.1","@types/qrcode":"^1.5.5"},"optionalDependencies":{"node-datachannel":"0.33.2"},"_npmOperationalInternal":{"tmp":"tmp/cli_0.4.0_1788782433650_0.011338108694435967","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@airterm/cli","version":"0.5.0","description":"Share local terminal sessions securely through AirTerm","keywords":["terminal","remote-terminal","cli"],"license":"MIT","homepage":"https://airterm.app","bugs":{"url":"https://github.com/nazmulpcc/airterm-cli/issues"},"repository":{"type":"git","url":"git+https://github.com/nazmulpcc/airterm-cli.git","directory":"packages/cli"},"type":"module","bin":{"airterm":"dist/bin.js"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"build":"node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json","test":"npm run build && vitest run","types:check":"tsc -p tsconfig.json --noEmit","prepack":"npm run build --workspace @airterm/protocol && npm run build"},"engines":{"node":">=22.12"},"dependencies":{"@airterm/protocol":"0.5.0","commander":"15.0.0","node-pty":"1.1.0","qrcode":"1.5.4","ws":"8.21.3"},"devDependencies":{"@types/qrcode":"^1.5.5","@types/ws":"^8.18.1"},"publishConfig":{"access":"public"},"optionalDependencies":{"node-datachannel":"0.33.2"},"gitHead":"48e5a1264e6d0406197e356a7ec1051386119064","_id":"@airterm/cli@0.5.0","_nodeVersion":"26.7.0","_npmVersion":"11.19.0","dist":{"integrity":"sha512-5QgJcVr/KtP9Kv1XGqWtmxLxnU5kaVkrnpsC0jBZyl9A7hiW3Y9mpr7jnqCXjbIgJ+mol2LSaQv0zpVD6HelpA==","shasum":"34f2a6c900a6d3c182186880b501788b31b48bfa","tarball":"https://registry.npmjs.org/@airterm/cli/-/cli-0.5.0.tgz","fileCount":187,"unpackedSize":535224,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCSH5PZ77J7POdPLhFLRTTKd2r9PtVRypvQeqgyjZmVMwIgC5q9Ln9zHIfHDqGufRuDob/QzjRJQ6/6mPN6GfYRyFc="}]},"_npmUser":{"name":"nazmulpcc","email":"n@zmul.dev"},"directories":{},"maintainers":[{"name":"nazmulpcc","email":"n@zmul.dev"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cli_0.5.0_1788878634283_0.9584900874620672"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-05T12:08:10.362Z","modified":"2026-09-08T14:43:54.677Z","0.2.0":"2026-09-05T12:08:10.618Z","0.3.0":"2026-09-05T14:17:51.812Z","0.4.0":"2026-09-07T12:00:33.818Z","0.5.0":"2026-09-08T14:43:54.442Z"},"bugs":{"url":"https://github.com/nazmulpcc/airterm-cli/issues"},"license":"MIT","homepage":"https://airterm.app","keywords":["terminal","remote-terminal","cli"],"repository":{"type":"git","url":"git+https://github.com/nazmulpcc/airterm-cli.git","directory":"packages/cli"},"description":"Share local terminal sessions securely through AirTerm","maintainers":[{"name":"nazmulpcc","email":"n@zmul.dev"}],"readme":"# @airterm/cli\n\nAirTerm exposes a local terminal through an end-to-end encrypted browser session.\nThe foreground CLI process owns the PTYs; the AirTerm server relays encrypted\nframes and never receives the terminal plaintext.\n\n```sh\nnpx @airterm/cli\n```\n\nRun one command instead of a reusable shell session:\n\n```sh\nnpx @airterm/cli -- opencode\n```\n\nUse a terminal font that is available on the agent machine:\n\n```sh\nnpx @airterm/cli --font-file ~/.local/share/fonts/JetBrainsMonoNerdFont-Regular.ttf\n```\n\nAirTerm accepts one TTF, OTF, WOFF, or WOFF2 font up to 10 MiB. It streams the\nfont directly through the encrypted session and keeps it in browser memory\nonly. Browser image paste uses the same acknowledged streaming transport to\ncreate a private temporary file on the agent and paste its path into the active\nterminal. Session temporary files are removed when AirTerm exits.\n\nThe package installs the `airterm` executable and requires Node.js 22.12 or\nnewer. It uses `https://airterm.app` by default; pass `--server` or set\n`AIRTERM_SERVER_URL` to use another deployment.\n\nOn Linux, `node-pty@1.1.0` compiles during installation. Python 3, Make, a C++\ncompiler, and enabled npm installation scripts are required. On Debian/Ubuntu,\ninstall `build-essential` and `python3`. Linux installations with scripts disabled\nwill not have a usable PTY binary; prebuilt Linux PTYs are not included yet.\n\nRun `airterm --help` for TTL, shell, tab, replay-buffer, font, and QR options.\n\n## Access and lifetime\n\n**Anyone holding the full session URL, including its fragment, can control the\nterminal with the permissions of the user who launched AirTerm.** This includes\naccess to that user's files, environment variables, credentials, and network\nconnections. Treat the URL and QR code as credentials; do not include them in\nlogs, screenshots, or public issue reports. Command mode is not a sandbox.\n\nThe agent enforces the earlier of the server expiry and the requested lifetime,\nincluding while disconnected. Ctrl+C, SIGTERM, browser session close, and expiry\nstop the session and clean up its temporary files. Managed processes receive a\ngraceful termination signal, followed by SIGKILL after two seconds if necessary.\nAbrupt termination such as SIGKILL or a machine crash can prevent file cleanup.\nRemote session deletion is best effort and has a five-second deadline.\n\nOn slow connections, the agent pauses terminal output reads until the network\ncatches up. A prolonged stall or output-queue overflow disconnects the transport;\nreconnection uses retained replay history. History older than the configured\nper-terminal buffer can be truncated.\n\nSupported release targets are macOS and Linux. Windows is not currently a\nsupported release target. On macOS the agent checks the bundled node-pty helper's\nexecute permissions before starting a terminal and repairs them when possible;\na read-only installation with incorrect permissions must be reinstalled in a\nwritable location.\n\n## Development and release\n\nFrom the repository root, run `npm ci`, then `npm run check`. Run\n`npm run test:package` to build and install both package tarballs outside the\nworkspace and exercise the installed executable and a real PTY. The lifecycle\ntest, `npm run test:e2e`, also requires the sibling `airterm` Laravel repository\nand its configured local dependencies.\n\nBefore release, run these checks on macOS ARM64 and Linux with Node 22.12 and the\ncurrent LTS version, run `npm audit --omit=dev`, and scan the complete Git history\nfor credentials. Do not publish if a confirmed secret remains unrotated.\n\nPublish `@airterm/protocol` first, verify its exact version is publicly available\nwith `npm view`, and only then publish `@airterm/cli`. Finally smoke-test the CLI\ninstalled from the public registry. Packing builds fresh artifacts automatically.\nPublishing packages and changing repository visibility are separate release\nsteps, not part of the local verification commands.\n\n## Updates\n\nInteractive tunnel launches check the public npm registry for a newer AirTerm\nversion before starting the session. The check phase waits at most two seconds;\ntimeouts and check failures do not prevent launching. Checks, including failed\nattempts, are limited to once per 12 hours per user. A cached newer version is\nshown on every interactive launch until you update.\n\nFor a verified npm global installation, answer `y` to `Update now? [y/N]` to\ninstall the displayed version. AirTerm then exits and asks you to rerun the same\ncommand. Answering no or pressing Enter continues the tunnel launch. Update\ninstallation failure exits with an error; no tunnel is started.\n\nRun `airterm update` to force a fresh check and install a newer version without a\nconfirmation prompt. This works in scripts too, waits up to 15 seconds for the\ncheck, and does not downgrade or reinstall an already-current version. Use\n`airterm -- update` to share an executable named `update` instead.\n\nAutomatic checks require stdin, stdout, and stderr to be terminals and are\nskipped in CI. Set `AIRTERM_NO_UPDATE_CHECK=1` to disable them explicitly,\nincluding in scripts that allocate a pseudo-terminal. Help, version, invalid\narguments, and programmatic `runAirTerm()` calls do not check for updates.\n\nSelf-installation is supported only for verified npm global installations on\nmacOS and Linux. AirTerm does not run sudo, change a project's lockfile, modify\nlinked development checkouts, or create a global installation from an npx run.\nFor npx use `npx @airterm/cli@latest`; for project dependencies or other package\nmanagers, use the installation's original package manager. Installation uses the\nsame verified npm prefix, enforces Node compatibility, and has a five-minute\nlimit. An interrupted installation may require repair using your normal npm\nsetup before relaunching.\n\nUpdate metadata contains only timestamps and version information, stored at:\n\n- macOS: `~/Library/Caches/airterm/update.json`\n- Linux: `$XDG_CACHE_HOME/airterm/update.json`, or `~/.cache/airterm/update.json`\n- Windows cache resolution: `%LOCALAPPDATA%\\airterm\\Cache\\update.json`, or\n  `~/AppData/Local/airterm/Cache/update.json` (Windows self-installation is unsupported)\n\nRelative cache-directory environment overrides are ignored. Cache failures\nsilently skip automatic checks; `airterm update` can still check explicitly.\n\n## Local sessions\n\n```sh\nairterm list\nairterm list --verbose\nairterm list --json\n```\n\nListing shows live sessions launched by your OS user on this machine, across\nshells, Node versions, global installs, and npx. Each row includes an abbreviated\nsession ID, executable basename, launch directory, active/maximum PTY count,\nconnection status, and remaining locally enforced lifetime. The directory is the\nlaunch location; it does not follow later shell `cd` commands. Verbose output\nadds full session IDs, CLI PIDs, start times, and every active PTY's ID, generated\ntitle, and PID. Exited PTYs are excluded even while final output is being sent.\n\nStatuses are Connecting (initial gateway authentication), Waiting (authenticated\nbut no encrypted browser connection), Connected (browser encryption handshake\ncompleted), and Reconnecting (retrying lost gateway connectivity).\n\n`--json` returns a `schemaVersion: 1` envelope with `sessions` and `warnings`, full\nsession IDs, absolute launch directories, UTC timestamps, and all active PTYs.\nIt cannot be combined with `--verbose`. Empty results exit successfully; discovery\nerrors or potentially incomplete results exit nonzero. Text warnings go to stderr;\nJSON warnings stay in the envelope. Listing never checks for updates or contacts\nthe AirTerm server. Use `airterm -- list` to tunnel an executable named `list`.\n\nDiscovery uses private, read-only status sockets in `/tmp/airterm-<uid>` (directory\nmode 0700, socket mode 0600) on macOS and Linux. It exposes no capability URLs,\ntokens, command arguments, environment, or terminal output. Only sessions started\nby a CLI with this implementation are discoverable; relaunch older sessions.\nProgrammatic `runAirTerm()` sessions participate; standalone low-level agents do\nnot register automatically.\n\nIf the private directory cannot be created or safely used, the tunnel still runs\nwith a warning, but cannot be listed. Listing rejects insecure directories.\nShutdown removes the session's socket immediately. Abrupt termination may leave\na dead socket; listing ignores it without deleting it. Probes have a one-second\nlimit and the complete listing has a two-second limit, so unavailable endpoints\nmay produce verified partial results with warnings. Results are live snapshots,\nnot a transactional view across sessions. There is no saved history or discovery\nof sessions on other machines or under other OS users.\n\n## Direct connections\n\nAirTerm starts through its encrypted WSS relay, then optionally upgrades to an\napplication-encrypted WebRTC DataChannel. The relay stays connected for signaling,\nrevocation, and session lifetime enforcement. Direct connection failures recover\nthrough a fresh relay connection and stay on relay until the browser page reloads.\n\nUse `airterm --relay-only` or `AIRTERM_NO_P2P=1` to prevent native WebRTC loading and\nSTUN/ICE activity. Programmatic callers can set `relayOnly: true`. The browser's\n“Prefer direct connection” preference also disables direct attempts. Re-enabling\nthat preference takes effect on the next page connection.\n\nDirect connections expose network addresses to the other peer. Public STUN also\nreceives network addressing information; it does not receive terminal contents.\nThe default is `stun:stun.l.google.com:19302`. Operators can set\n`AIRTERM_STUN_URLS` to a JSON array of up to four STUN URLs, or `[]` for host-only\ncandidates. TURN URLs and credentials are not supported. Invalid overrides and\nunavailable native binaries leave the session on relay. Replay buffers smaller\nthan 512 KiB also use relay only.\n\nDuring recovery, input is paused and uncertain keystrokes are never resent.\nUnacknowledged output is retained for up to 30 seconds, pausing noisy PTYs when\nnecessary. After that window, commands resume without a browser and bounded\nhistory eviction continues. An unrecoverable output gap disables that terminal's\ninput and rendering; open a new terminal or explicitly relaunch command mode.\nA terminal reset cannot reconstruct arbitrary missing ANSI/TUI state.\n\nThe optional `node-datachannel` dependency and its platform binaries are licensed\nunder MPL-2.0; their license and source information are distributed in their npm\npackages and at https://github.com/murat-dogan/node-datachannel. AirTerm's own\nsource remains MIT licensed. The optional adapter runs in a child process; it\nreceives signaling and encrypted frames, not AirTerm's Noise keys.\n\n## P2P verification and release status\n\nP2P is experimental in 0.3.0. The external-network verification scenarios below\nremain outstanding; use `--relay-only` to disable P2P. Run these checks from the\nworkspace root with the sibling web repository at\n`../airterm`:\n\n```sh\nnpm run check\nnpm run test:p2p\nnpm run test:package\nnpm run test:e2e\nnpm audit --omit=dev\n```\n\nOn macOS, `npm run test:p2p:browser` opens the installed Chrome, Firefox and Safari\nagainst a temporary localhost fixture. It uses the actual browser gateway,\nnative agent worker and encryption, keeps the connection alive in background\ntabs, and verifies a fresh relay connection after direct operation. It does not\ncontact production. Close its “AirTerm P2P verification” tabs afterward.\n\n```sh\nAIRTERM_TEST_BLOCK_ICE=1 npm run test:p2p:browser\nAIRTERM_TEST_UNAVAILABLE_STUN=1 npm run test:p2p:browser\nAIRTERM_BROWSER_REPORT=/tmp/airterm-browser-results.json npm run test:p2p:browser\nAIRTERM_P2P_REPORT=/tmp/airterm-output-results.json npm run test:p2p\n```\n\nThe blocked-ICE fixture rewrites candidate destinations inside the test peers;\nit does not alter the machine's firewall. Unavailable STUN may still permit host\nconnections. Reports contain timing and byte counts only. The 100 MiB test uses\na real PTY, delayed acknowledgements and a fault-injected transport; native\nDataChannel exchange is exercised separately.\n\nVerified on 2026-09-05 on an Apple M3 Pro:\n\n| Check | Result |\n| --- | --- |\n| CLI/protocol tests | 238 passed; types and builds passed |\n| Browser unit tests | 13 passed; types and formatting passed |\n| Backend checks | Passed; 93 tests passed, 38 environment-dependent tests skipped |\n| Encrypted cross-repository lifecycle | Passed |\n| Production npm audit | No reported vulnerabilities |\n| Chrome, Firefox, Safari | Direct encryption, background connection, fresh relay recovery, blocked ICE and unavailable STUN passed |\n| macOS ARM64/x64, Node 22.12.0 and 24.20.0 | Packed native startup, optional omission and installation checks passed; x64 used Rosetta |\n| Linux ARM64/x64, Node 22.12.0 and 24.20.0 | Same package checks passed in Docker; x64 used emulation |\n\nThe local 100 MiB run recovered all 104,857,600 bytes, retained at most 4 MiB,\nand delivered exit after output. It completed in 4.66 seconds; sampled parent\nRSS peaked at 250 MiB including Vitest, with 4.51 seconds user CPU and 0.21 seconds\nsystem CPU. These are test-process measurements, not production capacity claims.\nLocal browser echo RTT was 1–1.6 ms; fresh relay echo after intentional fallback\nwas 9–26 ms. The direct integration test asserts no additional terminal payload\nframes on relay after handoff; relay coordination remains connected.\n\nBefore treating P2P as fully verified, repeat the browser checks on separate\nmachines across LAN and NAT/WAN networks, physically block UDP, change network interfaces during direct\ntraffic, and exercise long background/sleep intervals. Run the 100 MiB delayed-\nACK workload through actual WebRTC with a forced path failure, measuring parent\nand worker memory together. Repeat with multiple PTYs, transfers, revocation,\nexpiry and gateway restart, verifying input stays paused until inventory and\nreplay finish and that no uncertain operation is resent. Disconnect for over\nthirty seconds and confirm commands resume while any evicted terminal history\nis rejected by the renderer. Record browser versions, network conditions,\nlatency, relay byte counts and recovery results without terminal content or\nSDP/ICE addresses. These physical-network and combined stress scenarios remain\noutstanding verification requirements; localhost fixtures do not substitute\nfor them.\n\nPublish a new protocol version before the matching CLI and verify that it\nresolves publicly. Package publication and production deployment are separate\nactions.\n\n## Background sessions\n\nUse `airterm --daemon` (or `airterm -d`) to detach a session, or\n`airterm -d -- npm run dev` to run one command in the background. The launcher\nwaits up to 30 seconds for a live PTY, an authenticated gateway, and local\ndiscovery/control endpoints. It prints the private URL/QR, expiry, session ID,\nPID, diagnostic log path, and stop command before returning. `--no-qr` still\nsuppresses the QR code. Interactive update checks run once, in the launcher.\n\n```sh\nairterm -d\nairterm list\nairterm list --verbose\nairterm stop <session-id-or-prefix>\n```\n\nStop accepts a full session ID or a unique prefix of at least eight characters.\nIt supports both foreground and background sessions started by this version,\nand confirms completion only after cleanup. An ambiguous ID or incomplete\nlisting is an error. An accepted request with unconfirmed cleanup is reported\nas an error, not as a completed stop. Older sessions without a control endpoint\nrequire browser close, SIGTERM, or relaunch with this version. `airterm -- stop`\nstill tunnels an executable named `stop`.\n\nDetached sessions survive closing the launching shell or SSH connection, retain\nthe launch directory and environment, and remain visible in `airterm list`.\nThey obey existing expiry and explicit End session behavior. Shell sessions stay\navailable with zero open terminals; reconnect and choose New terminal to open one.\nFixed-command sessions stop when their command exits. Stop terminates managed PTYs, escalates surviving process groups\nafter two seconds, cleans temporary uploads, and closes the remote session.\nThere is no restart after a crash or reboot. OS policies that terminate all user\nprocesses on logout can still terminate the daemon.\n\nThe launcher returns startup status, not the eventual command exit code. A\ncommand that finishes before readiness returns its own status. If startup fails,\ntimes out, or loses its launcher before handoff, the child cleans up rather than\nremaining as an undisclosed background session. Calling `runAirTerm()` directly\ncontinues in the caller's process, even when its parsed options include daemon\nmode; detaching is an executable-layer feature.\n\nDaemon diagnostics use private `0600` files in `/tmp/airterm-<uid>/logs` under\n`0700` directories. Each session retains at most two 1 MiB files. They contain\nbounded lifecycle events and final exit status, never capability URLs, secrets,\ncommand arguments, environment values, SDP/ICE addresses, or terminal output.\nCompleted-session logs older than 24 hours are pruned on subsequent daemon\nlaunches when discovery is complete. These temporary logs do not survive every\nOS cleanup or reboot. A logging failure after startup does not stop the command.\n\nDaemon startup requires working private logs and local discovery/control.\nForeground sessions continue with a warning if local control is unavailable.\nThe existing listing socket remains read-only; stop uses a separate private\nsocket and does not signal processes by a saved PID. Supported daemon platforms\nare macOS and Linux; no service manager or system package installation is added.\n\nContributor verification: run `npm run test:daemon` for the local lifecycle\nfixture, `npm run test:package` for isolated tarball and npx launches, and\n`npm run test:e2e` for encrypted background sessions with multiple PTYs and\nupload cleanup. These tests use temporary sessions and local test services.\n\nDaemon verification on 2026-09-05 passed 283 CLI/protocol tests and the encrypted\ncross-repository lifecycle test. Isolated package checks passed on macOS and\nLinux, ARM64 and x64, with Node 22.12.0 and 24.20.0. macOS x64 used Rosetta;\nLinux ran in Docker with an init process, with x64 emulation on the ARM64 host.\nThe checks included npx startup, launcher loss before handoff, shell closure\nafter handoff, expiry during connection/reconnection, and an uncooperative PTY.\n\n## Account sessions (unreleased)\n\nOn a server with account features enabled:\n\n```sh\nairterm login\nairterm whoami\nairterm --no-qr\nairterm --anonymous\nairterm logout\n```\n\nLogin prints a private five-minute approval link. Open it, sign in, unlock your vault, and approve the CLI. The terminal validates that approval and finishes linking automatically; no second confirmation is needed. New sessions then appear in your account dashboard. Anonymous sessions are not retroactively claimed. `--anonymous` explicitly bypasses a saved account link; an expired or revoked link otherwise fails with relinking guidance. Programmatic `runAirTerm()` remains anonymous unless account configuration is explicitly supplied.\n\nThe CLI pins the approved account public key and stores a create-session-only credential in a private per-user configuration directory, separately for each server origin. On macOS this defaults to `~/Library/Application Support/airterm`; Linux uses `$XDG_CONFIG_HOME/airterm` or `~/.config/airterm`. Directories use mode 0700 and files 0600. The server credential expires after 90 days; relink before then. Logout removes local credentials and attempts remote revocation; if the server is unavailable, revoke the CLI in account settings. Existing sessions are unaffected.\n\nThe CLI encrypts each session secret to the pinned account public key. It never receives your vault private key, passphrase, or recovery key. Dashboard Join decrypts locally in the same browser tab; Lock disconnects that browser without ending the remote command. Reload or closing the tab discards unlocked account keys. Keep your recovery key: email account recovery does not decrypt the vault. Resetting a lost vault cannot restore old session secrets.\n\nThe reserved account commands dispatch before update checks. Use `airterm -- login`, `airterm -- logout`, or `airterm -- whoami` to tunnel executables with those names. Account features are behind a server deployment flag and are not yet a production release promise.\n\n\n## Session lifetime and usage policy\n\nAnonymous sessions default to eight hours and cannot exceed the server's configured anonymous cap. A linked account session has **no fixed expiration** by default. Use `--ttl 2d`, for example, to set an explicit account-session lifetime. Operators may configure a finite account default. `airterm list` displays **No expiration** for unbounded sessions.\n\nSessions survive up to seven continuous days without their agent, bounded by any explicit expiry. The running CLI retries transient failures with jitter and delays capped at 30 seconds. The host and CLI must remain running; this does not survive reboot or restart commands automatically. An offline session remains listed and consumes a session slot until ended or expired. Account owners can end offline sessions from the sidebar. Device revocation ends that device's sessions when authorization is checked; the expiry of a CLI login credential alone does not end existing sessions.\n\nThe default monthly relay allowance is **1 GiB per anonymous creator IP** and **50 GiB per account**, shared across sessions and both directions. It resets on the first day of each calendar month at 00:00 UTC. Direct P2P payloads are excluded. Existing short-term abuse limits still apply. Usage is reserved in durable chunks of at most 1 MiB; displayed usage includes reserved bytes, and unused reservations may count after disconnect or restart.\n\nAt exhaustion, relay terminal and upload traffic pause while remote commands continue. Direct connections remain usable when available. A separate **1 MiB/day** coordination reserve allows encrypted negotiation; because the relay cannot inspect plaintext, this is a bounded opaque-traffic allowance, not proof that every byte is signaling. If it is exhausted too, encrypted relay forwarding waits for its reset. Updated clients keep their bounded control connection open so revocation can still stop the agent immediately. Any interrupted encrypted relay stream is re-paired before it resumes. Missing terminal history still triggers the existing unsafe-restoration warning; no keyboard input is automatically replayed.\n\nClipboard-image uploads are limited to **10 MiB anonymously** and **25 MiB for account sessions**, per image on either transport. The browser checks the advertised limit and the CLI verifies offers and cumulative chunks. Older implementations default to 10 MiB. Font asset limits remain unchanged.\n\nOperators configure these policies using `AIRTERM_ANONYMOUS_SESSION_TTL_SECONDS`, `AIRTERM_ACCOUNT_SESSION_TTL_SECONDS` (`0` means no fixed expiry), `AIRTERM_AGENT_OFFLINE_GRACE_SECONDS`, `AIRTERM_ANONYMOUS_MONTHLY_RELAY_BYTES`, `AIRTERM_ACCOUNT_MONTHLY_RELAY_BYTES`, `AIRTERM_COORDINATION_DAILY_BYTES`, `AIRTERM_ANONYMOUS_UPLOAD_BYTES`, and `AIRTERM_ACCOUNT_UPLOAD_BYTES`. Byte limits use binary units: 1 MiB = 1,048,576 bytes. Deploy database migrations before this server code, keep the hourly pruning scheduler running, and restart the gateway to activate configuration changes. Monthly accounting requires the durable SQL database; Redis/gateway restarts do not reset it.\n","readmeFilename":"README.md"}