feat(codex): Codex command group + plugin install hardening#52
Merged
Conversation
added 30 commits
May 23, 2026 09:23
… + README bootstrap + hook hardening
…codex-commands design
…elper - doctor-verify in both specs now spells out the same 7-check set (4 base + 3 codex), so codex setup and codex repair share semantics. - auth/re-auth argv documents --profile and --config forwarding via the buildAuthLoginArgv helper, matching codex repair re-auth. - onInstall hook path pinned to packages/codex-plugin/bin/auth.js per current .codex-plugin/hooks.json (no more "(or install.js)" wording). - stepRegisterCodexPlugin / repairStepRegisterPlugin / setup register-plugin all reference the shared registerCodexPlugin() helper; inline npm root -g and pluginId concat are forbidden.
…helper - preflight: --agent codex now fails (was warn) when the npm package is missing — this is a register-only command, so a missing package is a hard stop, not a soft hint. - codex-checks: warning text for missing/unregistered plugin now spells out the full repair recipe (npm install -g @cly-org/switchbot-codex-plugin && switchbot install --agent codex) instead of just the latter half. - codex-checks: add registerCodexPlugin() helper that wraps resolveCodexPackageRoot + resolvePluginId + runCodexPluginRegistration with normalized error strings, so the three call sites (install --agent codex, codex repair, codex setup) share one path. - default-steps.stepRegisterCodexPlugin: replace the inlined npm root -g + path.join + runCodexPluginRegistration with a single registerCodexPlugin() call. - doctor: export formatDoctorChecks for reuse by codex doctor / repair / setup output formatting.
…ed helper - New `switchbot codex setup` subcommand runs five steps: check-codex-cli, install-switchbot-cli, register-plugin, auth, doctor-verify (4 base + 3 codex = 7 checks). Only install-switchbot-cli and auth are skippable; --skip on other steps exits 2. - codex repair re-auth and codex setup auth share buildAuthLoginArgv, which forwards the active --profile and --config to the spawned `auth login`. Spawn now uses process.execPath + cliPath (process.argv[1]) instead of resolving "switchbot" through PATH. - repairStepRegisterPlugin replaced with a single registerCodexPlugin() call so install --agent codex and codex repair share one registration path. The local printDoctorChecks copy is dropped in favour of formatDoctorChecks from doctor.ts. - capabilities: COMMAND_META gains an entry for `codex setup`.
- New ## Codex integration section after Quick start documents the full surface: prerequisites, npx-based one-command bootstrap (which also explains why the install-switchbot-cli step exists — it pins the global install after npx), manual install path, doctor/repair flow, --dry-run / --json / --yes / --skip flag matrix, and --profile / --config inheritance. - Quick start gains a one-line callout redirecting Codex users away from the --agent claude-code default example. - Table of contents links the new section.
…publish gate, grep scope
Enable `"workspaces": ["packages/*"]` and import the Codex plugin from the sibling `openclaw-switchbot-skill` repo into `packages/codex-plugin/`, renamed from `@cly-org/switchbot-codex-plugin` to `@switchbot/codex-plugin` (version reset to 0.1.0; old scope was never published — `npm view` 404). CLI updates: - src/install/codex-checks.ts: resolveCodexPackageRoot path now joins `@switchbot/codex-plugin`; doctor warning recipes reference the new name. - src/install/preflight.ts and default-steps.ts: same rename. - Test mocks updated; default plugin id derived from new dirname is now `switchbot@codex-plugin`. Build / CI: - Add aggregate scripts: `test:workspaces`, `test:all`, `typecheck:workspaces`, `typecheck:all`. Existing root `test`/`typecheck` keep root-only scope (preserves pre-commit/pre-push timings). - ci.yml `test` job now runs `npm test` plus `npm run test:workspaces`. Plugin import notes: - Plain copy used instead of `git subtree add`. Sibling working tree had uncommitted in-flight test files that subtree split would have skipped. History remains in the sibling repo for archival lookup. - Added `lib/` to `package.json#files`; sibling omitted it but `lib/error-messages.js` is a runtime import of `bin/auth.js`. - Dropped 3 sibling-repo orchestration tests (codex-mcp-config, codex-setup, install-scripts) that imported `../../../scripts/*` — no analog in this monorepo; superseded by `switchbot codex setup`. Spec decision log updated with the three execution-time deviations. Hard checks pass: tarball has concrete peerDep `">=3.7.1"` (not `workspace:*`), 11 files including `lib/error-messages.js`; scoped grep for `@cly-org` in src/packages/.github/tests/scripts/README.md returns zero hits.
Plain copy from sibling repo (per PR #1 deviation rationale — captures in-flight uncommitted state that subtree split would miss). Package identity: - name: @switchbot/openclaw-skill (was @cly-org/switchbot-openclaw-skill) - version: 0.1.0 (reset for first publish under new scope) - repo URLs point at OpenWonderLabs/switchbot-openapi-cli - author block stripped (top-level package.json has author) - peerDep "@switchbot/openapi-cli": ">=3.7.1" (literal range, not workspace:*) - files[] adds lib/ (matches PR #1 fix — runtime imports of error-messages.js) Skipped @switchbot/agent-shared package entirely. The only candidate for sharing is lib/error-messages.js (~30 lines of static error strings) and codex's catalog is a strict superset of openclaw's. Bundling or publishing a third package adds machinery without paying off; revisit if either catalog grows materially. Documented in spec decision log. Cleanup: - codex-plugin: strip personal author/repo metadata, align with openclaw - README: add OpenClaw integration section, update Quick start callout - spec: log PR #2 deviations (plain copy, agent-shared skip) Tests: codex-plugin 31/31, openclaw-skill 29/29, root vitest 2715/2715.
Restructure release pipeline now that @switchbot/codex-plugin and @switchbot/openclaw-skill ship from this repo as workspace packages. publish.yml: add detect-versions step that queries npm for each package, publish only unpublished versions, gate plugin tarballs on concrete peerDeps, set continue-on-error on plugin steps so plugin failures surface as annotations without blocking CLI promotion. npm-published-smoke.yml: per-package matrix (cli vs plugin kinds), skip-if-not-republished logic, plugin tarball-shape checks (peerDep literal, executable bin entries) without live smoke. ci.yml: drop policy-schema-sync — the skill consumer is now in this monorepo so the cross-repo sync gate is obsolete. CHANGELOG: Unreleased entry documenting monorepo absorption, Codex command group, plugin renames, and workflow restructure. No version chosen yet — release decision is separate.
Keep packages/openclaw-skill/ in the monorepo (workspace tests still cover it) but drop it from publish.yml and npm-published-smoke.yml matrix. README OpenClaw integration section and CHANGELOG mentions removed so users do not search for an unpublished npm package. Re-enabling later only requires re-adding the publish + smoke entries.
…t-effort - codex setup: add install-codex-plugin step between install-switchbot-cli and register-plugin so npx @switchbot/openapi-cli codex setup bootstraps end-to-end on a brand-new machine. Both install steps remain --skip-able. - codex-plugin onInstall hook: always exits 0. When CLI is present it runs switchbot codex setup --yes; when absent it prints a hint and lets the Codex plugin install succeed (so a missing CLI never rolls it back). - README + plugin README: 6-step list, single recommended npx path. - Specs: align design docs with @SwitchBot scope and 6-step flow. Tests: +1 root (codex setup install-codex-plugin coverage), +3 workspace (makeRunOnInstall: missing CLI / setup fail / setup ok). 2716 root + 34 codex-plugin pass.
…e.json - runCodexPluginRegistration now bridges packageRoot via a junction (Windows) or symlink (POSIX) in os.tmpdir() before invoking `codex plugin marketplace add`. codex CLI 0.133.0 misparses local paths containing `@` (e.g. <npm-root>/@switchbot/codex-plugin) as `owner/repo@ref` and rejects them with `--ref is only supported for git marketplace sources`. Falls back to the original path if the link cannot be created (test mocks, restricted filesystems). - Track packages/codex-plugin/.agents/plugins/marketplace.json in git and add `.agents/` to the package `files` array so it ships in the npm tarball. The local source `path` is `../../` so codex resolves the plugin manifest at packageRoot/.codex-plugin/plugin.json (was `./`, which pointed at the wrong directory).
Pack the CLI and @switchbot/codex-plugin workspaces, install both into a scratch dir, and assert tarball shape before any release: - Plugin peerDependency is concrete (not workspace:*). - Required files ship: .codex-plugin/plugin.json + hooks.json, .agents/plugins/marketplace.json, .mcp.json, skills/, bin/. - marketplace.json name is "codex-plugin"; the switchbot plugin source.path is "../../" — codex resolves the path relative to .agents/plugins/marketplace.json itself, so it must climb two levels back to packageRoot/.codex-plugin/plugin.json. - onInstall hook runs bin/auth.js --hook and exits 0. - `switchbot codex setup --dry-run --json` lists the 6 setup steps. Wired into prepublishOnly so a wrong path, missing files entry, or broken hook is caught before the release tag.
…ationResult resolveMarketplaceSourceRoot bridges scoped-npm packageRoots through ~/switchbot before calling `codex plugin marketplace add`. codex 0.133.0 mis-parses paths containing `@` (e.g. <root>\node_modules\@SwitchBot\ codex-plugin) as `owner/repo@ref` and rejects them. A previous attempt aliased through os.tmpdir/<pid> and deleted the link in a finally — codex persists the marketplace path, so the alias must outlive the install. Reuse ~/switchbot when it already points at the same packageRoot; only create the junction once. runCodexPluginRegistration now sets stage: 'marketplace-add' | 'plugin-add' so registerCodexPlugin can name the failing codex subcommand in its error (marketplace-add exit 1: ...). resolvePluginId splits into resolvePluginName + resolveMarketplaceName so the marketplace segment of the plugin id reads from .agents/plugins/ marketplace.json instead of basename(packageRoot). publish.yml gains a smoke:codex-pack-install step (runs when the CLI or codex-plugin version bumps). Tests cover the stage field, the not-installed warn path, the marketplace.json name source, and the Windows alias creation.
…unctions resolveMarketplaceSourceRoot moves the @-path workaround junction from ~/switchbot to %LOCALAPPDATA%\switchbot\codex-plugin-marketplace (fallback ~/.switchbot/codex-plugin-marketplace when LOCALAPPDATA is unset). The previous location collided with user-owned directories named "switchbot" and silently fell back to the broken @ path on any mismatch. The new path is app-owned, so we treat divergent junctions as repairable (unlink + recreate) and throw when a non-junction sits at the alias path instead of returning a path Codex cannot parse. fs errors propagate to the caller. Tests cover four states: missing alias, healthy junction, stale junction pointing elsewhere, and real-directory collision.
Mirrors the resolveMarketplaceSourceRoot rewrite from the CLI side: junction lives at %LOCALAPPDATA%\switchbot\codex-plugin-marketplace (fallback ~/.switchbot/...), divergent junctions are repaired in place, and a real directory at the alias path throws instead of silently returning the broken @ path. Adds a deps parameter for fs injection so node:test can cover all four states without touching the real filesystem.
added 6 commits
May 23, 2026 20:09
Server listens on 127.0.0.1; on Node 18 (Linux), undici resolves 'localhost' to ::1 first and fails without IPv4 fallback, so CI test (18.x) reports 'TypeError: fetch failed'. Match the bind address explicitly to make the tests deterministic across Node versions.
- publish.yml: add openclaw-skill version detection, unpublished check, and publish step (continue-on-error, provenance) mirroring codex-plugin - preflight.ts: add shell: process.platform === 'win32' to the npm spawnSync call so .cmd shims are found on Windows (aligns with the existing pattern in codex-checks.ts) - check-cli.js: add shell: SHELL to all execFile/execFileSync calls so switchbot.cmd and npm.cmd shims are launched correctly on Windows
Expose close() on CallbackHandle so callers can tear down the server on demand. In browserLogin, wrap open() in try/catch and call close() on failure — previously the port stayed occupied for the full 120 s timeout if open() failed (e.g. headless / no-browser environments), blocking immediate retries with EADDRINUSE.
…SS_DENIED codex plugin-add backs up any existing installation before replacing it. On Windows, if the previously registered plugin path is a junction with restricted permissions, the backup hits os error 5 (ACCESS_DENIED). Running plugin-remove first forces a fresh install, bypassing the backup.
runCodexPluginRegistration now calls plugin-remove before plugin-add; update the four affected spawnSync mock sequences to match.
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.
Summary
This branch lands the full Codex integration workstream that was sitting under
[Unreleased]in CHANGELOG.md. Highlights:switchbot codexcommand group —setup,doctor,repairorchestrate plugin install, the 7-check health summary, and end-to-end repair.switchbot install --agent codexis the register-only sibling.codex setup6-step flow —install-switchbot-cli→install-codex-plugin→register-plugin→auth→doctor-verify. A singlenpx @switchbot/openapi-cli codex setupbootstraps a brand-new machine end-to-end. Both install steps are skippable via--skip.@switchbot/codex-plugin(formerly@cly-org/switchbot-codex-plugin) now ships from this repo underpackages/codex-plugin/. A single GitHub Release publishes any package whose version was bumped.onInstallhook hardening — best-effort, always exits 0 so a missing/broken switchbot CLI never rolls back the Codex plugin install. When the CLI is present it runsswitchbot codex setup --yes; when absent it prints a hint pointing atnpx @switchbot/openapi-cli codex setup.register-pluginon Windows —codex plugin marketplace addfrom<npm-root>/@switchbot/codex-pluginfailed because Codex 0.133.0 misparses paths containing@. Registration now bridges via a junction at%LOCALAPPDATA%\switchbot\codex-plugin-marketplace(fallback~/.switchbot/codex-plugin-marketplace); divergent junctions are repaired in place, real directories at the alias path raise an error instead of silently falling back to the broken@path.detect-versionsgate; per-package smoke matrix (CLI offline + live; plugins tarball-shape only); siblingpolicy-schema-syncremoved since the skill consumer is now in this monorepo.See CHANGELOG.md
[Unreleased]for the full bullet list.Test Plan
npm test— 2723/2723 across 143 filesnpm test --workspace @switchbot/codex-plugin— 39/39 (5 new cases for the alias logic)npm run smoke:codex-pack-install— tarball install + setup dry-run shape + non-blocking onInstall hookresolveMarketplaceSourceRootdirectly, junction repointed to real packageRoot)