Repository navigation
cli: npx @simular-ai/box connect / doctor / remove - #4
Merged
Merged
Conversation
Adds @simular-ai/box (bin simular-box), which detects Claude Code, Codex, Cursor, OpenCode, Hermes, OpenClaw and VS Code, and writes the box MCP server into each app's native config. It merges without clobbering other entries, backs up each file and is idempotent. Claude Code reuses the claude/ plugin. Every app signs in with OAuth; Hermes and OpenClaw can use a personal key (--auth key) kept in the app's .env, with the gateway restarted when it changes. doctor checks config, endpoint and key; remove undoes connect. BOX_MCP_URL overrides the single URL constant. A cli-v* tag publishes to npm with provenance. Closes #3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…blish
- One workflow, publish-cli.yml, modeled on sai-sdk's publish-sai-mcp.yml. It
replaces release-cli.yml and test-cli.yml:
- check: lint, test and smoke on Linux, macOS, Windows, plus Node 18
- plugins: each agent's validator over the plugin folders
- clients (Layer 1): the real CLI against real Claude Code, Codex, Cursor
(cursor-agent), OpenCode, Hermes and OpenClaw in an isolated HOME
- live (Layer 2): a staging key proves Hermes and OpenClaw list box's tools;
never runs on fork PRs
- publish: only on cli-v* tags, through npm trusted publishing (OIDC, no
token) with provenance, then a GitHub release
- Actions pinned by SHA, persist-credentials false, a draft guard and a
concurrency group.
- Client versions are pinned in cli/e2e/clients.json.
- cross-spawn runs the client CLIs, so Windows .cmd shims get proper quoting.
The home dir follows os.homedir() on Windows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Unit-test sandbox sets USERPROFILE, APPDATA and LOCALAPPDATA. On Windows, libuv copies USERPROFILE from the parent into a child env that lacks it, so the spawned CLI was writing to the runner's real profile. - The install matrix sets CODEX_HOME for Codex on Windows. Codex finds its home through the OS profile API and ignores USERPROFILE. - TOML and JSONC edits keep the file's line endings. Removing the last TOML table keeps the final newline. - Test fixtures are checked out with LF line endings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… gating
- connect and remove act only on servers whose URL host is box by Simular:
relay.simular.cloud, the staging relay, mcp.box.simular.ai, or the host in
BOX_MCP_URL.
- Any other server named box (e.g. box.com's MCP server) stops connect with
a clear message and is left alone by remove.
- --name installs box under another name.
- Legacy box-control entries are removed only if they are ours.
- Applies to every client, including Claude Code's user scope.
- Codex keeps other settings and subtables of our own table, such as tool
approvals and timeouts. When a table is removed, the comment and blank lines
above the next table stay with it.
- Writes go through symlinks to the real file, so the link survives.
- .env files that hold keys get no backup, so rotation and remove leave no old
key on disk.
- The publish job runs in the GitHub environment "npm" and refuses tags that
are not on main.
- The live job skips with a notice when the endpoint is unreachable from the
runner (staging resolves to a private address).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ALAPPDATA on Windows) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #3
npx @simular-ai/box connectsets up box in every AI app on the machine.doctorchecks the setup andremoveundoes it. New foldercli/, published as@simular-ai/boxwith binsimular-box;boxbelongs to box.com's CLI.Not published yet. Publishing uses npm trusted publishing and needs a one-time setup by repo and npm admins; see Before the first release.
What each app gets
claude plugin marketplace add simular-ai/box-plugin+claude plugin install box@simular-box(reusesclaude/: server and skill). WithBOX_MCP_URLset:claude mcp add --scope user --transport http box <url>, and the plugin is disabled. Our own user-scopeboxfrom install.md is removed so box doesn't show twice/mcp)[mcp_servers.box] url = …in$CODEX_HOME/config.toml, edited surgically: other tables, comments and our table's other settings and subtables (tool approvals, timeouts) are kept. Notcodex mcp add: it starts the OAuth browser flow inline and would blockcodex mcp login box)mcpServers.box = {url}in~/.cursor/mcp.jsonmcp.box = {type: remote, url, enabled}inopencode.jsoncif present, elseopencode.json(XDG_CONFIG_HOME,OPENCODE_CONFIG)opencode mcp auth box)mcp_servers.boxin$HERMES_HOME/config.yaml, written directly becausehermes mcp addis interactive and cancels without a TTYauth: oauth);--auth key→ headerBearer ${MCP_BOX_API_KEY}, key in$HERMES_HOME/.envopenclaw mcp set box '{url, transport: "streamable-http", …}': OpenClaw's own CLI, so its config journal stays consistent. Withouttransport, OpenClaw defaults to SSEauth: oauth);--auth key→ headerBearer ${BOX_TOKEN}, key in~/.openclaw/.envservers.box = {type: http, url}in the usermcp.json. Detected by its user-data folder, notcodeon PATH, because Cursor installs acodeshimWindsurf was left out: it is now Devin Desktop, and its config moved.
Ownership
The CLI only changes or removes a server whose URL host is box by Simular:
relay.simular.cloud,staging.relay.simular.cloud,mcp.box.simular.ai, or the host inBOX_MCP_URL.box, such as box.com'shttps://mcp.box.com, stopsconnectwith a message, and nothing is written.removeleaves such a server alone and says so.doctorreports it.--name <name>installs box under another name next to it.box-controlentry is removed only when it is ours.Keys (
--auth key)BOX_TOKENor a hidden prompt. It is rejected as an argument, so it never lands in shell history orps..env(0600). The app config holds only a${...}reference..envfiles, so rotating or removing a key leaves no old key on disk.The two problems from #2
${VAR}from its own process environment and sends the placeholder literally when the variable is missing. A launchd or systemd Gateway never sees a shell export, but it loads<state dir>/.envat start. So the key goes there, and connect restarts a running Gateway when the key changes.doctorwarns when the header referencesBOX_TOKENbut.envlacks it.config.yaml, with the key name that Hermes's ownmcp adduses. A running gateway is restarted (hermes gateway status/restart) after a change.General behaviour
*.box-backup-<time>).--dry-runpreviews changes.cross-spawn, which quotes arguments for Windows.cmdshims.BOX_MCP_URLoverrides the single constant insrc/constants.js. It must be https, except http for localhost.doctor
doctorchecks, for each app:box, and no legacy entry remainsWWW-Authenticateresource_metadatalink whose document names an authorization serverinvalid_token)It prints a fix for each problem, supports
--json, and exits 1 when something is wrong.Dependencies:
@clack/prompts0.11 (1.x needs Node 20),cross-spawn,jsonc-parser,smol-toml(parse only; edits are surgical),yaml(its Document API keeps comments). Node >= 18. Plain ESM, no build step. Dev dependency:eslint.CI:
.github/workflows/publish-cli.ymlOne workflow for checks and publishing, modeled on sai-sdk's
publish-sai-mcp.yml.cli/or the plugin folders (paths filter; draft PRs run nothing), pushes to main,cli-v*tags, and manual dispatch.persist-credentials: false, andfail-fastis false.checkeslint --max-warnings 0), unit tests, smoke test andnpm pack --dry-run, on ubuntu, macOS and windows (Node 24), plus ubuntu on Node 18pluginsclaude plugin validate .and./claude,npm pack ./dsh.hermes plugins validate ./hermesandopenclaw plugins install/inspect/doctor ./openclawrun once those folders exist (PR #2); until then they skip with a messageclients(Layer 1)cli/e2e/clients.json, on ubuntu, macOS and windows, in an isolated HOME.e2e/install-matrix.mjsseeds an unrelated server, runsconnect --client X --yes, and asserts through the client's own listing that box is there and needs sign-in. It then re-runs (must be a no-op with no new backup), runsdoctor --json(must be clean), runsremove, and asserts that box is gone and the seeded server survivedlive(Layer 2)e2e/live.mjswithBOX_MCP_URL=${{ vars.BOX_CI_MCP_URL }}andBOX_TOKEN=${{ secrets.BOX_CI_KEY }}(masked). Steps: a minimal MCPinitialize+tools/list;connect --auth keyfor Hermes and OpenClaw;doctor(key valid);openclaw mcp probe box;hermes mcp test box. Tools are never called. Runs on pushes to main, tags, manual runs and same-repo PRs; never on fork PRsBOX_CI_KEYpublishcli-v*tags, aftercheck,pluginsandclients. Runs in environmentnpm. Checks the tagged commit is onorigin/mainand the tag equals thecli/package.jsonversion. A plain version gets dist-taglatest,-rc.Ngetsnext. Thennpm publish --provenance --access publicthrough trusted publishing (OIDC,id-token: write, noregistry-url, no npm token), andgh release create --generate-notesClients that don't run everywhere:
cursor-agent, which ships for Linux and macOS only, so Windows is excluded.USERPROFILE, so the matrix setsCODEX_HOMEthere. The CLI honoursCODEX_HOMEtoo.Layer 2 can't reach staging from GitHub-hosted runners.
staging.relay.simular.cloudresolves only to10.16.0.11, an internal address. The job checks this first and skips with a notice; it doesn't fail or work around it. Choose one:https://relay.simular.cloud/mcpwith a dedicated prod free-tier test key: setBOX_CI_MCP_URLandBOX_CI_KEYaccordingly.The live path was exercised locally against a header-logging stand-in.
openclaw mcp probe boxlisted the tool with the key read from.env, andhermes mcp test boxshowed "Connected" and "Tools discovered: 1".TODO, Layer 3 (not in this PR): a nightly run against the latest client versions, plus a real agent task on a box.
Before the first release
Repo admin settings, which I did not change:
npm. Settings → Environments → New environmentnpm. Add required reviewers and restrict deployment to tags matchingcli-v*.cli-v*. Restrict creation, update and deletion to maintainers, and block force pushes.npm, by an owner of the
@simular-aiorg:3. Publish 0.1.0 once by hand. npm can only attach a trusted publisher to a package that already exists.
npm help trust: "Package must exist: The package you're configuring must already exist on the npm registry". Fromcli/on main, with 2FA:npm publish --access public. This version has no provenance; later CI releases do.4. Add the trusted publisher. npmjs.com →
@simular-ai/box→ Settings → Trusted Publisher, or with npm >= 11.10.0:npm trust github @simular-ai/box --repo simular-ai/box-plugin --file publish-cli.yml --env npm. Values: organizationsimular-ai, repositorybox-plugin, workflowpublish-cli.yml, environmentnpm.5. Push the next tag within 2 days. The docs say: "A newly created trusted publisher configuration must complete its first successful publish within 2 days", otherwise it expires.
Study (8–12 comparable CLIs)
Studied: add-mcp, Neon
neonctl init, install-mcp, Smithery, Context7ctx7 setup, mcpm.sh and Composio; also what Stripe, Sentry and Supabase ship. Details are in #3.Adopted:
--client,-y, and a TTY-only multiselectAvoided:
mcp-remotewrappers for apps that speak HTTP nativelySources for each app's config
claude plugin --help(2.1.316)npm help trustTested
No real config was touched, no OAuth login was performed, and no real key was used; the fake key was
cb_test_fake_key_123.claudeandopenclawCLIsboxfor every client (connect refuses, remove leaves it,--nameinstalls alongside).envbackups after rotation or removeCODEX_HOME,BOX_MCP_URLand dry rune2e/install-matrix.mjswith Claude Code 2.1.316, Codex 0.166.0, cursor-agent 2026.10.22, OpenCode 1.19.5, OpenClaw 2026.10.5 and Hermes 0.22.6. CI runs the pinned versions on all three OSes.Open questions
npmenvironment, the tag ruleset and the first manual publish.mcp setwhile the Gateway runs (connect then prints the stop/start steps)mcp.jsonpath on a real VS Code install; the docs also describe~/.copilot/mcp-config.jsonskills/boxfolder for both.--yesdefault them to the personal key (better for 24/7 servers) instead of OAuth?🤖 Generated with Claude Code