Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/zoo-cli-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Zoo CLI Release

on:
workflow_dispatch:
inputs:
dry_run:
description: Build and verify without publishing
type: boolean
default: true

permissions:
contents: read

jobs:
artifact:
strategy:
fail-fast: false
matrix:
include:
- runs-on: macos-latest
platform: darwin-arm64
- runs-on: ubuntu-latest
platform: linux-x64
- runs-on: ubuntu-24.04-arm
platform: linux-arm64
runs-on: ${{ matrix.runs-on }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: ./.github/actions/setup-node-pnpm
- run: pnpm --dir packages/zoo-protocol test
- run: pnpm --dir packages/zoo-host test
- run: pnpm --dir apps/zoo test
- run: pnpm bundle
- run: pnpm --dir packages/zoo-host build
- name: Assemble relocatable artifact
env:
PLATFORM: ${{ matrix.platform }}
run: |
root="zoo-cli-${PLATFORM}"
mkdir -p "$root/bin" "$root/lib/host" "$root/lib/extension"
cp -R apps/zoo/dist/. "$root/lib/"
cp -R packages/zoo-host/dist/. "$root/lib/host/"
cp -R src/dist/. "$root/lib/extension/"
printf '{"type":"commonjs"}\n' > "$root/lib/extension/package.json"
node -e 'const p=require("./apps/zoo/package.json"); console.log(JSON.stringify({name:p.name,version:p.version,private:true,type:"module",dependencies:{ink:p.dependencies.ink,react:p.dependencies.react}},null,2))' > "$root/package.json"
npm install --prefix "$root" --omit=dev --ignore-scripts
printf '%s\n' '#!/usr/bin/env sh' 'base=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)' 'export ZOO_HOST_PATH="$base/lib/host/child.js"' 'export ZOO_EXTENSION_PATH="$base/lib/extension"' 'exec node "$base/lib/index.js" "$@"' > "$root/bin/zoo"
chmod +x "$root/bin/zoo"
"$root/bin/zoo" --help
"$root/bin/zoo" --version
tar -czf "zoo-cli-${PLATFORM}.tar.gz" "$root"
shasum -a 256 "zoo-cli-${PLATFORM}.tar.gz" > "zoo-cli-${PLATFORM}.tar.gz.sha256"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: zoo-cli-${{ matrix.platform }}
path: zoo-cli-${{ matrix.platform }}.tar.gz*

release:
if: ${{ !inputs.dry_run }}
needs: artifact
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: zoo-cli-*
merge-multiple: true
- env:
GH_TOKEN: ${{ github.token }}
run: gh release create "zoo-cli-v$(node -p 'require("./apps/zoo/package.json").version')" zoo-cli-*.tar.gz* --generate-notes
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

> Your AI-Powered Dev Team, Right in Your Editor

Zoo Code is also available in the terminal through the new [`zoo` CLI](docs/zoo-cli.md), with an interactive UI and deterministic text, JSON, and NDJSON automation.

## We are Zoo Code

> Zoo Code continues development of this project after the Roo team wound down
Expand Down
25 changes: 25 additions & 0 deletions apps/zoo/src/__tests__/help-docs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import fs from "node:fs"
import { execFileSync } from "node:child_process"
import path from "node:path"
import { fileURLToPath } from "node:url"

import { describe, expect, it } from "vitest"

const packageRoot = path.resolve(fileURLToPath(new URL("../../", import.meta.url)))
const repositoryRoot = path.resolve(packageRoot, "../..")

describe("CLI documentation", () => {
it("keeps documented commands discoverable in live help", () => {
const help = execFileSync(process.execPath, [path.join(packageRoot, "dist/index.js"), "--help"], {
encoding: "utf8",
})
const docs = fs.readFileSync(path.join(repositoryRoot, "docs/zoo-cli.md"), "utf8")

for (const command of ["run", "resume", "sessions"]) {
expect(help).toContain(command)
expect(docs).toContain(`zoo ${command}`)
}
expect(docs).toContain("stream-json")
expect(docs).toContain("--approval safe")
})
})
34 changes: 34 additions & 0 deletions docs/zoo-cli-release-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Zoo CLI Release Review

This checklist records the release gates for the initial `zoo` CLI. It is evidence for maintainers, not a claim that unsupported capabilities exist.

## Security

- [x] Dedicated IPC carries protocol data; child stdout cannot contaminate machine output.
- [x] Safe automation returns `needs_input`; auto approval preserves explicit denials and hard boundaries.
- [x] Run overrides remain in memory and propagate through delegation without profile/settings mutation.
- [x] macOS Keychain and Linux Secret Service adapters avoid plaintext files and secret argv values.
- [x] Stateful bounded redaction covers events, command output, diagnostics, errors, and folded headers.
- [x] Workspace/session identity is canonicalized and pinned per host.
- [x] Startup, heartbeat, commands, timeout, cancellation, flush, shutdown, and kill phases are bounded.

## Privacy And Telemetry

- [x] The production extension retains its canonical telemetry preference and flush behavior.
- [x] Public events exclude prompts/tool payloads beyond redacted terminal-visible activity.
- [x] Debug diagnostics are opt-in, bounded, redacted, and sent only to stderr.
- [x] Machine stdout contracts contain no hidden analytics or log records.
- [ ] Dedicated `client=cli` telemetry tagging is required before enabling CLI-specific product analytics. Until then, no CLI-only prompt, tool, or command telemetry is introduced.

## Artifacts

- [x] Matrix is limited to macOS ARM64 and Linux x64/ARM64.
- [x] Artifacts lock client, host, protocol, extension bundle, and Node 22 runtime expectations.
- [x] Each artifact runs live `--help` and `--version` smoke checks.
- [x] SHA-256 checksum accompanies every tarball.
- [x] Unit, host, packaged-process, type, and lint gates run before assembly.
- [ ] Signing and npm publication credentials remain maintainer-controlled release steps.

## Rollback And Support

Artifacts and tags are immutable release units. Rollback selects an earlier artifact; it never deletes or migrates `~/.zoo`, VS Code, or inherited `roo` data. Support requests should include `zoo --version`, platform, exit code, and redacted `--debug` stderr. Do not request prompt contents, API keys, vault exports, or unredacted event streams.
130 changes: 130 additions & 0 deletions docs/zoo-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Zoo Code CLI

The `zoo` executable runs the production Zoo Code extension in a private supervised host. It does not contain a second agent loop. Existing `.roo`, `.roomodes`, `.rooignore`, `AGENTS.md`, rules, skills, custom tools, and MCP configuration keep their extension semantics.

## Installation

Zoo CLI requires Node.js 22.23.1. Supported release artifacts are macOS ARM64, Linux x64, and Linux ARM64.

```sh
npm install --global @zoo-code/cli
zoo --version
```

Platform tarballs contain `bin/zoo`; add that directory to `PATH`. Windows, macOS x64, Linux musl, and older CPU baseline packages are not currently supported.

## Quick Start

Start the interactive terminal UI in the current workspace:

```sh
zoo
zoo "explain this repository"
```

Run explicit automation:

```sh
zoo run "run the focused tests" --approval safe
zoo run "summarize the project" --format json
zoo run "fix the reported bug" --format stream-json > events.ndjson
printf '%s\n' "review this workspace" | zoo run --format text
```

Resume and inspect workspace-scoped history:

```sh
zoo sessions list
zoo sessions list --format json -C ./project
zoo resume
zoo resume 019abc --format json
```

Run `zoo --help`, `zoo run --help`, or `zoo resume --help` for live option reference. A positional prompt and piped prompt cannot be combined. Root `zoo` always requires TTY stdin and stdout; redirection never changes approval policy.

## Selection And Credentials

Run selections are invocation-local:

```sh
zoo run "investigate" --provider anthropic --model claude-sonnet-4-20250514 --mode debug
zoo run "review" --profile work --reasoning-effort high
```

`--provider` conflicts with `--profile`. Explicit invalid providers, profiles, models, modes, sessions, workspaces, and durations fail instead of falling back.

Automation can read provider credentials from the provider's documented environment variable. Persisted credentials are accessed only through the operating-system vault adapter: macOS Keychain or Linux Secret Service. Secrets are never written to shim JSON, accepted as command-line flags, or included in events and diagnostics. Unsupported OAuth flows must be completed through a supported environment or vault setup.

Precedence is invocation override, invocation environment credential, selected vault profile, canonical project configuration, CLI state, then product default. CLI state is under `~/.zoo`; VS Code and inherited Roo CLI storage are not imported implicitly.

## Approvals And Threat Model

| Mode | Use | Unresolved `ask` |
| ------------- | ---------------------------- | -------------------------------------- |
| `interactive` | TTY UI | Prompt the user |
| `safe` | Default automation | Return resumable `needs_input`, exit 3 |
| `auto` | Explicit unattended autonomy | Approve eligible asks only |

`auto` is powerful and should only run in a workspace and account you trust. It never overrides explicit command denials, protected-file policy, outside-workspace restrictions, organization policy, destructive-command boundaries, mode restrictions, or MCP restrictions. Follow-up questions are not answered with invented text.

Tool arguments, terminal output, MCP payloads, errors, debug diagnostics, and final content pass through bounded redaction before rendering. Project files cannot expand access beyond canonical trust boundaries.

## Output Contracts

### Text

`--format text` is append-only and suitable for logs. It shows initialization, assistant/reasoning activity, tools, approvals, terminal and MCP activity, delegation, warnings, and the final result. `--quiet` emits only the final content or failure.

### Final JSON

`--format json` writes exactly one compact `zoo-run-result` object to stdout. Diagnostics go to stderr. Important fields are `schemaVersion`, `success`, `outcome`, root/current task IDs, workspace, resumability, content or stable error, usage/cost, elapsed time, and changed files.

### Streaming JSON

`--format stream-json` writes newline-delimited `zoo-stream` v1 records. The first record is `system.init`; each record has a monotonic `seq`, timestamp, and host identity. Deltas reconstruct ordered output. Exactly one authoritative-root `task.result` is terminal. stdout contains no ANSI or human diagnostics. `--quiet` is intentionally incompatible.

Breaking machine-schema changes increment the major schema version. Additive optional fields retain it. Unknown visible activity is represented generically rather than silently discarded.

## Outcomes And Exit Codes

| Outcome | Exit |
| ---------------------------------- | ---: |
| Completed | 0 |
| Usage or configuration | 2 |
| Needs input | 3 |
| Explicit cancellation | 4 |
| Provider failure | 10 |
| Runtime, host, or protocol failure | 70 |
| Timeout | 124 |
| SIGINT | 130 |
| SIGTERM | 143 |

Stable errors include invalid selection/workspace/session, missing credentials, permission denial, provider failure, host startup/crash, incompatible protocol, sequence gap, cancellation failure, cleanup timeout, task timeout, and closed output.

## Sessions, Signals, And Ephemeral Runs

Sessions are scoped to the canonical real path of `-C/--cwd`. `zoo resume` selects the latest root for that workspace; an ID must belong to the same workspace. Delegated histories retain root/current identity.

The first Ctrl+C requests canonical cancellation and waits for interrupted history to settle. A second Ctrl+C escalates cleanup. SIGTERM follows bounded graceful cancellation. `--timeout 10m` is a parent-owned whole-invocation deadline covering startup, history, acceptance, execution, cancellation, flush, and shutdown. Broken stdout triggers cancellation without a stack trace.

`--ephemeral` creates isolated temporary storage and removes it after success, error, signal, or timeout. Its session cannot be resumed after exit. It does not weaken project rules or approvals.

## Supported And Unsupported Capabilities

The CLI preserves canonical modes, rules, `.rooignore`, instructions, tools, MCP startup, histories, delegation, cancellation, terminal execution, and accepted root completion. Editor tabs, selections, decorations, diff UI, terminal panels, browser automation, and checkpoints are unavailable. The CLI does not expose config/profile mutation, auth management, MCP management, session mutation/import/export, cloud/daemon/remote control, worktrees, schedules, or a public long-lived stdin protocol.

`modes list` and `models list` are also withheld in this release: the current canonical queries activate mutable extension services, so they do not yet meet the side-effect-free metadata requirement.

## Coexistence With `roo`

The inherited `roo` executable remains intact during migration. `zoo` uses `~/.zoo`; it does not read or mutate inherited CLI state. Project `.roo*` files remain canonical and are shared by design. No history or plaintext-secret migration occurs automatically.

## Troubleshooting

- Run `zoo --version` to report the client/build contract.
- Use `--debug` for bounded redacted host diagnostics on stderr.
- Verify the effective `-C` workspace when a session is not found.
- A `needs_input` result is expected under safe approval; resume interactively to answer it.
- A host/protocol failure exits 70 and never contaminates JSON stdout.
- Timeout or signal cleanup is bounded; no host, shell, MCP, index, terminal, or watcher should survive.
- If vault access fails, verify Keychain or Secret Service availability, or use an invocation environment credential.
3 changes: 2 additions & 1 deletion packages/zoo-host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lint": "eslint src --ext=ts --max-warnings=0",
"check-types": "tsc --noEmit",
"test": "vitest run",
"build": "tsc",
"build": "tsup",
"clean": "rimraf dist .turbo"
},
"dependencies": {
Expand All @@ -21,6 +21,7 @@
"@roo-code/config-eslint": "workspace:^",
"@roo-code/config-typescript": "workspace:^",
"@types/node": "22.20.1",
"tsup": "8.5.1",
"tsx": "4.22.4",
"vitest": "4.1.9"
}
Expand Down
11 changes: 11 additions & 0 deletions packages/zoo-host/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineConfig } from "tsup"

export default defineConfig({
entry: ["src/child.ts", "src/index.ts"],
format: ["esm"],
clean: true,
sourcemap: true,
target: "node22",
platform: "node",
noExternal: ["@roo-code/types", "@roo-code/vscode-shim", "@roo-code/zoo-protocol"],
})
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading