{"_id":"@aiconnect/nexus-chat-cli","_rev":"3-330e6ad1969c06e3bf58c1c944a793e0","name":"@aiconnect/nexus-chat-cli","dist-tags":{"latest":"0.2.1"},"versions":{"0.1.0":{"name":"@aiconnect/nexus-chat-cli","version":"0.1.0","keywords":["cli","chat","openai"],"author":{"name":"AI Connect"},"license":"MIT","_id":"@aiconnect/nexus-chat-cli@0.1.0","maintainers":[{"name":"ericsantos","email":"ericsantos.net@gmail.com"}],"homepage":"https://github.com/aiconnect-cloud/nexus-chat-cli#readme","bugs":{"url":"https://github.com/aiconnect-cloud/nexus-chat-cli/issues"},"bin":{"nexus-chat":"dist/cli.js"},"dist":{"shasum":"87f3fd920dcc5c803dffd7b5b8091b5cef50daeb","tarball":"https://registry.npmjs.org/@aiconnect/nexus-chat-cli/-/nexus-chat-cli-0.1.0.tgz","fileCount":4,"integrity":"sha512-aBYGUVI9FDNKrJcNDqXSr5wniAFBuNhCK4s95u0e6f9lS59FlDZ62ciGRLz+XKFMoe7cTJ5z41G67vCO3fIIWw==","signatures":[{"sig":"MEUCIQCyGcmvkQi+60vYtY3bVvBGFYSbpVJcO8YycUByyh+SJgIgQ8AoJI39GpT8WRS0QXZ1F5jpoaHSEkc39TIqqtGixz0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":66448},"type":"module","engines":{"node":">=20"},"gitHead":"131c5835517454098711b5fc21f453a2b3ae05b7","scripts":{"lint":"biome check .","test":"vitest run","build":"tsc --noEmit && tsup src/cli.ts --format esm --platform node --target node20 --clean","smoke":"npm run build && node test/e2e-smoke.mjs","release":"npm publish","prepublishOnly":"npm run build"},"_npmUser":{"name":"ericsantos","email":"ericsantos.net@gmail.com"},"repository":{"url":"git+https://github.com/aiconnect-cloud/nexus-chat-cli.git","type":"git"},"_npmVersion":"11.17.0","description":"Terminal chat client for OpenAI-compatible endpoints","directories":{},"_nodeVersion":"26.5.0","dependencies":{"marked":"^15.0.12","commander":"^14.0.0","picocolors":"^1.1.1","marked-terminal":"^7.3.0","@inquirer/prompts":"^7.8.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.0","vitest":"^3.2.4","typescript":"^5.9.2","@types/node":"^22.17.1","@biomejs/biome":"^2.5.8"},"_npmOperationalInternal":{"tmp":"tmp/nexus-chat-cli_0.1.0_1786498174828_0.6073382243527277","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@aiconnect/nexus-chat-cli","version":"0.2.0","keywords":["cli","chat","openai"],"author":{"name":"AI Connect"},"license":"MIT","_id":"@aiconnect/nexus-chat-cli@0.2.0","maintainers":[{"name":"ericsantos","email":"ericsantos.net@gmail.com"}],"homepage":"https://github.com/aiconnect-cloud/nexus-chat-cli#readme","bugs":{"url":"https://github.com/aiconnect-cloud/nexus-chat-cli/issues"},"bin":{"nexus-chat":"dist/cli.js"},"dist":{"shasum":"f57dfb5315cd612a250bbc19993e91c713bcc84d","tarball":"https://registry.npmjs.org/@aiconnect/nexus-chat-cli/-/nexus-chat-cli-0.2.0.tgz","fileCount":4,"integrity":"sha512-AtbU7sZFoAxLbuiDcDAtrc9H1m9kRiyKRnhaFQPZ/bCRmWx05TEXz7cgWQJ9dNBnwl16LgEeavZ01hJH6pFDHA==","signatures":[{"sig":"MEUCICc5uel+XXxnJdLL3d1J1QdrwJJRalQCOy1VoaL/vcfdAiEA8ycwVyeP1TOEQInTVFJ1eAM/lOaQGR2lYu02MGXQd7g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":91439},"type":"module","engines":{"node":">=20"},"gitHead":"8638751fc3bc29d70ccaa1be2680d87902a43f5a","scripts":{"lint":"biome check .","test":"vitest run","build":"tsc --noEmit && tsup src/cli.ts --format esm --platform node --target node20 --clean","smoke":"npm --ignore-scripts run build && node test/e2e-smoke.mjs","release":"npm publish","release:check":"node scripts/release-check.mjs","smoke:package":"node scripts/package-smoke.mjs","prepublishOnly":"npm run build","release:verify":"node scripts/verify-release.mjs"},"_npmUser":{"name":"ericsantos","email":"ericsantos.net@gmail.com"},"repository":{"url":"git+https://github.com/aiconnect-cloud/nexus-chat-cli.git","type":"git"},"_npmVersion":"11.17.0","description":"Terminal chat client for OpenAI-compatible endpoints","directories":{},"_nodeVersion":"26.5.0","dependencies":{"marked":"^15.0.12","commander":"^14.0.0","picocolors":"^1.1.1","marked-terminal":"^7.3.0","@inquirer/prompts":"^7.8.4"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.0","vitest":"^3.2.4","typescript":"^5.9.2","@types/node":"^22.17.1","@biomejs/biome":"^2.5.8"},"_npmOperationalInternal":{"tmp":"tmp/nexus-chat-cli_0.2.0_1786840248240_0.47199600671305175","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@aiconnect/nexus-chat-cli","version":"0.2.1","description":"Terminal chat client for OpenAI-compatible endpoints","type":"module","bin":{"nexus-chat":"dist/cli.js"},"engines":{"node":">=20"},"scripts":{"prepublishOnly":"npm run build","build":"tsc --noEmit && tsup src/cli.ts --format esm --platform node --target node20 --clean","test":"vitest run","lint":"biome check .","smoke":"npm --ignore-scripts run build && node test/e2e-smoke.mjs","release:verify":"node scripts/verify-release.mjs","smoke:package":"node scripts/package-smoke.mjs","release:check":"node scripts/release-check.mjs","release":"npm publish"},"keywords":["cli","chat","openai"],"license":"MIT","author":{"name":"AI Connect"},"repository":{"type":"git","url":"git+https://github.com/aiconnect-cloud/nexus-chat-cli.git"},"bugs":{"url":"https://github.com/aiconnect-cloud/nexus-chat-cli/issues"},"homepage":"https://github.com/aiconnect-cloud/nexus-chat-cli#readme","publishConfig":{"access":"public"},"dependencies":{"@inquirer/prompts":"^7.8.4","commander":"^14.0.0","marked":"^15.0.12","marked-terminal":"^7.3.0","picocolors":"^1.1.1"},"devDependencies":{"@biomejs/biome":"^2.5.8","@types/node":"^22.17.1","tsup":"^8.5.0","typescript":"^5.9.2","vitest":"^3.2.4"},"gitHead":"06737943cd27a197e2f14a8f4917207cfc3d959c","_id":"@aiconnect/nexus-chat-cli@0.2.1","_nodeVersion":"26.5.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-AiuLXfqCF9OJlt/dBbzVZStpMcUBW+5KMWKK6uKvvuXyLZGUm8Y1K39rFnZzf5CF5awAEUC/IiUxyh6TMXhkVg==","shasum":"27803428dfeffa9a32d36996e75e001c00ff8708","tarball":"https://registry.npmjs.org/@aiconnect/nexus-chat-cli/-/nexus-chat-cli-0.2.1.tgz","fileCount":4,"unpackedSize":91571,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDG488o23kGrzqoNm2Hao/RYzNWeemqXo+EazgfoCG7WgIgCgbMiLPF49wSmxD0QN/9jcRZXupWU3xU6rwRYdxneog="}]},"_npmUser":{"name":"ericsantos","email":"ericsantos.net@gmail.com"},"directories":{},"maintainers":[{"name":"ericsantos","email":"ericsantos.net@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nexus-chat-cli_0.2.1_1786844305441_0.5907794376641315"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-12T01:29:34.672Z","modified":"2026-08-16T01:38:25.702Z","0.1.0":"2026-08-12T01:29:34.968Z","0.2.0":"2026-08-16T00:30:48.393Z","0.2.1":"2026-08-16T01:38:25.558Z"},"bugs":{"url":"https://github.com/aiconnect-cloud/nexus-chat-cli/issues"},"author":{"name":"AI Connect"},"license":"MIT","homepage":"https://github.com/aiconnect-cloud/nexus-chat-cli#readme","keywords":["cli","chat","openai"],"repository":{"type":"git","url":"git+https://github.com/aiconnect-cloud/nexus-chat-cli.git"},"description":"Terminal chat client for OpenAI-compatible endpoints","maintainers":[{"name":"ericsantos","email":"ericsantos.net@gmail.com"}],"readme":"# nexus-chat\n\n`nexus-chat` is a terminal chat client for any service that exposes an OpenAI-compatible Chat Completions API. It supports searchable model discovery, streamed responses, terminal markdown, persistent sessions, server-side-history agents, and a pipe-safe one-shot mode.\n\nVersion 0.2.0 requires Node.js 20 or newer. Generic OpenAI-compatible endpoints retain their existing prompts and runtime semantics; configuration now makes one silent discovery probe, and shared API requests identify the v0.2 package in their User-Agent. Nexus Proxy endpoints can additionally use terminal-native discovery and pairing.\n\n## Install\n\n```sh\nnpm install --global @aiconnect/nexus-chat-cli\nnexus-chat --version\n```\n\nTo run a checkout instead:\n\n```sh\nnpm install\nnpm run build\nnode dist/cli.js --help\n```\n\n## Quick start\n\nStart the interactive client:\n\n```sh\nnexus-chat\n```\n\nOn first run, the setup wizard:\n\n1. Asks for an HTTP or HTTPS endpoint, such as `http://localhost:11434`.\n2. Silently checks that endpoint for Nexus Proxy discovery. This unauthenticated `/health` request happens only during first-run setup or `nexus-chat config`.\n3. For a pairing-enabled Nexus Proxy, offers direct pairing, manual-token, and no-token choices. Generic endpoints and proxies with pairing disabled keep the masked optional-token flow; press Enter for servers that need no token.\n4. Discovers models from `/models`. If discovery returns a single model, it is used automatically; with two or more, a searchable picker opens. If discovery is unavailable or empty, enter the model id manually.\n\nThe default `auto` path mode turns the example above into `http://localhost:11434/v1/models` and `http://localhost:11434/v1/chat/completions`. A URL whose path already ends in `/v1` is not changed.\n\nUseful invocations:\n\n```sh\nnexus-chat\nnexus-chat --resume\nnexus-chat --resume 20260811-143002\nnexus-chat --endpoint http://localhost:11434 --model llama3.2\nnexus-chat -p \"Explain this error\"\ncat error.log | nexus-chat -p \"Explain this error\"\nnexus-chat config\n```\n\n`--endpoint`, `--token`, and `--model` override configuration for one run and are never persisted. Precedence is CLI flags, then `NEXUS_CHAT_BASE_URL` / `NEXUS_CHAT_API_KEY`, then the config file, then built-in defaults.\n\n## Nexus Proxy pairing\n\nDuring first-run setup or `nexus-chat config`, the wizard checks the entered endpoint for Nexus Proxy discovery. This check is wizard-only. When the proxy reports that pairing is enabled, choose one of the following:\n\n- **Pair with this proxy**: obtain the pairing password from the proxy operator, verify that you entered the intended endpoint, and enter the password at the masked prompt. The CLI sends it once directly to that endpoint with a 10-second deadline, does not follow redirects, and never stores or prints the password.\n- **Keep current pairing**: available when the stored token belongs to the same trailing-slash-equivalent endpoint and has a valid future expiry. This option makes no pairing request.\n- **Enter an API token manually**: use an existing token instead of pairing.\n- **Continue without a token**: suitable for an endpoint that does not require authentication.\n\nAfter a successful exchange, the issued token is used immediately for the first model-discovery request. The protected default profile stores it as `apiKey`, records `serverType: \"nexus\"`, and records its normalized UTC expiry as `tokenExpiresAt`. The pairing password is never saved. Administrative token identifiers remain proxy-side and are not part of the client-facing pairing response. Manual or absent credentials remove stale pairing expiry metadata.\n\n`serverType` belongs to the detected endpoint, while `tokenExpiresAt` belongs to the stored paired credential. A materially different endpoint override removes both resolved fields without probing. A temporary token override keeps Nexus classification for C3 but removes the resolved expiry; model-only and trailing-slash-equivalent endpoint overrides retain both when the stored credential remains in use. These runtime overrides never rewrite the config file.\n\nPairing occurs only after an explicit wizard selection. Ordinary chat, resume, and one-shot startup never probe, pair, validate, refresh, or re-pair automatically. Interactive chat prints one advisory warning when a stored Nexus token is expired or within 14 days of expiry; run `nexus-chat config` to pair again.\n\n## Nexus Proxy session correlation\n\nFor a detected, endpoint-bound Nexus profile, every Chat Completions request automatically includes `x-openclaw-session-key`. Its value is exactly the request's existing OpenAI-compatible `user` field. This behavior depends only on the resolved `serverType: \"nexus\"` classification: it applies with a paired token, a manual or overridden token, or no token. A materially different endpoint override clears that classification, so the header is not sent and no runtime discovery is attempted.\n\nThe key remains stable across turns, retries, streaming fallback, model changes, and `/retry`. `/new` creates a new key; `--resume` and `/sessions` restore the selected conversation's saved key. Each one-shot `-p` invocation uses a fresh ephemeral key and never creates a session file. The CLI continues sending `body.user` unchanged; Nexus Proxy correlation comes from the explicit header rather than from interpreting `body.user` as the proxy session key.\n\nRequests originally resolved for generic endpoints do not receive this Nexus-specific header. Neither do non-chat operations such as Nexus discovery (`/health`), pairing (`/pair`), or model discovery (`/models`). Correlation adds no probe, token refresh, configuration field, or additional request. An implicit HTTP redirect is not reclassified; the runtime's existing Fetch redirect policy governs header forwarding to its target.\n\n## Interactive controls\n\n- Enter sends the current message; empty messages are ignored.\n- Ctrl+J inserts a newline.\n- Multi-line bracketed paste is kept as one message.\n- Up and Down navigate the current run's input history.\n- Ctrl+C cancels an active response and preserves its partial text as `[interrupted]`. At an idle prompt it clears the input and prints an exit hint.\n- Ctrl+D exits.\n\nResponses stream as plain tokens first. On a TTY, completed responses are erased and rendered again as terminal markdown when `renderMarkdown` is enabled. Reasoning is hidden unless `showReasoning` is enabled.\n\n## Slash commands\n\n| Command | Behavior |\n| --- | --- |\n| `/help` | List all commands. |\n| `/model` | Refresh models and open the searchable picker, with manual fallback. |\n| `/model <id>` | Switch the current session directly to a model id. |\n| `/new` | Start an empty session with a new session UUID. |\n| `/sessions` | List saved sessions newest-first and switch to one. |\n| `/clear` | Clear only the screen; conversation context is unchanged. |\n| `/retry` | Remove the last assistant response locally and regenerate it. |\n| `/exit` | Quit. |\n\nModel changes apply only to the current session. Run `nexus-chat config` to change the default model.\n\n## Configuration\n\nThe configuration file is JSON:\n\n- macOS/Linux: `$XDG_CONFIG_HOME/nexus-chat/config.json`, defaulting to `~/.config/nexus-chat/config.json`\n- Windows: `$XDG_CONFIG_HOME/nexus-chat/config.json` when set, otherwise `%APPDATA%\\\\nexus-chat\\\\config.json`\n\nSessions use `$XDG_DATA_HOME/nexus-chat/sessions/`, defaulting to `~/.local/share/nexus-chat/sessions/` on macOS/Linux and `%LOCALAPPDATA%\\\\nexus-chat\\\\sessions` on Windows. Directories are created with mode `0700` and the config with mode `0600` where the platform supports POSIX permissions.\n\nComplete configuration example:\n\n```json\n{\n  \"profiles\": {\n    \"default\": {\n      \"baseUrl\": \"http://localhost:11434\",\n      \"apiPathMode\": \"auto\",\n      \"model\": \"llama3.2\"\n    }\n  },\n  \"defaults\": {\n    \"historyMode\": \"full\",\n    \"renderMarkdown\": true,\n    \"showReasoning\": false,\n    \"systemPrompt\": null,\n    \"temperature\": null,\n    \"maxTokens\": null\n  },\n  \"models\": {}\n}\n```\n\nProfile fields:\n\n- `baseUrl` is required.\n- `apiKey` is optional. When absent or empty, no `Authorization` header is sent.\n- `apiPathMode` is `auto` or `verbatim`. `auto` appends `/v1` unless the path already ends in `/v1`; `verbatim` treats `baseUrl` as the complete API root.\n- `model` is the default model id.\n- `serverType` is optional managed metadata. The wizard sets it to `\"nexus\"` only for a detected Nexus Proxy at the saved endpoint.\n- `tokenExpiresAt` is optional pairing metadata containing the token expiry instant as a parseable date string. It is retained only while the resolved credential is the stored pairing credential at that endpoint.\n\nFor example, a successfully paired profile can contain:\n\n```json\n{\n  \"baseUrl\": \"https://proxy.example.com\",\n  \"apiPathMode\": \"auto\",\n  \"model\": \"agent-a\",\n  \"apiKey\": \"sk-nexus-…\",\n  \"serverType\": \"nexus\",\n  \"tokenExpiresAt\": \"2026-11-10T12:00:00.000Z\"\n}\n```\n\nTreat `apiKey` as a secret. The containing configuration directory and file use modes `0700` and `0600` respectively where POSIX permissions are supported.\n\nDefault fields:\n\n- `historyMode`: `full` or `lastMessageOnly`.\n- `renderMarkdown`: re-render completed output as terminal markdown on a TTY.\n- `showReasoning`: display `reasoning_content` and a leading `<think>…</think>` block in dim text.\n- `systemPrompt`: a string or `null`.\n- `temperature`: a number or `null`; null is omitted from requests.\n- `maxTokens`: a number or `null`; non-null values are sent as `max_tokens`.\n\nUnknown configuration keys are preserved when the wizard updates the default profile.\n\n### Server-side-history agents (`lastMessageOnly`)\n\nSome model ids represent agents that maintain conversation history on the server. Configure those models to receive only the newest user message:\n\n```json\n{\n  \"profiles\": {\n    \"default\": {\n      \"baseUrl\": \"https://gateway.example.com\",\n      \"apiPathMode\": \"auto\",\n      \"model\": \"my-agent\"\n    }\n  },\n  \"defaults\": {\n    \"historyMode\": \"full\",\n    \"renderMarkdown\": true,\n    \"showReasoning\": false,\n    \"systemPrompt\": \"This is used by ordinary models only.\",\n    \"temperature\": null,\n    \"maxTokens\": null\n  },\n  \"models\": {\n    \"my-agent\": {\n      \"historyMode\": \"lastMessageOnly\"\n    }\n  }\n}\n```\n\nFor `my-agent`, each request contains exactly one user message: the newest one. It contains no system message or prior turns. The complete local transcript is still stored and shown. `/retry` resends only the last user message. Every request includes the standard OpenAI `user` field set to the session's stable UUID; a generic provider may choose to use that field for correlation. For a resolved Nexus profile, correlation instead uses the explicit `x-openclaw-session-key` header described above, with the same value. `/new` creates a new UUID and resuming a session restores its UUID.\n\nWhen a Nexus session key is present and the selected proxy target omits its operator-side `lastMessageOnly` setting, the proxy's automatic mode trims forwarded messages to the latest user message. A proxy operator can explicitly set that target option to `true` (always trim) or `false` (never trim). In the automatic setup, configuring CLI-side `lastMessageOnly` is normally unnecessary, but it remains an explicit local option for other server-side-history agents. The CLI itself has no `auto` history mode: it does not rewrite existing history settings and continues sending the body selected by the configured `full` or `lastMessageOnly` mode.\n\n## One-shot and pipelines\n\n```sh\nnexus-chat -p \"Summarize this\"\ncat report.txt | nexus-chat -p \"Find the risks\"\n```\n\nPiped input is appended after the prompt, separated by a blank line. Assistant text streams plainly to standard output; diagnostics go to standard error. One-shot runs use a fresh ephemeral UUID and never create a session file. They require an existing config, or both `--endpoint` and `--model`.\n\nExit codes are `0` for success, `1` for a runtime or API error, and `2` for invalid usage or missing prerequisites.\n\n## Sessions\n\nSessions are append-oriented JSONL files, one per conversation. The first user message creates the file; `/new` without a message does not. User messages are appended before the request and assistant messages after completion or interruption, so a failed turn keeps the user's text available for `/retry`.\n\n`--resume` opens the most recently active session. `--resume <id>` accepts a unique id prefix. Resuming restores the transcript, model, and stable session UUID.\n\n## Troubleshooting\n\n**Nexus Proxy pairing is disabled**\n\nUse the existing masked manual-token prompt, or ask the proxy operator to enable pairing and rerun `nexus-chat config`. Previously issued tokens may remain usable even while new pairing is disabled.\n\n**Wrong pairing password**\n\nVerify the password with the proxy operator. The CLI permits three human-entered attempts per pairing selection and never retries a password automatically; select pairing again to start a new three-attempt budget.\n\n**Pairing is temporarily locked**\n\nHonor the displayed wait time derived from `Retry-After`, then select pairing again. The wizard does not sleep or retry in the background. If no safe wait time is available, wait before retrying or use a manual token.\n\n**Pairing times out, redirects, or returns an unexpected response**\n\nVerify the endpoint and proxy availability, then retry explicitly. Redirects are rejected to prevent forwarding the pairing password to another origin. You can always continue with a manually issued token.\n\n**A Nexus token expired or was revoked (401/403)**\n\nRun `nexus-chat config` and pair again, or enter a replacement token manually. Expiry warnings and authentication guidance are informational: the CLI never refreshes or re-pairs automatically.\n\n**Authentication failed on a generic endpoint (401/403)**\n\nRun `nexus-chat config` and update the token. Tokens are masked by the prompt and are never printed or logged. Nexus-classified endpoints use the expired-or-revoked guidance above instead of reflecting provider details.\n\n**Cannot reach the endpoint**  \nCheck the hostname, port, protocol, and whether the server is running. For proxies that expose Chat Completions without `/v1`, set `apiPathMode` to `verbatim` by editing the config.\n\n**Model discovery fails**  \nEnter the model id manually in the wizard or with `/model <id>`. A working `/models` route is helpful but not required.\n\n**The model does not support streaming**  \nThe client automatically retries once with `stream: false` when the initial response indicates streaming is unsupported.\n\n**Rate limit or server error**  \nHTTP 429 and 5xx responses, and retryable network failures, are retried twice with 500 ms and 1500 ms backoff. Authentication and model-not-found errors are not retried.\n\n**Conversation exceeds the context window**  \nUse `/new`. The CLI does not truncate or summarize `full` history automatically; proxy-side trimming is the separate, target-controlled behavior described above.\n\n**An agent repeats or duplicates context**  \nConfigure that model with `historyMode: \"lastMessageOnly\"` as shown above.\n\n## Development\n\n```sh\nnpm ci --ignore-scripts\nnpm run build\nnpm run lint\nnpm test\nnpm run smoke\nNEXUS_INTEROP_CANONICAL_PATH=/path/to/INTEROP-v1.md npm --ignore-scripts run release:check\n```\n\nTests include unit coverage and integration tests against an in-process OpenAI-compatible HTTP server. `release:check` is a non-publishing local-preparation gate: it verifies the external canonical contract and exact audited script map, runs the checkout smoke, packs and installs the tarball in isolation, and stops before any tag, push, merge, npm publication, or GitHub Release action. Install the local candidate with `npm ci --ignore-scripts`, then always use the exact `npm --ignore-scripts run release:check` form shown above; the outer flag and explicit guards on nested npm commands prevent lifecycle hooks from running before, after, or inside the audited gate. Neither bare `npm run release:check` nor direct `node scripts/release-check.mjs` execution is release evidence or can establish `locally prepared`. Run the canonical gate with Node 20 on the clean product-evidence source commit. The final review head must be the same commit or differ only by OpenSpec checkbox markers with an identical product fingerprint; strict OpenSpec validation and the existing four-leg Node 20/22 × Ubuntu/macOS CI matrix apply to that final head.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}