@zephyrcloudio/harness-cli

The ze-harness command-line client and its embedded-or-remote composition edge. Requires Node.js 26.7 or later.

Command help and shell completion scripts come from Effect 4's CLI command tree. Run ze-harness --help or ze-harness session --help to inspect commands and flags. Run ze-harness completions --help for installation examples. Generate a script without starting the daemon (redirect the script to a file):

ze-harness completions bash > ~/.local/share/bash-completion/completions/ze-harness
ze-harness completions zsh > ~/.zsh/completions/_ze-harness
ze-harness completions fish > ~/.config/fish/completions/ze-harness.fish

For Zsh, add ~/.zsh/completions to fpath before running compinit. Bash needs bash-completion installed; Fish loads its completion directory automatically.

ze-harness
ze-harness tui <SESSION_ID>
ze-harness --config ze-harness.config.json run "hello"
ze-harness --remote http://127.0.0.1:4317 --server my-server chat <SESSION_ID>

Interactive TUI

Run ze-harness with no arguments in a terminal to open the interactive TUI. It launches with --experimental-ffi; the CLI re-executes itself automatically when the flag is missing.

ze-harness
ze-harness tui
ze-harness tui <SESSION_ID>
ze-harness -s <SESSION_ID>

Use -s <SESSION_ID> (or --resume <SESSION_ID>) to resume a Session. The --session flag on session fork assigns the new fork's ID; it does not resume a Session.

The opening screen has a single input. Type a prompt and press Enter to start a Session, or press Ctrl+X O to open an existing Session by ID.

From a source checkout, pnpm clean:host stops the local daemon and moves Ze Harness's XDG config, data (including Sessions and saved provider logins), cache, and ~/.ze-harness into a recoverable backup under ~/.Trash on macOS (or ~/.local/share/ze-harness-backups elsewhere). It does not change project config files or shell-provided API keys. If a provider key is exported in your shell, it can still make models available after the reset. On Windows, this source-checkout helper still uses the Unix-style default data path. After migration, set XDG_DATA_HOME to the absolute native data parent (normally $env:LOCALAPPDATA) before running it so it backs up the data rather than the legacy junction.

On first launch with no usable model, the TUI opens Connect provider automatically. When a provider is already connected (for example through a shell-exported API key), it skips the prompt. It also opens when no model provider is available. Run ze-harness tui --onboarding to open provider setup again on demand (ze-harness --onboarding also works). Press Ctrl+X P (or Ctrl+Alt+P) to reopen it later without leaving the TUI. Choose OpenAI's Codex login to open the displayed URL and enter its device code, or choose an API key connection. For API keys, the TUI briefly suspends while its terminal prompts securely for the key. After connection, it refreshes models. Ctrl+Alt+O hides optional first-run tips on future launches, independently of the one-time provider prompt; the connection action remains visible until a provider is available. A missing provider still needs to be connected before sending a prompt. The preference is stored alongside model favorites under model-preferences.json in the durable state directory.

Local skill discovery includes ~/.codex/skills, ~/.claude/skills, ~/.agents/skills, $XDG_CONFIG_HOME/opencode/{skill,skills}, and ~/.ze-harness/skills, plus matching project-directory sources. Additional directories can be set in the host's skills configuration.

