@supercollab/cli 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/bin/supercollab.js +271 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,6 +133,34 @@ Print MCP config:
|
|
|
133
133
|
supercollab mcp print-config --client codex
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
+
For Claude Desktop on macOS, generate the config from the project directory you
|
|
137
|
+
want the agent to use:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
cd /path/to/project
|
|
141
|
+
supercollab mcp print-config --client claude
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The Claude config uses absolute Node and CLI paths plus an explicit `PATH` so it
|
|
145
|
+
does not depend on Homebrew, nvm, zsh, or GUI app shell startup behavior. Before
|
|
146
|
+
opening Claude, verify the local MCP handshake:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
supercollab mcp smoke
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
For Claude Code, install the MCP server from the project directory:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
cd /path/to/project
|
|
156
|
+
supercollab mcp install --client claude-code
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The installer first runs the MCP smoke check, then calls Claude Code's own
|
|
160
|
+
`claude mcp add` with absolute Node and CLI paths plus explicit `HOME`, `PATH`,
|
|
161
|
+
and `SUPERCOLLAB_WORKDIR` values. This avoids Homebrew, nvm, shell startup, and
|
|
162
|
+
GUI path differences.
|
|
163
|
+
|
|
136
164
|
Default server: `https://hyper.polynode.dev`.
|
|
137
165
|
|
|
138
166
|
Local config is stored at `~/.supercollab/config.json` with mode `0600`; the
|
package/bin/supercollab.js
CHANGED
|
@@ -3,11 +3,13 @@ import fs from 'node:fs';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
|
-
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
7
8
|
import * as readlineCore from 'node:readline';
|
|
8
9
|
import { stdin as input, stdout as output } from 'node:process';
|
|
9
10
|
|
|
10
|
-
const VERSION = '0.4.
|
|
11
|
+
const VERSION = '0.4.6';
|
|
12
|
+
const CLI_ENTRY = fileURLToPath(import.meta.url);
|
|
11
13
|
const DEFAULT_SERVER = process.env.SUPERCOLLAB_URL || 'https://hyper.polynode.dev';
|
|
12
14
|
const DEFAULT_CONFIG = process.env.SUPERCOLLAB_CONFIG || path.join(os.homedir(), '.supercollab', 'config.json');
|
|
13
15
|
const SESSION_TTL_SKEW = 60;
|
|
@@ -59,7 +61,9 @@ Usage:
|
|
|
59
61
|
supercollab embeddings status
|
|
60
62
|
supercollab embeddings warmup
|
|
61
63
|
supercollab mcp stdio
|
|
64
|
+
supercollab mcp install --client claude-code [--scope local] [--cwd PATH]
|
|
62
65
|
supercollab mcp print-config --client codex
|
|
66
|
+
supercollab mcp smoke [--timeout 5000]
|
|
63
67
|
supercollab config path
|
|
64
68
|
|
|
65
69
|
Options:
|
|
@@ -1169,9 +1173,96 @@ async function runMcp(opts) {
|
|
|
1169
1173
|
}
|
|
1170
1174
|
}
|
|
1171
1175
|
|
|
1176
|
+
function encodeMcpMessage(msg) {
|
|
1177
|
+
const raw = Buffer.from(JSON.stringify(msg));
|
|
1178
|
+
return Buffer.concat([Buffer.from(`Content-Length: ${raw.length}\r\n\r\n`), raw]);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
function parseMcpMessages(state) {
|
|
1182
|
+
const messages = [];
|
|
1183
|
+
while (true) {
|
|
1184
|
+
const headerEnd = state.buffer.indexOf('\r\n\r\n');
|
|
1185
|
+
if (headerEnd < 0) break;
|
|
1186
|
+
const header = state.buffer.subarray(0, headerEnd).toString();
|
|
1187
|
+
const match = header.match(/content-length:\s*(\d+)/i);
|
|
1188
|
+
if (!match) {
|
|
1189
|
+
state.parseError = `missing content-length in MCP output: ${header.slice(0, 200)}`;
|
|
1190
|
+
break;
|
|
1191
|
+
}
|
|
1192
|
+
const length = Number(match[1]);
|
|
1193
|
+
const total = headerEnd + 4 + length;
|
|
1194
|
+
if (state.buffer.length < total) break;
|
|
1195
|
+
const body = state.buffer.subarray(headerEnd + 4, total).toString();
|
|
1196
|
+
state.buffer = state.buffer.subarray(total);
|
|
1197
|
+
try {
|
|
1198
|
+
messages.push(JSON.parse(body));
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
state.parseError = err.message || String(err);
|
|
1201
|
+
break;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
return messages;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
async function runMcpSmoke(opts) {
|
|
1208
|
+
const file = configPath(opts);
|
|
1209
|
+
const timeoutMs = Math.max(1000, Math.min(Number(opts.timeout || 5000), 30000));
|
|
1210
|
+
const args = [CLI_ENTRY, 'mcp', 'stdio', '--config', file];
|
|
1211
|
+
return await new Promise((resolve, reject) => {
|
|
1212
|
+
const child = spawn(process.execPath, args, {
|
|
1213
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1214
|
+
env: {
|
|
1215
|
+
...process.env,
|
|
1216
|
+
PATH: defaultPathEnv(),
|
|
1217
|
+
SUPERCOLLAB_CONFIG: file,
|
|
1218
|
+
},
|
|
1219
|
+
});
|
|
1220
|
+
const state = { buffer: Buffer.alloc(0), parseError: null };
|
|
1221
|
+
const responses = [];
|
|
1222
|
+
let stderr = '';
|
|
1223
|
+
let settled = false;
|
|
1224
|
+
const finish = (err, result) => {
|
|
1225
|
+
if (settled) return;
|
|
1226
|
+
settled = true;
|
|
1227
|
+
clearTimeout(timer);
|
|
1228
|
+
try { child.kill('SIGTERM'); } catch {}
|
|
1229
|
+
if (err) reject(err);
|
|
1230
|
+
else resolve(result);
|
|
1231
|
+
};
|
|
1232
|
+
const timer = setTimeout(() => {
|
|
1233
|
+
finish(new Error(`MCP smoke timed out after ${timeoutMs}ms. stderr: ${stderr.slice(0, 1000)}`));
|
|
1234
|
+
}, timeoutMs);
|
|
1235
|
+
child.on('error', (err) => finish(err));
|
|
1236
|
+
child.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
|
|
1237
|
+
child.stdout.on('data', (chunk) => {
|
|
1238
|
+
state.buffer = Buffer.concat([state.buffer, chunk]);
|
|
1239
|
+
for (const msg of parseMcpMessages(state)) responses.push(msg);
|
|
1240
|
+
if (state.parseError) {
|
|
1241
|
+
finish(new Error(state.parseError));
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
if (responses.some((msg) => msg.id === 1) && responses.some((msg) => msg.id === 2)) {
|
|
1245
|
+
const init = responses.find((msg) => msg.id === 1);
|
|
1246
|
+
const tools = responses.find((msg) => msg.id === 2);
|
|
1247
|
+
finish(null, {
|
|
1248
|
+
ok: Boolean(init?.result?.serverInfo && Array.isArray(tools?.result?.tools)),
|
|
1249
|
+
command: process.execPath,
|
|
1250
|
+
args,
|
|
1251
|
+
config: file,
|
|
1252
|
+
server_info: init?.result?.serverInfo || null,
|
|
1253
|
+
tools: (tools?.result?.tools || []).map((tool) => tool.name),
|
|
1254
|
+
stderr: stderr.trim() || null,
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
child.stdin.write(encodeMcpMessage({ jsonrpc: '2.0', id: 1, method: 'initialize', params: {} }));
|
|
1259
|
+
child.stdin.write(encodeMcpMessage({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} }));
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1172
1263
|
function printCodexConfig(opts) {
|
|
1173
1264
|
const file = configPath(opts);
|
|
1174
|
-
console.log(mcpConfigText(String(opts.client || 'codex'), file));
|
|
1265
|
+
console.log(mcpConfigText(String(opts.client || 'codex'), file, opts));
|
|
1175
1266
|
}
|
|
1176
1267
|
|
|
1177
1268
|
async function embeddingStatus() {
|
|
@@ -1670,14 +1761,35 @@ async function runSetupSmoke(config, file, roomId, prompts) {
|
|
|
1670
1761
|
};
|
|
1671
1762
|
}
|
|
1672
1763
|
|
|
1673
|
-
function
|
|
1764
|
+
function defaultPathEnv() {
|
|
1765
|
+
const parts = [
|
|
1766
|
+
path.dirname(process.execPath),
|
|
1767
|
+
'/opt/homebrew/bin',
|
|
1768
|
+
'/usr/local/bin',
|
|
1769
|
+
'/usr/bin',
|
|
1770
|
+
'/bin',
|
|
1771
|
+
'/usr/sbin',
|
|
1772
|
+
'/sbin',
|
|
1773
|
+
process.env.PATH || '',
|
|
1774
|
+
].flatMap((part) => String(part || '').split(path.delimiter)).filter(Boolean);
|
|
1775
|
+
return Array.from(new Set(parts)).join(path.delimiter);
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
function mcpConfigText(client, file, opts = {}) {
|
|
1779
|
+
const absoluteFile = path.resolve(file);
|
|
1674
1780
|
const escaped = file.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
|
|
1675
1781
|
if (client === 'claude') {
|
|
1782
|
+
const cwd = path.resolve(String(opts.cwd || process.env.SUPERCOLLAB_WORKDIR || process.cwd()));
|
|
1676
1783
|
return JSON.stringify({
|
|
1677
1784
|
mcpServers: {
|
|
1678
1785
|
supercollab: {
|
|
1679
|
-
|
|
1680
|
-
|
|
1786
|
+
type: 'stdio',
|
|
1787
|
+
command: process.execPath,
|
|
1788
|
+
args: [CLI_ENTRY, 'mcp', 'stdio', '--config', absoluteFile],
|
|
1789
|
+
env: {
|
|
1790
|
+
PATH: defaultPathEnv(),
|
|
1791
|
+
SUPERCOLLAB_WORKDIR: cwd,
|
|
1792
|
+
},
|
|
1681
1793
|
},
|
|
1682
1794
|
},
|
|
1683
1795
|
}, null, 2);
|
|
@@ -1685,9 +1797,120 @@ function mcpConfigText(client, file) {
|
|
|
1685
1797
|
if (client === 'codex') {
|
|
1686
1798
|
return `[mcp_servers.supercollab]\ncommand = "supercollab"\nargs = ["mcp", "stdio", "--config", "${escaped}"]`;
|
|
1687
1799
|
}
|
|
1800
|
+
if (client === 'claude-code') {
|
|
1801
|
+
const install = claudeCodeInstallPlan(file, opts);
|
|
1802
|
+
return shellCommand(install.command, install.args);
|
|
1803
|
+
}
|
|
1688
1804
|
return `supercollab mcp stdio --config "${escaped}"`;
|
|
1689
1805
|
}
|
|
1690
1806
|
|
|
1807
|
+
function shellQuote(value) {
|
|
1808
|
+
const text = String(value);
|
|
1809
|
+
if (/^[A-Za-z0-9_./:=@%+-]+$/.test(text)) return text;
|
|
1810
|
+
return `'${text.replaceAll("'", "'\\''")}'`;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
function shellCommand(command, args = []) {
|
|
1814
|
+
return [command, ...args].map(shellQuote).join(' ');
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
function claudeCodeMcpEnv(opts = {}) {
|
|
1818
|
+
const cwd = path.resolve(String(opts.cwd || process.env.SUPERCOLLAB_WORKDIR || process.cwd()));
|
|
1819
|
+
return {
|
|
1820
|
+
HOME: os.homedir(),
|
|
1821
|
+
PATH: defaultPathEnv(),
|
|
1822
|
+
SUPERCOLLAB_WORKDIR: cwd,
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
function claudeCodeInstallPlan(file, opts = {}) {
|
|
1827
|
+
const scope = String(opts.scope || 'local');
|
|
1828
|
+
const claude = String(opts.claude || 'claude');
|
|
1829
|
+
const env = claudeCodeMcpEnv(opts);
|
|
1830
|
+
const args = [
|
|
1831
|
+
'mcp', 'add',
|
|
1832
|
+
'--scope', scope,
|
|
1833
|
+
'--transport', 'stdio',
|
|
1834
|
+
'--env', `HOME=${env.HOME}`,
|
|
1835
|
+
'--env', `PATH=${env.PATH}`,
|
|
1836
|
+
'--env', `SUPERCOLLAB_WORKDIR=${env.SUPERCOLLAB_WORKDIR}`,
|
|
1837
|
+
'supercollab',
|
|
1838
|
+
'--',
|
|
1839
|
+
process.execPath,
|
|
1840
|
+
CLI_ENTRY,
|
|
1841
|
+
'mcp',
|
|
1842
|
+
'stdio',
|
|
1843
|
+
'--config',
|
|
1844
|
+
path.resolve(file),
|
|
1845
|
+
];
|
|
1846
|
+
return { command: claude, args, env, scope, cwd: env.SUPERCOLLAB_WORKDIR };
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
function runProcess(command, args, options = {}) {
|
|
1850
|
+
const result = spawnSync(command, args, {
|
|
1851
|
+
encoding: 'utf8',
|
|
1852
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1853
|
+
...options,
|
|
1854
|
+
});
|
|
1855
|
+
return {
|
|
1856
|
+
ok: result.status === 0,
|
|
1857
|
+
status: result.status,
|
|
1858
|
+
signal: result.signal,
|
|
1859
|
+
stdout: String(result.stdout || '').trim(),
|
|
1860
|
+
stderr: String(result.stderr || '').trim(),
|
|
1861
|
+
error: result.error?.message || null,
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
async function installClaudeCodeMcp(config, file, opts = {}) {
|
|
1866
|
+
const client = String(opts.client || 'claude-code');
|
|
1867
|
+
if (!['claude-code', 'claude'].includes(client)) throw new Error(`unsupported mcp install client: ${client}`);
|
|
1868
|
+
const plan = claudeCodeInstallPlan(file, opts);
|
|
1869
|
+
const dryRun = Boolean(opts['dry-run'] || opts.dryRun);
|
|
1870
|
+
const smoke = await runMcpSmoke({ ...opts, config: file, timeout: opts.timeout || 5000 });
|
|
1871
|
+
const removeArgs = ['mcp', 'remove', 'supercollab', '--scope', plan.scope];
|
|
1872
|
+
|
|
1873
|
+
if (dryRun) {
|
|
1874
|
+
return {
|
|
1875
|
+
ok: true,
|
|
1876
|
+
dry_run: true,
|
|
1877
|
+
client: 'claude-code',
|
|
1878
|
+
smoke,
|
|
1879
|
+
remove_command: shellCommand(plan.command, removeArgs),
|
|
1880
|
+
install_command: shellCommand(plan.command, plan.args),
|
|
1881
|
+
scope: plan.scope,
|
|
1882
|
+
cwd: plan.cwd,
|
|
1883
|
+
config: path.resolve(file),
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
const probe = runProcess(plan.command, ['--version'], { cwd: plan.cwd });
|
|
1888
|
+
if (!probe.ok) {
|
|
1889
|
+
throw new Error(`Claude Code CLI not found or not runnable as ${plan.command}. Install Claude Code first, or pass --claude /absolute/path/to/claude. ${probe.stderr || probe.error || ''}`.trim());
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
runProcess(plan.command, removeArgs, { cwd: plan.cwd });
|
|
1893
|
+
const add = runProcess(plan.command, plan.args, { cwd: plan.cwd });
|
|
1894
|
+
if (!add.ok) {
|
|
1895
|
+
throw new Error(`claude mcp add failed: ${add.stderr || add.stdout || add.error || `exit ${add.status}`}`);
|
|
1896
|
+
}
|
|
1897
|
+
const list = runProcess(plan.command, ['mcp', 'list'], { cwd: plan.cwd });
|
|
1898
|
+
const get = runProcess(plan.command, ['mcp', 'get', 'supercollab'], { cwd: plan.cwd });
|
|
1899
|
+
return {
|
|
1900
|
+
ok: true,
|
|
1901
|
+
client: 'claude-code',
|
|
1902
|
+
scope: plan.scope,
|
|
1903
|
+
cwd: plan.cwd,
|
|
1904
|
+
config: path.resolve(file),
|
|
1905
|
+
smoke,
|
|
1906
|
+
install_command: shellCommand(plan.command, plan.args),
|
|
1907
|
+
claude_add: { stdout: add.stdout, stderr: add.stderr },
|
|
1908
|
+
claude_list: { ok: list.ok, stdout: list.stdout, stderr: list.stderr },
|
|
1909
|
+
claude_get: { ok: get.ok, stdout: get.stdout, stderr: get.stderr },
|
|
1910
|
+
next: 'Start Claude Code in this project, or run /mcp inside Claude Code and confirm supercollab is connected.',
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1691
1914
|
async function promptSystemCheck(config, file, prompts) {
|
|
1692
1915
|
const spin = prompts.spinner();
|
|
1693
1916
|
spin.start('Checking this machine and warming the local BGE model');
|
|
@@ -1989,6 +2212,7 @@ async function promptMcpConfig(config, file, prompts) {
|
|
|
1989
2212
|
const client = await prompts.select({
|
|
1990
2213
|
message: 'MCP client config',
|
|
1991
2214
|
options: [
|
|
2215
|
+
{ value: 'claude-code', label: 'Claude Code', hint: 'one-command installer' },
|
|
1992
2216
|
{ value: 'codex', label: 'Codex', hint: 'TOML config snippet' },
|
|
1993
2217
|
{ value: 'claude', label: 'Claude', hint: 'JSON config snippet' },
|
|
1994
2218
|
{ value: 'manual', label: 'Manual', hint: 'stdio command' },
|
|
@@ -1999,6 +2223,42 @@ async function promptMcpConfig(config, file, prompts) {
|
|
|
1999
2223
|
prompts.note(mcpConfigText(client, file), `${client} MCP config`);
|
|
2000
2224
|
}
|
|
2001
2225
|
|
|
2226
|
+
async function promptInstallClaudeCode(config, file, prompts) {
|
|
2227
|
+
const cwd = await prompts.text({
|
|
2228
|
+
message: 'Claude Code project directory',
|
|
2229
|
+
defaultValue: process.cwd(),
|
|
2230
|
+
placeholder: process.cwd(),
|
|
2231
|
+
validate: (value) => fs.existsSync(path.resolve(String(value || ''))) ? undefined : 'Directory does not exist',
|
|
2232
|
+
});
|
|
2233
|
+
if (prompts.isCancel(cwd)) throw new Error('cancelled');
|
|
2234
|
+
const scope = await prompts.select({
|
|
2235
|
+
message: 'Claude Code MCP scope',
|
|
2236
|
+
options: [
|
|
2237
|
+
{ value: 'local', label: 'Local project', hint: 'recommended; private to this project' },
|
|
2238
|
+
{ value: 'user', label: 'User', hint: 'available across your projects' },
|
|
2239
|
+
],
|
|
2240
|
+
});
|
|
2241
|
+
if (prompts.isCancel(scope)) throw new Error('cancelled');
|
|
2242
|
+
const spin = prompts.spinner();
|
|
2243
|
+
spin.start('Installing SuperCollab into Claude Code');
|
|
2244
|
+
try {
|
|
2245
|
+
const result = await installClaudeCodeMcp(config, file, { client: 'claude-code', cwd: String(cwd), scope });
|
|
2246
|
+
spin.stop('Claude Code MCP installed');
|
|
2247
|
+
prompts.note([
|
|
2248
|
+
`Scope: ${result.scope}`,
|
|
2249
|
+
`Project: ${result.cwd}`,
|
|
2250
|
+
`Smoke: ${result.smoke?.ok ? 'ok' : 'failed'}`,
|
|
2251
|
+
result.claude_list?.stdout || '',
|
|
2252
|
+
'',
|
|
2253
|
+
result.next,
|
|
2254
|
+
].filter(Boolean).join('\n'), 'Claude Code');
|
|
2255
|
+
return result;
|
|
2256
|
+
} catch (err) {
|
|
2257
|
+
spin.stop('Claude Code install failed');
|
|
2258
|
+
throw err;
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2002
2262
|
function sessionsFromResponse(data) {
|
|
2003
2263
|
if (Array.isArray(data)) return data;
|
|
2004
2264
|
if (Array.isArray(data?.sessions)) return data.sessions;
|
|
@@ -2147,6 +2407,7 @@ async function runSettingsMenu(config, file, prompts) {
|
|
|
2147
2407
|
options: [
|
|
2148
2408
|
{ value: 'doctor', label: 'System check / install BGE model' },
|
|
2149
2409
|
{ value: 'account', label: 'Account and config status' },
|
|
2410
|
+
{ value: 'install_claude_code', label: 'Install Claude Code MCP' },
|
|
2150
2411
|
{ value: 'mcp', label: 'Show MCP config' },
|
|
2151
2412
|
{ value: 'sessions', label: 'Manage sessions' },
|
|
2152
2413
|
{ value: 'server', label: 'Set server URL' },
|
|
@@ -2157,6 +2418,7 @@ async function runSettingsMenu(config, file, prompts) {
|
|
|
2157
2418
|
try {
|
|
2158
2419
|
if (action === 'doctor') await promptSystemCheck(config, file, prompts);
|
|
2159
2420
|
if (action === 'account') await promptAccountStatus(config, file, prompts);
|
|
2421
|
+
if (action === 'install_claude_code') await promptInstallClaudeCode(config, file, prompts);
|
|
2160
2422
|
if (action === 'mcp') await promptMcpConfig(config, file, prompts);
|
|
2161
2423
|
if (action === 'sessions') await promptManageSessions(config, prompts);
|
|
2162
2424
|
if (action === 'server') await promptSetServerUrl(config, file, prompts);
|
|
@@ -2233,6 +2495,7 @@ async function runSetupWizard(config, file, opts = {}) {
|
|
|
2233
2495
|
const client = await prompts.select({
|
|
2234
2496
|
message: 'MCP client config',
|
|
2235
2497
|
options: [
|
|
2498
|
+
{ value: 'claude-code', label: 'Claude Code', hint: 'one-command installer' },
|
|
2236
2499
|
{ value: 'codex', label: 'Codex', hint: 'TOML config snippet' },
|
|
2237
2500
|
{ value: 'claude', label: 'Claude', hint: 'JSON config snippet' },
|
|
2238
2501
|
{ value: 'manual', label: 'Manual', hint: 'stdio command' },
|
|
@@ -2321,6 +2584,8 @@ async function main() {
|
|
|
2321
2584
|
if (sub === 'warmup') return console.log(JSON.stringify(await embeddingWarmup(), null, 2));
|
|
2322
2585
|
}
|
|
2323
2586
|
if (cmd === 'mcp' && sub === 'stdio') return runMcp(opts);
|
|
2587
|
+
if (cmd === 'mcp' && sub === 'install') return console.log(JSON.stringify(await installClaudeCodeMcp(config, file, opts), null, 2));
|
|
2588
|
+
if (cmd === 'mcp' && sub === 'smoke') return console.log(JSON.stringify(await runMcpSmoke(opts), null, 2));
|
|
2324
2589
|
if (cmd === 'mcp' && sub === 'print-config') return printCodexConfig(opts);
|
|
2325
2590
|
throw new Error(`unknown command: ${positionals.join(' ')}`);
|
|
2326
2591
|
}
|