diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/ci.yml similarity index 51% rename from .github/workflows/publish-cli.yml rename to .github/workflows/ci.yml index c1cc269..674c987 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/ci.yml @@ -1,30 +1,16 @@ -name: Publish CLI +name: CI -# CI + npm publish for @simular-ai/box (cli/). Modeled on simular-ai/sai-sdk's publish-sai-mcp.yml. +# Checks this repo's plugins and the box CLI they use, against real clients. The CLI itself is the +# Rust simular-box, built and published to npm as @simular-ai/box from simular-ai/box-cli; this +# repo publishes nothing to npm (the old JavaScript CLI that lived in cli/ was retired). # -# Every non-draft PR that touches the CLI or a plugin folder runs: -# check lint, unit tests, smoke test on Linux, macOS and Windows (plus Node 18 on Linux) -# plugins each agent's own validator over the plugin folders -# clients Layer 1: the real CLI against each real, installed client in an isolated HOME -# (versions pinned in cli/e2e/clients.json). No secrets. +# Every non-draft PR that touches a plugin folder or e2e/ runs: +# plugins each agent's own validator over the plugin folders, and the Claude plugin's CLI pin +# clients Layer 1: the pinned CLI against each real, installed client in an isolated HOME +# (versions pinned in e2e/clients.json). No secrets. # live Layer 2: a real staging key (secret BOX_CI_KEY, variable BOX_CI_MCP_URL) proves the # headless clients reach box and list its tools. Never on PRs from forks. -# Publishing happens only when a tag `cli-vX.Y.Z` (or `cli-vX.Y.Z-rc.N`, dist-tag "next") is pushed. -# -# Since @simular-ai/box 1.0.0 (the Rust CLI from simular-ai/box-cli) this repo no longer -# publishes the CLI: its npm trusted publisher was removed, so do not push cli-v* tags. The -# publish job below is kept only until cli/ is retired. -# -# Auth: npm trusted publishing (OIDC). The publish job's `id-token: write` lets the npm CLI swap -# a GitHub OIDC token for a short-lived publish token, so no long-lived npm token is stored. -# Requires the trusted-publisher config on npmjs.com (package @simular-ai/box -> Settings -> -# Trusted Publisher): organization "simular-ai", repository "box-plugin", workflow filename -# "publish-cli.yml", environment "npm". npm only lets you configure it on a package that already -# exists, so the very first version is published once by hand (see cli/README.md). -# -# Gating: the publish job runs in the GitHub environment "npm" (add required reviewers there) -# and refuses a tag whose commit is not on main. A tag ruleset on `cli-v*` limits who can push one. -# The repo is public, so npm attaches a provenance attestation. +# The CLI under test is `npx -y @simular-ai/box@X.Y.Z` at the version claude/ pins (e2e/cli.mjs). # # TODO(layer 3): nightly run against the latest client versions plus a real agent task on a box. @@ -32,20 +18,20 @@ on: pull_request: types: [opened, synchronize, reopened, ready_for_review, converted_to_draft] paths: - - 'cli/**' + - 'e2e/**' - 'claude/**' - 'dsh/**' - 'hermes/**' - 'openclaw/**' - '.claude-plugin/**' - - '.github/workflows/publish-cli.yml' + - '.github/workflows/ci.yml' push: branches: [main] paths: - - 'cli/**' - - '.github/workflows/publish-cli.yml' - tags: - - 'cli-v*' + - 'e2e/**' + - 'claude/**' + - '.claude-plugin/**' + - '.github/workflows/ci.yml' workflow_dispatch: concurrency: @@ -60,43 +46,6 @@ defaults: shell: bash jobs: - check: - name: Check (${{ matrix.os }}, Node ${{ matrix.node }}) - # Draft PRs run nothing; `ready_for_review` starts the checks. Non-PR events read `draft` as null. - if: github.event_name != 'pull_request' || !github.event.pull_request.draft - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - node: [24] - include: - - os: ubuntu-latest - node: 18 - runs-on: ${{ matrix.os }} - timeout-minutes: 15 - defaults: - run: - working-directory: cli - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 - with: - node-version: ${{ matrix.node }} - cache: npm - cache-dependency-path: cli/package-lock.json - - run: npm ci - - name: Lint - if: matrix.node == 24 - run: npm run lint - - name: Unit tests - run: npm test - - name: Smoke test - run: npm run smoke - - name: Pack - run: npm pack --dry-run - plugins: name: Validate plugin folders if: github.event_name != 'pull_request' || !github.event.pull_request.draft @@ -110,12 +59,12 @@ jobs: with: node-version: 24 - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 - - name: Install validators (versions from cli/e2e/clients.json) + - name: Install validators (versions from e2e/clients.json) run: | set -euo pipefail - spec() { node -p "require('./cli/e2e/clients.json')['$1'].npm"; } + spec() { node -p "require('./e2e/clients.json')['$1'].npm"; } npm install -g "$(spec claude-code)" "$(spec openclaw)" - tag=$(node -p "require('./cli/e2e/clients.json').hermes.tag") + tag=$(node -p "require('./e2e/clients.json').hermes.tag") git clone --quiet --depth 1 --branch "$tag" https://github.com/NousResearch/hermes-agent "$RUNNER_TEMP/hermes" (cd "$RUNNER_TEMP/hermes" && uv sync --frozen --extra mcp --quiet) echo "$RUNNER_TEMP/hermes/.venv/bin" >> "$GITHUB_PATH" @@ -127,8 +76,8 @@ jobs: claude plugin validate ./claude - name: Plugin pins the CLI to one exact version that is on npm # A range (^1.0.0) lets npx run a version published after review; the Claude directory - # review asks for an exact pin. Since 1.0.0 the CLI is the Rust simular-box, released from - # simular-ai/box-cli, so the pin follows its npm releases, not cli/package.json. + # review asks for an exact pin. The CLI is the Rust simular-box, released from + # simular-ai/box-cli, so the pin follows its npm releases. e2e/ tests this same version. run: | set -euo pipefail pins=$(grep -rhoE '@simular-ai/box@[^ `"]*' claude | sort -u) @@ -177,15 +126,15 @@ jobs: with: node-version: 24 cache: npm - cache-dependency-path: cli/package-lock.json + cache-dependency-path: e2e/package-lock.json - run: npm ci - working-directory: cli + working-directory: e2e - name: Install ${{ matrix.client }} env: CLIENT: ${{ matrix.client }} run: | set -euo pipefail - get() { node -p "(require('./cli/e2e/clients.json')['$CLIENT'] || {})['$1'] || ''"; } + get() { node -p "(require('./e2e/clients.json')['$CLIENT'] || {})['$1'] || ''"; } if [ -n "$(get npm)" ]; then npm install -g "$(get npm)" elif [ "$CLIENT" = hermes ]; then @@ -208,7 +157,6 @@ jobs: uv sync --frozen --extra mcp --quiet if [ -d .venv/Scripts ]; then echo "$(cygpath -w "$PWD/.venv/Scripts")" >> "$GITHUB_PATH"; else echo "$PWD/.venv/bin" >> "$GITHUB_PATH"; fi - name: connect, list, re-run, doctor, remove - working-directory: cli run: node e2e/install-matrix.mjs ${{ matrix.client }} live: @@ -227,85 +175,21 @@ jobs: with: node-version: 24 cache: npm - cache-dependency-path: cli/package-lock.json + cache-dependency-path: e2e/package-lock.json - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 - run: npm ci - working-directory: cli + working-directory: e2e - name: Install OpenClaw and Hermes run: | set -euo pipefail - npm install -g "$(node -p "require('./cli/e2e/clients.json').openclaw.npm")" - git clone --quiet --depth 1 --branch "$(node -p "require('./cli/e2e/clients.json').hermes.tag")" https://github.com/NousResearch/hermes-agent "$RUNNER_TEMP/hermes" + npm install -g "$(node -p "require('./e2e/clients.json').openclaw.npm")" + git clone --quiet --depth 1 --branch "$(node -p "require('./e2e/clients.json').hermes.tag")" https://github.com/NousResearch/hermes-agent "$RUNNER_TEMP/hermes" (cd "$RUNNER_TEMP/hermes" && uv sync --frozen --extra mcp --quiet) echo "$RUNNER_TEMP/hermes/.venv/bin" >> "$GITHUB_PATH" - name: Key valid, clients connect and list box's tools - working-directory: cli env: BOX_MCP_URL: ${{ vars.BOX_CI_MCP_URL }} BOX_TOKEN: ${{ secrets.BOX_CI_KEY }} run: | echo "::add-mask::$BOX_TOKEN" node e2e/live.mjs hermes openclaw - - publish: - name: Publish to npm - needs: [check, plugins, clients] - if: startsWith(github.ref, 'refs/tags/cli-v') - runs-on: ubuntu-latest - timeout-minutes: 15 - environment: npm - permissions: - contents: write # create the GitHub Release - id-token: write # npm trusted publishing (OIDC) and provenance - defaults: - run: - working-directory: cli - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - fetch-depth: 0 - - name: Tagged commit is on main - run: | - set -euo pipefail - git fetch --quiet origin main - if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then - echo "::error::$GITHUB_REF_NAME points at $GITHUB_SHA, which is not on main. Tag a commit on main." >&2 - exit 1 - fi - # No `registry-url`: setup-node would write an .npmrc with `_authToken=${NODE_AUTH_TOKEN}`, - # which shadows the OIDC handshake. Node 24 ships npm 11 (trusted publishing needs >= 11.5.1). - - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 - with: - node-version: 24 - cache: npm - cache-dependency-path: cli/package-lock.json - - run: npm ci - - name: Verify tag matches package version and pick the dist-tag - id: release - run: | - set -euo pipefail - TAG_NAME="${GITHUB_REF#refs/tags/}" - TAG_VERSION="${TAG_NAME#cli-v}" - PKG_VERSION="$(node -p "require('./package.json').version")" - if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then - echo "::error::Tag version ($TAG_VERSION) does not match cli/package.json ($PKG_VERSION)." >&2 - exit 1 - fi - if echo "$TAG_VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "dist-tag=latest" >> "$GITHUB_OUTPUT" - elif echo "$TAG_VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$'; then - echo "dist-tag=next" >> "$GITHUB_OUTPUT" - else - echo "::error::Tag '$TAG_NAME' is not a release tag (expected cli-vX.Y.Z or cli-vX.Y.Z-rc.N)." >&2 - exit 1 - fi - - name: Publish to npmjs.org - run: npm publish --provenance --access public --tag "${{ steps.release.outputs.dist-tag }}" - - name: Create GitHub Release - env: - GH_TOKEN: ${{ github.token }} - TAG_NAME: ${{ github.ref_name }} - PRERELEASE_FLAG: ${{ steps.release.outputs.dist-tag == 'next' && '--prerelease' || '' }} - run: | - gh release create "$TAG_NAME" --title "$TAG_NAME" --generate-notes --latest=false $PRERELEASE_FLAG diff --git a/README.md b/README.md index 6751097..7853501 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ Most agents need no plugin: add the MCP server `https://mcp.box.simular.ai/mcp` | Claude Code and Cowork | [`claude/`](claude/) | OAuth, once in the browser | `/plugin marketplace add simular-ai/box-plugin`, then `/plugin install simular-box@simular-box` | | DeepSeek Harness (dsh) | [`dsh/`](dsh/) | Personal key (`cb_...`), pasted on the plugin's page in Plugins | [dsh/README.md](dsh/README.md) | | OpenClaw | [`openclaw/`](openclaw/) | OAuth, once from the plugin's page in Settings | `openclaw plugins install @simular-ai/openclaw-box` | -| Any of Claude Code, Codex, Cursor, OpenCode, Gemini CLI, Hermes, OpenClaw, VS Code (and steps for Claude Desktop) | [`cli/`](cli/) | OAuth; personal key with `--auth key` for Hermes and OpenClaw | `npx @simular-ai/box connect` (check with `npx @simular-ai/box doctor`) | +| Any of Claude Code, Codex, Cursor, OpenCode, Gemini CLI, Hermes, OpenClaw, VS Code (and steps for Claude Desktop) | [`@simular-ai/box`](https://www.npmjs.com/package/@simular-ai/box) (the box CLI, from [simular-ai/box-cli](https://github.com/simular-ai/box-cli)) | OAuth; personal key with `--auth key` for Hermes and OpenClaw | `npx @simular-ai/box connect` (check with `npx @simular-ai/box doctor`) | | Hermes Agent | [`hermes/`](hermes/) | Personal key (`cb_...`) in `MCP_BOX_API_KEY`; OAuth optional | [hermes/README.md](hermes/README.md) | | ChatGPT and Codex (OpenAI plugin directory) | [`openai/`](openai/) | OAuth, once in the browser | Listed by OpenAI after review; build the upload with `openai/build-zip.sh` ([openai/README.md](openai/README.md)) | ## Offload: move a coding task into your box and back -Going offline in the middle of a task? In Claude Code with the plugin, `/simular-box:offload` hands the task to a fresh Claude Code (or Codex, Cursor) session in your box, which keeps working while you are away; `/simular-box:back` brings its commits and its written hand-over home and merges them. Any other agent or a plain terminal uses `npx @simular-ai/box offload` and `npx @simular-ai/box back`. Setup and details: [cli/README.md](cli/README.md#offload). +Going offline in the middle of a task? In Claude Code with the plugin, `/simular-box:offload` hands the task to a fresh Claude Code (or Codex, Cursor) session in your box, which keeps working while you are away; `/simular-box:back` brings its commits and its written hand-over home and merges them. Any other agent or a plain terminal uses `npx @simular-ai/box offload` and `npx @simular-ai/box back`. Setup and details: [the CLI's README](https://www.npmjs.com/package/@simular-ai/box). ## Adding an agent @@ -29,4 +29,4 @@ Add a folder named after the agent with its own manifest, MCP config and, where - `openai/`: bump `version` in `openai/plugin.json` and `publication.release_notes`, run `openai/build-zip.sh`, upload the ZIP at platform.openai.com/plugins. - `hermes/`: bump `version` in `hermes/plugin.json`. Users copy the folder by hand until box is in the Hermes plugin catalog. - The Claude plugin's CLI pin: since 1.0.0, `@simular-ai/box` on npm is the Rust CLI released from [simular-ai/box-cli](https://github.com/simular-ai/box-cli). To move the plugin to a new CLI release, change the exact pin `npx -y @simular-ai/box@X.Y.Z` in `claude/skills/box-offload/SKILL.md` and bump `version` in `claude/.claude-plugin/plugin.json` in one PR (CI fails unless every pin under `claude/` names the same exact version and that version is on npm; never a range, because the Claude directory review requires that what runs is what it read). -- `cli/`: the JavaScript CLI (0.2.x). It no longer publishes to npm: its trusted publisher on `@simular-ai/box` was removed when the Rust 1.0.0 shipped, so do not push `cli-v*` tags. The endpoint and CLI changes now go to simular-ai/box-cli. +- The box CLI (`@simular-ai/box` on npm) is released from simular-ai/box-cli, not from here. `e2e/` tests the pinned version against each real client (versions in `e2e/clients.json`); see `.github/workflows/ci.yml`. diff --git a/claude/README.md b/claude/README.md index 5ba9e90..b5ace53 100644 --- a/claude/README.md +++ b/claude/README.md @@ -9,7 +9,7 @@ Most agents need no plugin: add the MCP server `https://mcp.box.simular.ai/mcp` | Claude Code and Cowork | [`claude/`](https://github.com/simular-ai/box-plugin/tree/main/claude) | OAuth, once in the browser | `/plugin marketplace add simular-ai/box-plugin`, then `/plugin install simular-box@simular-box` | | DeepSeek Harness (dsh) | [`dsh/`](https://github.com/simular-ai/box-plugin/tree/main/dsh) | Personal key (`cb_...`), pasted on the plugin's page in Plugins | [dsh/README.md](https://github.com/simular-ai/box-plugin/blob/main/dsh/README.md) | | OpenClaw | [`openclaw/`](https://github.com/simular-ai/box-plugin/tree/main/openclaw) | OAuth, once from the plugin's page in Settings | `openclaw plugins install @simular-ai/openclaw-box` | -| Any of Claude Code, Codex, Cursor, OpenCode, Gemini CLI, Hermes, OpenClaw, VS Code (and steps for Claude Desktop) | [`cli/`](https://github.com/simular-ai/box-plugin/tree/main/cli) | OAuth; personal key with `--auth key` for Hermes and OpenClaw | `npx @simular-ai/box connect` (check with `npx @simular-ai/box doctor`) | +| Any of Claude Code, Codex, Cursor, OpenCode, Gemini CLI, Hermes, OpenClaw, VS Code (and steps for Claude Desktop) | [`@simular-ai/box`](https://www.npmjs.com/package/@simular-ai/box) (the box CLI, from [simular-ai/box-cli](https://github.com/simular-ai/box-cli)) | OAuth; personal key with `--auth key` for Hermes and OpenClaw | `npx @simular-ai/box connect` (check with `npx @simular-ai/box doctor`) | | Hermes Agent | [`hermes/`](https://github.com/simular-ai/box-plugin/tree/main/hermes) | Personal key (`cb_...`) in `MCP_BOX_API_KEY`; OAuth optional | [hermes/README.md](https://github.com/simular-ai/box-plugin/blob/main/hermes/README.md) | | ChatGPT and Codex (OpenAI plugin directory) | [`openai/`](https://github.com/simular-ai/box-plugin/tree/main/openai) | OAuth, once in the browser | Listed by OpenAI after review; build the upload with `openai/build-zip.sh` ([openai/README.md](https://github.com/simular-ai/box-plugin/blob/main/openai/README.md)) | @@ -79,4 +79,4 @@ Add a folder named after the agent with its own manifest, MCP config and, where - `openai/`: bump `version` in `openai/plugin.json` and `publication.release_notes`, run `openai/build-zip.sh`, upload the ZIP at platform.openai.com/plugins. - `hermes/`: bump `version` in `hermes/plugin.json`. Users copy the folder by hand until box is in the Hermes plugin catalog. - The Claude plugin's CLI pin: since 1.0.0, `@simular-ai/box` on npm is the Rust CLI released from [simular-ai/box-cli](https://github.com/simular-ai/box-cli). To move the plugin to a new CLI release, change the exact CLI version in every `npx -y` command in `claude/skills/box-offload/SKILL.md` and `claude/README.md`, and bump `version` in `claude/.claude-plugin/plugin.json`, in one PR (CI fails unless every pin under `claude/` names the same exact version and that version is on npm; never a range, because the Claude directory review requires that what runs is what it read). -- `cli/`: the JavaScript CLI (0.2.x). It no longer publishes to npm: its trusted publisher on `@simular-ai/box` was removed when the Rust 1.0.0 shipped, so do not push `cli-v*` tags. The endpoint and CLI changes now go to simular-ai/box-cli. +- The box CLI (`@simular-ai/box` on npm) is released from simular-ai/box-cli, not from here. `e2e/` tests the pinned version against each real client (versions in `e2e/clients.json`); see `.github/workflows/ci.yml`. diff --git a/cli/.gitattributes b/cli/.gitattributes deleted file mode 100644 index 36fcc5d..0000000 --- a/cli/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -test/fixtures/** text eol=lf diff --git a/cli/.gitignore b/cli/.gitignore deleted file mode 100644 index 6b1d0bf..0000000 --- a/cli/.gitignore +++ /dev/null @@ -1 +0,0 @@ -LICENSE diff --git a/cli/README.md b/cli/README.md deleted file mode 100644 index 01ef596..0000000 --- a/cli/README.md +++ /dev/null @@ -1,114 +0,0 @@ -# @simular-ai/box - -Connect [box](https://box.simular.ai) to your AI apps in one command. box gives your AI agent a computer of its own: a cloud Linux desktop with Chrome and a shell, driven over MCP. - -```bash -npx @simular-ai/box connect -``` - -The CLI finds the AI apps on this computer, asks which ones should use box, and adds the box MCP server (`https://mcp.box.simular.ai/mcp`) to each one in its own format. Other servers and settings in those files are left alone, each changed file gets a backup next to it (`*.box-backup-