Keyboard controls. Ctrl+X is the leader key: press it, then the second key.

  • Enter — send a prompt, follow up on the running Turn, or accept the highlighted slash command
  • Ctrl+V (also Ctrl+X V) — paste an image or text from the local clipboard
  • Ctrl+X R — remove the last attached image; click an attachment to remove it
  • Shift+Enter (also Ctrl+J or Alt+Enter) — newline in composer
  • Tab — complete the selected slash command when command suggestions are open, otherwise cycle the primary agent (Shift+Tab cycles back)
  • ↑/↓ (also Ctrl+P / Ctrl+N) — move in the open suggestion menu; Esc hides it
  • Esc — close overlay, cancel a pending paste, hide the suggestion menu, cancel the active Turn, or return to the parent Session from a sub-agent, in that order
  • Ctrl+X M / A / K — open the model, agent, or skill picker
  • Ctrl+X E — cycle the selected model's reasoning effort
  • Ctrl+X P (or Ctrl+Alt+P) — connect a provider
  • Ctrl+X S — list Sessions of this project, newest activity first
  • Ctrl+X N — start a new Session
  • Ctrl+X C — compact the Session context
  • Ctrl+X F — open the fork picker
  • Ctrl+X G — list sub-agents; Ctrl+X ↓ opens the first one directly
  • Ctrl+X T — toggle details for the latest tool call
  • Ctrl+X O — open a Session by ID (Ctrl+O still works on the opening screen)
  • Ctrl+C — quit; the Session keeps running in the background
  • Ctrl+X Q or Ctrl+D — quit as well; /exit (also /quit, /q) does the same from the composer
  • ↑/↓ · Enter · Tab · Ctrl+R · Esc — move, select, favorite, refresh, and close in the model picker
  • ↑/↓ · Enter · Ctrl+R · Esc — move, select, refresh, and close in the agent picker
  • ↑/↓ · Tab · Space/Enter · Ctrl+R · Esc — move, details, toggle, refresh, and close in the skill picker
  • ↑/↓ · Enter · Esc — move, select, and close in the fork and sub-agent pickers
  • ↑/↓ · Enter · Ctrl+R · Esc — move, select, refresh, and close in the session picker
  • ←/→/Tab · ↑/↓ · Enter · Esc — choose, scroll, select, and reject (or go back) in permission requests
  • Ctrl+B — detach foreground sub-agents to the background (otherwise moves left)
  • Alt+↑ — return to the parent Session from a sub-agent
  • Alt+← / Alt+→ — move between sub-agent siblings
  • Ctrl+Alt+K — pin or unpin the shortcut panel; it also appears while a leader sequence is pending

Paste screenshots with Ctrl+V on the opening screen or in a conversation. You can also paste or drop a local PNG, JPEG, GIF, or WebP file path (one file per paste). Images appear above the input; press Enter to send them with your text, or on their own. Images stay attached if submission fails and clear when you switch Sessions. Press Esc to cancel a pending paste and continue editing. Existing /image, /base64, and /send draft commands also work with pasted attachments.

Clipboard reading uses macOS system tools, PowerShell on Windows/WSL, or wl-paste (from wl-clipboard) / xclip on Linux. It runs on the machine running the TUI, including when connected to a remote Ze server. If your terminal intercepts Ctrl+V, use Ctrl+X V for image clipboard reads. Ordinary terminal paste still handles text and file paths. In an SSH terminal without access to a graphical clipboard, paste a path available to the TUI.

Selecting text with the mouse or clicking the short Session ID copies to the local clipboard through the same system tools (pbcopy, PowerShell, wl-copy/xclip/xsel), plus an OSC 52 escape for terminals that honor it, including over SSH.

The TUI shows you for prompts carrying this machine's stable author identity and a short author ID for other writers. Entries written before authorship fall back to the connection match: you for the current client, a short Client ID otherwise. Permission requests appear inline. Reply with Allow once, Allow always, or Reject; Esc rejects the open request. The first durable reply wins. Allow always confirms the exact Session patterns proposed by the Tool. The status row shows when approval or a response is required. Brief stream disconnects resume from the last update without a notice. A longer outage shows Reconnecting within about two seconds and keeps its error above the composer until the stream catches up; meanwhile active Turns refresh once a second so pending requests still appear, and the working spinner pauses. A stream that cannot resume is restarted with backoff and reloads Session history. Running tool activity appears as one collapsed line. Click a line to expand its latest bounded preview and full-output workspace artifact path when one exists. Completed tool output appears under its tool call. Output longer than three lines is collapsed with an inline expansion control. Execute groups show a summary of the tools the agent ran. Click the group to switch between that summary and its code and output; click N more to reveal hidden calls. Read previews show file contents with line numbers, shell previews show command output, and edits show diffs. Paths are relative to the Session's working directory, including when connected to a remote server. For a single call, details opens its result without repeating the summary header. Ctrl+X T switches Execute source/output. Ctrl+X J selects the next child (Ctrl+X Shift+J the previous), Ctrl+X D toggles that child's details, Ctrl+X L reveals ten more calls, and Ctrl+X Shift+L folds the selected revealed range, or the latest revealed range. Trace shortcuts close source/output first. Clipped shell commands and full failure diagnostics remain inspectable; native tools never show raw argument blocks. Pause stays a non-expandable duration/status row with its full failure diagnostic. Rejecting an approval inside Execute skips that operation; the script may continue. Cancel the Turn to stop the whole execution. Reasoning stays in an expandable block. Its header shows the observed thinking duration once the block finishes alongside a first-line preview. Completed Turns show the model and elapsed execution time below the response, excluding queue wait. New TUI Sessions receive a small built-in assistant system prompt; the opening screen does not ask users to configure it. They also start in the directory the client runs from: the CLI sends its working directory and the server stores it (relative inside the server root, absolute otherwise). Pass --workdir PATH for an explicit directory, or --no-workdir to use the server root. When the client directory cannot be used at all, the Session uses the server root and the TUI says so.

