Repository navigation
feat(cli): interactive init P2 — clack, vendor CLIs, pinned MCP, update - #161
PatrickSys wants to merge 3 commits into
Conversation
Phase P2 builds on the six-client init work: unified diff previews, tiny-config backups, Claude Desktop detection, npx -y codebase-context@latest launches, daily npm update notice, post-setup indexing with progress, and expanded tests.
…heck Adopt @clack/prompts with --yes --clients and --scope project for CI. Configure Claude, Codex, and VS Code through their CLIs; merge JSON for Cursor and OpenCode only. Pin codebase-context@version by default with update command and opt-in --track-latest. Finish init with MCP initialize smoke test. Add server.json for registry metadata and README install buttons.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7bffa1eaf1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| function frameMessage(body: string): string { | ||
| const bytes = Buffer.byteLength(body, 'utf8'); | ||
| return `Content-Length: ${bytes}\r\n\r\n${body}`; |
There was a problem hiding this comment.
Use newline-delimited JSON for the MCP startup check
Every init run that reaches the default server check will time out even when the server is healthy. The server uses StdioServerTransport in src/index.ts, and the SDK's stdio implementation reads and writes newline-delimited JSON, not Content-Length frames. These headers therefore cause parsing errors, while the response parser below also cannot consume valid SDK responses. Use the SDK client transport or newline-delimited messages in both directions so normal setup can complete without --skip-server-check.
Useful? React with 👍 / 👎.
| args: ['mcp', 'add', 'codebase-context', '--', ...launch.shellCommand], | ||
| cwd: options.scope === 'project' ? rootPath : undefined |
There was a problem hiding this comment.
Honor project scope when registering Codex
With the default --scope project, this still modifies the user's global Codex configuration: changing cwd does not change where codex mcp add writes. The upstream command implementation explicitly describes add as “add a server launcher entry to ~/.codex/config.toml” and writes through config.codex_home. Consequently, initializing another repository replaces the global server's fixed project argument, affecting other Codex projects; meanwhile, update searches the project-local .codex/config.toml and misses the registration. Implement genuine project-scoped registration or explicitly reject that scope instead of silently writing globally.
Useful? React with 👍 / 👎.
| const merged = await _buildMergedMcpContent(generated.path, generated.content, client); | ||
| const existing = await fs.readFile(generated.path, 'utf8').catch(() => null); | ||
| if (existing === null) return 'missing'; | ||
| if (merged.content === existing) return 'unchanged'; |
There was a problem hiding this comment.
Check that the server entry exists before applying an update
Running update with its default client list registers Codebase Context in existing client config files that never contained it. _buildMergedMcpContent deliberately inserts the generated server when the entry is absent, so checking only whether the config file exists allows this branch to write a new registration; the Codex branch has the same behavior through _buildMergedCodexContent. For example, updating an existing Cursor installation also adds the server to an unrelated opencode.json containing only other settings. Inspect the parsed config for an existing Codebase Context entry before merging, and report missing rather than installing it.
Useful? React with 👍 / 👎.
[Medium impact] The changes reach developer setup workflows across several MCP clients. Do not merge until setup and upgrades work through the real executable, backups preserve privacy, and existing index settings remain intact. Findings
SummaryThis PR adds interactive client selection, vendor CLI registration, pinned launches, config previews and backups, indexing, a startup check, and an update command.
Diagram%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Run init] --> B[Check npm version and write notice cache]
B --> C[Choose clients and preview changes]
C --> D{Client kind}
D -->|Cursor or OpenCode| E[Back up and merge JSON]
D -->|Claude, Codex, or VS Code| F[Run vendor command]
E --> G[Build index unless skipped]
F --> G
G --> H[Check MCP startup unless skipped]
I[Run update] --> J[Executable command list]
J --> K[Starts MCP server: update is missing]
Reviews (1) · Last reviewed commit: "feat(cli): clack init via vendor CLIs, p..." · Reviewed by Greptile |
| if (command === 'update') { | ||
| return handleUpdateCli(argv.slice(1)); |
There was a problem hiding this comment.
Upgrade command starts a server
update is added to handleCliCommand, but not to CLI_SUBCOMMANDS in src/index.ts. Running codebase-context update therefore starts the MCP server instead of updating configs. Add the command to the executable's routing list and cover the real entrypoint.
Knowledge Base Used: Command-line workflows
| function frameMessage(body: string): string { | ||
| const bytes = Buffer.byteLength(body, 'utf8'); | ||
| return `Content-Length: ${bytes}\r\n\r\n${body}`; |
There was a problem hiding this comment.
verifyMcpServerStarts sends and expects Content-Length frames, but the MCP SDK's stdio transport uses newline-separated JSON. A healthy server cannot complete this check, so normal init waits for the timeout and reports failure after writing configs. Use the SDK client transport for the handshake and require a successful result.
Knowledge Base Used: MCP service interface
| args: ['mcp', 'add', 'codebase-context', '--', ...launch.shellCommand], | ||
| cwd: options.scope === 'project' ? rootPath : undefined |
There was a problem hiding this comment.
Project setup changes user settings
For Codex, --scope project only changes the working folder. codex mcp add still writes user config, as the setup guide explains. The default project-scoped setup therefore changes a user-wide registration and pins it to this project's folder. Write project config for this scope, or reject the unsupported scope before changing anything.
| const result = spawnSync(file, args, { | ||
| cwd: options.cwd, | ||
| env: options.env ?? process.env, | ||
| encoding: 'utf8', | ||
| shell: false |
There was a problem hiding this comment.
Windows VS Code registration selects code.cmd, then passes it to spawnSync with shell: false. Windows cannot execute a .cmd file this way, so init --clients vscode --scope user fails before registering the server. Use a Windows-aware launcher that preserves argument quoting.
| } catch (error) { | ||
| if (!isFileNotFound(error)) throw error; | ||
| } | ||
| await fs.writeFile(backupPath, existingText, 'utf8'); |
There was a problem hiding this comment.
backupTinyConfig copies the whole existing config without preserving its permissions. If the process uses a 022 umask, updating a 0600 Cursor or OpenCode config creates a readable 0644 backup. If that config contains another server's credentials and its parent folders are accessible, other local users can read them. Create the backup with permissions no broader than the original.
How this was verified: The JSON update path copies the existing file into a new sibling using fs.writeFile without a mode, while the normal config writer explicitly preserves the original mode.
| await fs.writeFile(backupPath, existingText, 'utf8'); | |
| const { mode } = await fs.stat(configPath); | |
| await fs.writeFile(backupPath, existingText, { encoding: 'utf8', flag: 'wx', mode }); |
| } | ||
| if (!latestVersion) return; | ||
|
|
||
| await writeCache(cachePath, { checkedAtMs: nowMs, latestVersion }); |
There was a problem hiding this comment.
If the home cache is unwritable, writeCache rejects outside the guarded network request. handleInitCli awaits it before doing any setup work, so even project-local setup fails. Treat cache-write failures as optional and continue.
| await writeCache(cachePath, { checkedAtMs: nowMs, latestVersion }); | |
| await writeCache(cachePath, { checkedAtMs: nowMs, latestVersion }).catch(() => undefined); |
| const response = await fetchImpl(`https://registry.npmjs.org/${packageName}/latest`, { | ||
| headers: { accept: 'application/json' } | ||
| }); |
There was a problem hiding this comment.
Update check delays local setup
The daily version request has no timeout, yet setup waits for it before showing the wizard. If the registry or proxy stalls, setup waits until the network stack gives up. Give this optional request a short deadline and continue when it expires.
| const response = await fetchImpl(`https://registry.npmjs.org/${packageName}/latest`, { | |
| headers: { accept: 'application/json' } | |
| }); | |
| const response = await fetchImpl(`https://registry.npmjs.org/${packageName}/latest`, { | |
| headers: { accept: 'application/json' }, | |
| signal: AbortSignal.timeout(3_000) | |
| }); |
| } | ||
| if (!(await fs.stat(options.rootPath)).isDirectory()) | ||
| throw new Error(`Project root is not a directory: ${options.rootPath}`); | ||
| await maybePrintInitUpdateNotice(PKG_VERSION, { disabled: options.noUpdateNotice }); |
There was a problem hiding this comment.
--dry-run and --preview still run the notice before their read-only paths. On a successful uncached check, this creates or changes the home cache despite the no-write promise. Disable cache writes for these modes; the current dry-run tests hide this path by disabling the notice entirely.
| await maybePrintInitUpdateNotice(PKG_VERSION, { disabled: options.noUpdateNotice }); | |
| await maybePrintInitUpdateNotice(PKG_VERSION, { | |
| disabled: options.noUpdateNotice || options.dryRun || options.preview | |
| }); |
| Use Node.js 22 or newer. From your project root: | ||
|
|
||
| ```bash | ||
| npx -y codebase-context init |
There was a problem hiding this comment.
Install guide describes unreleased behavior
The new install section recommends unpinned npx ... init as the supported wizard, but the setup guide explicitly says this wizard is absent from the published package. This violates the repository requirement that public docs not claim capabilities that are not shipped and tested. Keep the candidate entrypoint instructions until the matching release is available. This requirement must be satisfied before merging.
Context Used: AGENTS.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| Non-interactive CI: | ||
|
|
||
| ```bash | ||
| npx -y codebase-context init --yes --clients cursor,opencode --scope project --dry-run |
There was a problem hiding this comment.
CI example rejects its clients
The CI example passes cursor,opencode as one client, but the parser accepts only one exact client ID per flag. Copying this command fails with an unsupported-client error. Use --clients cursor --clients opencode, or add comma-separated parsing.
| npx -y codebase-context init --yes --clients cursor,opencode --scope project --dry-run | |
| npx -y codebase-context init --yes --clients cursor --clients opencode --scope project --dry-run |
Remove update command, npm notice cache, backups, indexing, smoke checks, and vendor CLI registration. Init only merges MCP config files for selected clients. Docs label wizard as unreleased candidate. Add dist/npm-pack exec tests.
|
Simplified per Patrick ( Greptile/Codex findings: removed (not patched) — upgrade command, smoke check, vendor CLI scope, Windows |
What / Why
Minimal interactive
initfor the candidate release: detect installed MCP clients, let the user choose tools (@clack/promptsor--clients), merge MCP config files under the project root.--dry-runprints previews and writes nothing.Removed from earlier iterations:
update, npm update notice/cache, config backups, post-init indexing, MCP smoke checks, and vendor CLI registration (sources of most review findings).Release notes
Candidate-only until shipped; README/client-setup say wizard is not in published 2.2.0.
Smoke
node dist/index.js init --yes --clients cursor --dry-run --root .npm pack→ install tarball → same dry-run (seetests/cli-init-built-exec.test.ts)