Type / on the opening screen or in the conversation composer to load available commands. The opening screen lists only the commands that work before the first prompt (/new, /queue, /sessions, /models, /effort, /skills, /exit); full command text for anything else still reports why it needs a Session. /new [TEXT] starts a new Session and switches to it, leaving the previous Session running on the server; with TEXT that prompt is submitted as the first Turn. Ctrl+X N does the same without a prompt. /exit (aliases /quit, /q) quits the application from either screen. /sessions [QUERY] lists this project's Sessions newest-first and switches on select; the previous Session keeps running on the server. /queue TEXT always creates a later Turn. /steer TEXT always follows up on the running Turn and reports an error rather than silently queueing when that Turn is no longer steerable. /models [QUERY] opens the model picker; picking a model that advertises reasoning effort levels then prompts for the level. /effort [LEVEL|default] sets the effort for the next Turn directly, bare /effort opens the effort picker, and Ctrl+X E cycles through the advertised levels. The header shows the model the next Turn will use, including the selected effort, plus live context-window use like 87.9K (8%) once a Turn reports model usage. /agents [QUERY] opens the agent picker with the primary agents; the header shows the agent the next Turn will use. /skills [QUERY] opens a multi-select picker. Selected Skills load into the next queued Turn and then clear. /fork [SEQUENCE|TURN-ID] forks the Session before a Turn and opens the fork; bare /fork opens the fork picker. Favorites, recent models, and per-model effort levels persist across runs in model-preferences.json under the durable state directory.

Task tool rows show a live one-line summary of their sub-agent: the latest tool while it runs, tool-call count and duration once it settles, and a (background) marker when the chain detached. Click a task row, type /subagents, or press Ctrl+X G to open a sub-agent and watch its Turns, thinking, and tool calls live; the composer then prompts that sub-agent, which keeps its pinned agent. /back, Esc, or Alt+↑ returns to the parent, and Alt+← / Alt+→ moves between siblings. Only direct sub-agents open; Ctrl+B detaches blocking foreground chains to the background.

Durable state paths

The database, provider auth.json, model preferences, author identity, and daemon state share one directory. An absolute XDG_DATA_HOME selects $XDG_DATA_HOME/ze-harness on every platform; relative values are ignored. The default is ~/.local/share/ze-harness on Unix and %LOCALAPPDATA%\ze-harness on Windows, with %USERPROFILE%\AppData\Local\ze-harness as the fallback when LOCALAPPDATA is missing or relative. XDG_CONFIG_HOME and XDG_CACHE_HOME are unchanged.

Existing Windows ~/.local/share/ze-harness data stays in use while its registered daemon is live. Stop the daemon, then invoke the CLI again to migrate to the native location. Migration renames the directory and leaves a junction at its old location so existing launchers and absolute paths still work. Locked or cross-volume directories remain on the legacy root with a warning; independent directories at both locations produce an error. See migration guidance before resolving a conflict or moving data manually. Absolute --database and ZE_HARNESS_DB overrides still select their own database; relative ZE_HARNESS_DB values resolve under the shared state directory.

Local and remote commands

Local commands connect to one shared daemon on this machine: the first command starts it in the background and later commands reuse it, so Sessions and running Turns survive closing the client. service status, service start, service stop, and service restart manage the daemon explicitly; restarting picks up configuration, plugin, MCP server, and Skill changes. First start wins: a --database or --config flag that differs from what the daemon started with is refused, so restart the daemon or pass --embedded for that command. Stopping or restarting ends running Turns: the CLI names them first so nothing is lost silently. serve starts a foreground server instead. With --embedded, commands run in-process without the daemon (scripts and tests). With --remote, commands use only the network Client against the named --server server. The default local database is one shared ze-harness.db file under the durable state directory; the daemon and --embedded commands may open it at once (SQLite serializes writers, and a busy database surfaces a retryable error rather than corrupting state).

Provider API keys can be managed through the same local or remote Client path:

ze-harness provider list
ze-harness provider connect openai --label personal
ze-harness provider login openai
ze-harness provider disconnect openai

provider connect requires an interactive terminal and reads the key without echoing it, so the secret does not appear in shell history or process arguments. The local command stores the key once in the global auth.json in the durable state directory, shared by every working directory and database; --label is accepted but labels persist on remote servers only. With --remote URL --server NAME, the remote server stores it instead. Provider list and command results are JSON and never include credential values. Keys connected before the global auth.json stay in their per-project databases; reconnect once with provider connect.

provider login PROVIDER signs in to a provider that supports login; provider list shows each provider's logins. Open the printed URL, enter the displayed code, and approve the login. The host runs the exchange, so the command works over SSH and waits until you approve, the provider rejects the request, the code expires, or you press Ctrl-C (which cancels the login). The URL and code appear on stderr; stdout contains only the safe connection result. Use --method ID when a provider offers several login methods.

  • openai signs in with a ChatGPT account (enable device code authorization in your ChatGPT security settings if OpenAI rejects it). Requests from that login go to the ChatGPT Codex Responses deployment; configured base URLs and routes apply only to API-key connections. Choose a model your ChatGPT plan supports with run --model openai/MODEL.
  • tap signs in to TAP with your TAP account; a first sign-in creates it. Before the code, login shows the notice the TAP app shows: by continuing you acknowledge Zephyr Cloud's Terms of Service and Privacy Policy, and agree to receive occasional product emails and notifications. tap/* models are TAP credits and free models; your own provider keys stay under that provider's id, such as opencode-go/*. To use a key you stored in TAP (or the TAP Gateway dashboard) instead of a local one, set the provider's via to tap, for example "providers": { "tap": true, "openrouter": { "via": "tap" } }. openrouter/* requests then go through the TAP Gateway (at the origin of the tap base URL) with your TAP sign-in, and provider list shows "source": "via", "via": "tap", available once you are signed in to TAP. Errors name TAP, for example "No openrouter key is stored in TAP".
  • Providers configured with login.device.discoveryURL use the standard OAuth 2.0 device flow. An optional login.device.notice (text and links, each link a label and url) is shown before the code.

A login replaces the provider's stored connection. The Node host saves access and refresh tokens in the global auth.json, refreshes them as requests need them (including during a long Turn), and retries once with a fresh token if the provider rejects an expired one. If the provider rejects the refresh token, run provider login again. An explicitly configured providers.<id>.options.apiKey takes precedence over a stored login. Use provider disconnect to remove a login, or provider connect to switch to an API key. Local daemon, embedded, and remote Node hosts support login; Cloudflare Worker hosts report login as unsupported.

run accepts --model PROVIDER/MODEL, --system-prompt TEXT, and --json. Images attach with --image URL, --image-base64 MIME:BASE64, or --image-file PATH (a local JPEG, PNG, GIF, or WebP file read from the machine running the CLI, also in --remote mode); the server may enforce a configured per-image size cap. The prompt text precedes images in the submitted content; images of the same option type retain their order. session create accepts --model PROVIDER/MODEL, --system-prompt TEXT, and --workdir PATH (a server-rooted working directory; the server root is the default). session list prints the current project's Sessions oldest-first as JSON, and server delete cancels and deletes them all (workspace files stay); both accept --all for every project. Projects resolve from git remote hashes like opencode, with directories outside any repository sharing the global project. session fork <id> copies settled history into a new Session; --turn ID|SEQUENCE forks before one Turn and --session ID selects the new identity. Forks inherit the parent workdir, model, system prompt, and permission rules, and record lineage on the new Session. session children <id> prints one Session's spawned children oldest-first as JSON, and session approvals <id> prints the pending permission requests owned by its descendant sub-agent Sessions oldest-first as JSON. session background <id> detaches its blocking foreground sub-agent chains (their results arrive as queued turns). Task tool calls render with their agent, state, and summary.

Append external context for the next admitted work without creating a Turn or calling a model:

ze-harness session conversation append <SESSION_ID> "Remember this for the next request."
cat context.txt | ze-harness session conversation append <SESSION_ID> -

The command prints the durable receipt as JSON. Use the same --database and working directory as the local Session, or add --remote URL --server NAME for a network Server. Work already admitted before the append does not see the entry.

Session system prompts are durable: they are stored with the Session, returned to Session readers, and sent with every model request in that Session. They must not contain credentials.

Remote permission and Session policy commands are:

ze-harness --remote URL --server NAME permission reply <sessionId> <requestId> <once|always|reject> [--message TEXT]
ze-harness --remote URL --server NAME permission saved list <sessionId>
ze-harness --remote URL --server NAME permission saved remove <sessionId> <savedPermissionId>
ze-harness --remote URL --server NAME session config show <sessionId>
ze-harness --remote URL --server NAME session config replace <sessionId> <expectedRevision> <configuration-json|'-'>

--message attaches feedback to a reject reply and is stored with the denial. A reject also denies the Session's other pending requests. A reply addressed to a parent Session for a descendant sub-agent request is forwarded to the owning Session, and run, chat, and the TUI surface those descendant approvals inline so sub-agents never wait unseen. They learn about sub-agent approvals and questions from the server activity stream (watchServer): each subscribes live, then bootstraps once from session children and the pending lists when the stream announces its cursor; nothing polls while idle.

The TUI and CLI choose only the reply. They cannot choose or widen the patterns saved by an always reply.

In chat, enter /steer TEXT to redirect the currently running Turn. Any other nonempty line queues a new Turn.

See the CLI quickstarts and database rules.

Install

Install the native executable without Node.js or GitHub authentication. stable tracks the latest completed production release; dev tracks completed builds from main. Public archives, signed metadata and installers are served from downloads.ze.dev.

curl -fsSL https://downloads.ze.dev/install.sh | sh
curl -fsSL https://downloads.ze.dev/install.sh | sh -s -- --channel dev
curl -fsSL https://downloads.ze.dev/install.sh | sh -s -- --version 0.3.0

On Windows PowerShell:

irm https://downloads.ze.dev/install.ps1 | iex
& ([scriptblock]::Create((irm https://downloads.ze.dev/install.ps1))) -Channel dev

Homebrew installs the stable SEA binary from the ZephyrCloudIO tap on supported macOS and glibc Linux machines:

brew install zephyrcloudio/ze/ze-harness
brew upgrade ze-harness

After the WinGet community submission is accepted and indexed, Windows x64 users can install and upgrade the portable package with:

winget install --id ZephyrCloudIO.ZeHarness -e

Before upgrading a WinGet installation, stop the local daemon and let active Turns finish:

ze-harness service stop
winget upgrade --id ZephyrCloudIO.ZeHarness -e

Stopping the daemon ends any Turns still running. Windows locks executables that are in use, and the local daemon runs from the installed executable, so WinGet cannot replace it while it runs.

Homebrew and WinGet installs are managed by those package managers, including future upgrades. They do not use the native installer's launcher or its versioned install directory. The native installer verifies signed release metadata and the archive checksum; Homebrew and WinGet verify the archive SHA-256 in their package definitions. Uninstall with brew uninstall ze-harness or winget uninstall --id ZephyrCloudIO.ZeHarness -e, respectively. These uninstall commands do not remove CLI configuration or Session data.

Supported builds: macOS 13.5+ on Apple Silicon; Linux x64/ARM64 with kernel 4.18+, glibc 2.28+, and libatomic; and Windows 10+/Server 2016+ on x64. Native installs require internet access. The Unix installer needs a POSIX shell, curl, openssl, mktemp, and tar (Linux) or unzip (macOS); Windows needs PowerShell 5.1+. Windows ARM64, Intel macOS, and Alpine/musl are not currently release targets. A version pin selects an immutable archive; rerun the installer to change versions. In PowerShell use -Version, -Dir, and -NoModifyPath; on Unix use --version, --dir, and --no-modify-path.

The launcher is installed in ~/.ze-harness/bin (or a custom --dir/-Dir). Executables and the install marker are stored under ${XDG_DATA_HOME:-~/.local/share}/ze-harness on Unix; Windows uses $env:XDG_DATA_HOME\ze-harness when XDG_DATA_HOME is an absolute path, otherwise $HOME\.local\share\ze-harness. On invocation, the CLI attempts the Windows state migration, preserving the installer's path with a junction; an in-use executable can defer the rename. The installer does not alter the CLI's existing XDG_CONFIG_HOME configuration or XDG_CACHE_HOME cache. Unix installs add the launcher directory to the shell profile unless disabled; Windows installs add it to the user PATH. Each installed version keeps LICENSE and THIRD_PARTY_LICENSES.txt beside the executable. Open a new terminal after installing. If an old unmanaged executable occupies the launcher path, move it aside before installing. New installs record the install channel or an explicit version pin alongside the launcher path. ze-harness update --check checks the signed channel pointer. ze-harness update verifies the signed manifest and archive checksum before installing. With a registered local daemon it stages the release and, when no Turns are running, restarts the daemon onto it; with running Turns it leaves the daemon unchanged and says so. Once active Turns finish, ze-harness service restart activates a prepared update and starts the daemon from the new executable. That explicit restart can interrupt active Turns, so wait for them to finish before using it. A staged release that the installed CLI already matches or passes (for example after reinstalling with the installer) is discarded instead of blocking a restart.

Upgrading the local daemon

A daemon keeps running the executable that started it, so after an upgrade it must restart onto the new version. That handoff happens without a separate command:

  • The installers finish by running ze-harness service restart --if-idle. Pass --no-restart-daemon (-NoRestartDaemon on Windows) to skip it.
  • ze-harness update hands off after staging, as above.
  • When the installed native CLI is newer than the running daemon, the first local command hands off and prints Local daemon updated from <old> to <new>.

Every handoff restarts only an idle daemon: running Turns, Sessions that cannot be inspected, or a daemon whose protocol differs leave it running and unchanged. A handoff never moves the daemon to an older release. ZE_HARNESS_DISABLE_AUTOUPDATE=1 turns off the automatic handoff on ordinary commands. If the new version cannot start, the CLI restarts the previous executable so work can continue, reports why, and does not retry the automatic handoff for that version until an explicit service restart succeeds.

While a daemon starts, the CLI shows its progress: a bar on a terminal, or one line per backup or migration step otherwise. Before migrating a database that already holds data, the daemon copies it to ze-harness.db.pre-<migration>.bak; the bar follows that copy byte by byte, then each migration. A large database can take a while, and the CLI waits for as long as the daemon is alive and reporting. A failed migration rolls back completely, and the CLI shows the daemon's own error.

Legacy native installs lack reliable pinning metadata and must explicitly opt in with ze-harness update --auto on or reinstall.

Channel-following native installations check for a new signed release on local daemon startup and every six hours while it runs. A newer version is verified and staged in a separate process, without interrupting the daemon or activating an incompatible old TUI. Run ze-harness update when current work finishes to switch both the CLI launcher and the daemon to a staged release. Use ze-harness update --auto off to disable background checks, or --auto on to enable them. Existing native installs do not get background updates unless explicitly enabled with --auto on; explicit version pins stay pinned. Homebrew and WinGet updates use their own package managers. The CLI observes Ze-Harness-Version: <protocol>/<release> on normal daemon and remote-server responses, so connecting does not add a handshake request. A running TUI shows a notice when the version differs or changes, including on a stream reconnection. Protocol differences warn that operations may fail; response observation does not cancel current work or retry prompts. Non-TUI commands send these notices to stderr, preserving stdout.

When a TUI sees a newer server release and native auto-update is enabled, it independently checks its configured signed release channel once for that CLI session. The server header never selects a download or changes the channel/pin. A separate CLI process verifies and stages an available channel update; only successful staging produces the downloaded/restart notice. It never restarts a remote server or silently switches this CLI. When local work finishes, exit the TUI and run update to activate the release and restart the daemon onto it. Repeated update commands reuse a verified staged copy of the same channel release. Native version slots are retained until uninstall. Automatic pruning is deferred until the host can track all live CLI/TUI processes using older executables, not only the daemon.

ze-harness uninstall removes the native launcher and versioned executables while leaving Sessions and credentials intact. Stop the local daemon first. Use ze-harness uninstall --remove-user-data --yes to also remove the local XDG data, config, and cache directories and user-authored global skills in ~/.ze-harness/skills. Symlinked application data directories are refused rather than deleting their targets. This does not delete custom databases outside those directories or project files. On Windows cleanup runs after the CLI exits. Remove any PATH entry added by the installer from your shell profile or Windows user PATH separately. For Homebrew and WinGet installations, the CLI provides the package-manager uninstall command instead of modifying managed files.

Binary releases

SEA builds generate THIRD_PARTY_LICENSES.txt with generate-license-file and append the Node.js notice fetched from the build runtime's exact release tag. Building the native archives requires network access to raw.githubusercontent.com for that notice.

Each release is stored at https://downloads.ze.dev/releases/v<version>/ with manifest.json, manifest.json.sig, checksums.txt, and the platform archives. The channels at /channels/stable.json and /channels/dev.json each contain a signed pointer bound to its channel. Installers contain the release certificate, verify the pointer and detached manifest signatures with RSA/SHA-256, then verify the archive SHA-256 from the signed manifest before activating it. The public certificate for independent verification is at /keys/<sha256-of-certificate>.crt (DER). Release assets are also retained on their GitHub Releases for future public visibility. Neither this release signature nor an archive checksum replaces OS-native code signing.

For release maintainers: set GitHub Actions variable ZE_RELEASE_CERT_BASE64 to the base64 DER X.509 RSA certificate, secret ZE_RELEASE_PRIVATE_KEY to its matching PEM private key, variables ZE_R2_ACCOUNT_ID and ZE_R2_BUCKET, and secrets ZE_R2_ACCESS_KEY_ID and ZE_R2_SECRET_ACCESS_KEY to write-scoped R2 credentials. For an initial keypair, generate it outside the repository:

openssl req -x509 -newkey rsa:3072 -nodes -days 3650 \
  -subj '/CN=Ze Harness Release' -keyout release-key.pem -out release.crt.pem
openssl x509 -in release.crt.pem -outform DER -out release.crt
openssl base64 -A -in release.crt # ZE_RELEASE_CERT_BASE64
openssl x509 -in release.crt.pem -noout -fingerprint -sha256

Store the entire release-key.pem contents as the private-key secret. Only the certificate is public. The release workflow checks that the certificate matches the signing key before publication. Keep the signing key outside the repository, record and publish its SHA-256 certificate fingerprint through an independently controlled channel. Public certificates have immutable fingerprinted paths; changing the release signing key requires a separate installer migration supporting both keys before switching the signed release channel. R2 must expose the bucket publicly as downloads.ze.dev; release jobs verify reads through that domain before advancing channel pointers. Configure signing and R2 settings before merging: the dev release job on every main push intentionally fails closed without them. Versioned R2 objects, including dev versions, are retained so pinned installs remain available even after old GitHub dev prereleases are pruned; plan R2 storage accordingly. The publisher uploads up to three immutable objects concurrently and verifies each through the public domain, retrying transient read errors. Each public read defaults to a 120-second timeout; set ZE_R2_READBACK_TIMEOUT_MS on the release job if the network needs longer. If publication stops partway, use Re-run failed jobs in GitHub Actions to reuse the same built archives. Re-run all jobs rebuilds archives with new timestamps and may produce different hashes, which the immutable-object preflight rejects. In that case, publish a new version rather than overwrite already published versioned objects.

Stable release publication also runs .github/workflows/package-managers.yml after R2 is verified. It reads the immutable signed manifest, verifies each archive's SHA-256 and legal notices, tests the generated Homebrew formula on macOS, and uploads the formula and WinGet manifests as a workflow artifact. The Homebrew job commits the formula directly to homebrew-ze; independently, the WinGet job commits one version into ZephyrCloudIO/winget-pkgs and opens a PR against microsoft/winget-pkgs:master. A package-manager failure does not undo the SEA release; rerun failed jobs, or dispatch Package managers with a published stable version for a backfill. Install the existing zephyr-workflows-automation GitHub App on both public repositories with Contents write permission. The jobs mint short-lived, repository-scoped App tokens using ZE_WORKFLOW_AUTOMATION_CLIENT_ID and ZE_WORKFLOW_AUTOMATION_PRIVATE_KEY, already used by the release-please workflow. To open the upstream PR, set the separate ZE_WINGET_SUBMIT_TOKEN Actions secret to a classic personal access token with only the public_repo scope and an expiration. GitHub does not yet support fine-grained personal access tokens for contributing to public repositories outside your organizations; this classic scope is limited to public repositories but cannot target only Microsoft's repository. The user token is used only in the WinGet PR job, never for commits or downloads.

The WinGet PR discloses that the source repository is currently private and leaves Windows-local validation unchecked; Microsoft runs its own checks, and acceptance and indexing may lag the stable release. Verify on Windows with winget validate --manifest <directory> and winget install --manifest <directory> using the workflow artifact.

The binary behaves like the Node CLI with two packaging limits: MCP stdio servers run without the process-tree supervisor (only the direct child is reaped on shutdown), and the TUI extracts its native assets to a versioned cache folder on first launch. macOS may require the user to approve an unnotarized executable in Privacy & Security.

Injected-host mode

serve --injected-host is the official injected-host entrypoint for embedding supervisors; the supervisor launches this binary instead of building its own. It serves the same harness HTTP/SSE protocol on loopback with credentialMode: 'injected-only': stored records, configured keys, and environment reads fail closed instead of resolving ambient authority. Provider, attachment, and tool frames arrive through the injected ports (embedder-implemented providers, content resolution after visibility filtering, host custody around final dispatch), never from the environment. The ready line prints <url> mode=injected-host for the supervisor to parse.

Agent Client Protocol

ze-harness acp serves the Agent Client Protocol over standard input and output, so ACP clients such as Zed or T3 Code can drive Ze Harness as their agent. Point the client at the ze-harness executable with the single argument acp. The command runs an embedded host and needs no daemon; stdout carries only protocol frames and every diagnostic goes to stderr.

Each ACP session is one Ze Session in the client's working directory, with symlinks resolved. Prompts queue Turns that stream text, reasoning, tool calls, and usage back as session updates. Tool approvals arrive as ACP permission requests with Allow once, Always allow in this session (when the Tool proposes save patterns), and Reject. Questions become form elicitations when the client supports them and are dismissed otherwise. Primary agents such as build and plan are offered as session modes; the model and reasoning effort are session config options. Sessions can be listed, loaded with their history replayed, resumed, forked, and closed. ACP has no steering: a prompt sent while a Turn runs is rejected, and clients cancel and resend instead.

MCP servers the client sends are attached to that Session only. Stdio servers run as local subprocesses of the acp process, with its user's authority. Their header and environment values stay in the acp process memory and are never written to the database, config, or events; clients resend them whenever they load or resume a Session. Ze sends MCP credential headers only over HTTPS, so an http:// server with headers is reported on stderr and does not connect. SSE MCP servers are not supported.

ACP Sessions use their own database, acp.db in the shared state directory, so clients that list the daemon's Sessions do not see editor work. Pass --database or set ZE_HARNESS_DB to share a database instead. Providers, configuration, and plugins load as they do for local commands, so connect providers once with ze-harness provider connect.

Public TypeScript exports

  • CliComposition
  • CliInputError
  • CliIO
  • defaultDatabasePath
  • detectTuiRequest
  • GlobalOptions
  • runCli
  • runClientCommand
  • TUI_FFI_FLAG
  • TUI_REEXEC_ENV
  • TuiCommands
  • TuiLaunch
  • TuiRunner
  • VERSION

Command reference

The output of ze-harness --help.

DESCRIPTION
  Run and manage AI coding Sessions with Ze Harness

USAGE
  ze-harness <subcommand> [flags]

FLAGS
  --remote string               Connect to a remote Harness instead of the local daemon
  --embedded                    Run in-process instead of using the local daemon
  --resume, -s string           Resume an existing Session in the TUI
  --server string               Select a server; requires --remote
  --token string                Authenticate to a remote Harness with a bearer token
  --config string               Use a local configuration file; unavailable with --remote
  --database string             Use a local SQLite database path or ':memory:'
  --mcp-callback-port string    Listen locally for MCP OAuth callbacks

GLOBAL FLAGS
  --help, -h                                                          Show help information
  --version, -v                                                       Show version information
  --wizard                                                            Start wizard mode for a command
  --completions <bash|zsh|fish|sh>                                    Print shell completion script (choices: bash, zsh, fish, sh)
  --log-level <all|trace|debug|info|warn|warning|error|fatal|none>    Sets the minimum log level (choices: all, trace, debug, info, warn, warning, error, fatal, none)

SUBCOMMANDS
  tui            Open the interactive terminal interface
  run            Run one prompt and print the result
  chat           Start a line-oriented interactive chat
  completions    Print a shell completion script; see the installation examples below
  update         Check for and install the latest version from the installation channel
  uninstall      Remove this native installation while retaining user data
  session        Create and manage Sessions; some commands require a remote server
  provider       Manage model provider connections
  mcp            Use MCP servers, resources, prompts, and OAuth
  permission     Reply to and manage tool permissions on a remote server
  question       Answer user questions asked by tools on a remote server
  compaction     Inspect and cancel context compactions
  turn           Manage Turns on a remote server
  plugins        Inspect and manage plugins
  serve          Start a local Harness HTTP server
  acp            Serve the Agent Client Protocol over standard input and output
  service        Manage the local Harness daemon
  server         Manage a remote Harness server
  runtime        Manage the remote Harness runtime
EXAMPLES
  # Open the interactive TUI in a terminal
  ze-harness

  # Resume a Session in the TUI
  ze-harness -s 550e8400-e29b-41d4-a716-446655440000

  # Open the provider setup dialog
  ze-harness tui --onboarding

  # Run one prompt locally
  ze-harness run "Explain this repository"

  # List Sessions on a remote server
  ze-harness --remote http://127.0.0.1:4317 --server main session list

  # Show help for a command
  ze-harness session --help

  # Install shell completions
  ze-harness completions --help