@supercollab/cli 0.4.5 → 0.4.7

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 CHANGED
@@ -149,6 +149,27 @@ opening Claude, verify the local MCP handshake:
149
149
  supercollab mcp smoke
150
150
  ```
151
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 same installer is available without typing the command:
160
+
161
+ ```text
162
+ supercollab -> Settings -> Install Claude Code MCP
163
+ ```
164
+
165
+ During first-run setup, choose `Install Claude Code MCP` at the MCP setup step
166
+ to run the installer immediately.
167
+
168
+ The installer first runs the MCP smoke check, then calls Claude Code's own
169
+ `claude mcp add` with absolute Node and CLI paths plus explicit `HOME`, `PATH`,
170
+ and `SUPERCOLLAB_WORKDIR` values. This avoids Homebrew, nvm, shell startup, and
171
+ GUI path differences.
172
+
152
173
  Default server: `https://hyper.polynode.dev`.
153
174
 
154
175
  Local config is stored at `~/.supercollab/config.json` with mode `0600`; the
@@ -8,7 +8,7 @@ import { fileURLToPath } from 'node:url';
8
8
  import * as readlineCore from 'node:readline';
9
9
  import { stdin as input, stdout as output } from 'node:process';
10
10
 
11
- const VERSION = '0.4.5';
11
+ const VERSION = '0.4.7';
12
12
  const CLI_ENTRY = fileURLToPath(import.meta.url);
13
13
  const DEFAULT_SERVER = process.env.SUPERCOLLAB_URL || 'https://hyper.polynode.dev';
14
14
  const DEFAULT_CONFIG = process.env.SUPERCOLLAB_CONFIG || path.join(os.homedir(), '.supercollab', 'config.json');
@@ -61,6 +61,7 @@ Usage:
61
61
  supercollab embeddings status
62
62
  supercollab embeddings warmup
63
63
  supercollab mcp stdio
64
+ supercollab mcp install --client claude-code [--scope local] [--cwd PATH]
64
65
  supercollab mcp print-config --client codex
65
66
  supercollab mcp smoke [--timeout 5000]
66
67
  supercollab config path
@@ -1796,9 +1797,120 @@ function mcpConfigText(client, file, opts = {}) {
1796
1797
  if (client === 'codex') {
1797
1798
  return `[mcp_servers.supercollab]\ncommand = "supercollab"\nargs = ["mcp", "stdio", "--config", "${escaped}"]`;
1798
1799
  }
1800
+ if (client === 'claude-code') {
1801
+ const install = claudeCodeInstallPlan(file, opts);
1802
+ return shellCommand(install.command, install.args);
1803
+ }
1799
1804
  return `supercollab mcp stdio --config "${escaped}"`;
1800
1805
  }
1801
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
+ '--env', `HOME=${env.HOME}`,
1834
+ '--env', `PATH=${env.PATH}`,
1835
+ '--env', `SUPERCOLLAB_WORKDIR=${env.SUPERCOLLAB_WORKDIR}`,
1836
+ '--transport', 'stdio',
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
+
1802
1914
  async function promptSystemCheck(config, file, prompts) {
1803
1915
  const spin = prompts.spinner();
1804
1916
  spin.start('Checking this machine and warming the local BGE model');
@@ -2098,8 +2210,9 @@ async function promptAccountStatus(config, file, prompts) {
2098
2210
 
2099
2211
  async function promptMcpConfig(config, file, prompts) {
2100
2212
  const client = await prompts.select({
2101
- message: 'MCP client config',
2213
+ message: 'MCP setup',
2102
2214
  options: [
2215
+ { value: 'claude-code', label: 'Install Claude Code MCP', hint: 'runs claude mcp add now' },
2103
2216
  { value: 'codex', label: 'Codex', hint: 'TOML config snippet' },
2104
2217
  { value: 'claude', label: 'Claude', hint: 'JSON config snippet' },
2105
2218
  { value: 'manual', label: 'Manual', hint: 'stdio command' },
@@ -2107,9 +2220,48 @@ async function promptMcpConfig(config, file, prompts) {
2107
2220
  ],
2108
2221
  });
2109
2222
  if (prompts.isCancel(client) || client === 'back') return;
2223
+ if (client === 'claude-code') return promptInstallClaudeCode(config, file, prompts);
2110
2224
  prompts.note(mcpConfigText(client, file), `${client} MCP config`);
2111
2225
  }
2112
2226
 
2227
+ async function promptInstallClaudeCode(config, file, prompts, options = {}) {
2228
+ const defaultCwd = path.resolve(String(options.cwd || process.cwd()));
2229
+ const cwd = await prompts.text({
2230
+ message: 'Claude Code project directory',
2231
+ defaultValue: defaultCwd,
2232
+ placeholder: defaultCwd,
2233
+ validate: (value) => fs.existsSync(path.resolve(String(value || ''))) ? undefined : 'Directory does not exist',
2234
+ });
2235
+ if (prompts.isCancel(cwd)) throw new Error('cancelled');
2236
+ let scope = options.scope || null;
2237
+ if (!scope) scope = await prompts.select({
2238
+ message: 'Claude Code MCP scope',
2239
+ options: [
2240
+ { value: 'local', label: 'Local project', hint: 'recommended; private to this project' },
2241
+ { value: 'user', label: 'User', hint: 'available across your projects' },
2242
+ ],
2243
+ });
2244
+ if (prompts.isCancel(scope)) throw new Error('cancelled');
2245
+ const spin = prompts.spinner();
2246
+ spin.start('Installing SuperCollab into Claude Code');
2247
+ try {
2248
+ const result = await installClaudeCodeMcp(config, file, { client: 'claude-code', cwd: String(cwd), scope });
2249
+ spin.stop('Claude Code MCP installed');
2250
+ prompts.note([
2251
+ `Scope: ${result.scope}`,
2252
+ `Project: ${result.cwd}`,
2253
+ `Smoke: ${result.smoke?.ok ? 'ok' : 'failed'}`,
2254
+ result.claude_list?.stdout || '',
2255
+ '',
2256
+ result.next,
2257
+ ].filter(Boolean).join('\n'), 'Claude Code');
2258
+ return result;
2259
+ } catch (err) {
2260
+ spin.stop('Claude Code install failed');
2261
+ throw err;
2262
+ }
2263
+ }
2264
+
2113
2265
  function sessionsFromResponse(data) {
2114
2266
  if (Array.isArray(data)) return data;
2115
2267
  if (Array.isArray(data?.sessions)) return data.sessions;
@@ -2258,7 +2410,8 @@ async function runSettingsMenu(config, file, prompts) {
2258
2410
  options: [
2259
2411
  { value: 'doctor', label: 'System check / install BGE model' },
2260
2412
  { value: 'account', label: 'Account and config status' },
2261
- { value: 'mcp', label: 'Show MCP config' },
2413
+ { value: 'install_claude_code', label: 'Install Claude Code MCP' },
2414
+ { value: 'mcp', label: 'MCP setup/config' },
2262
2415
  { value: 'sessions', label: 'Manage sessions' },
2263
2416
  { value: 'server', label: 'Set server URL' },
2264
2417
  { value: 'back', label: 'Back' },
@@ -2268,6 +2421,7 @@ async function runSettingsMenu(config, file, prompts) {
2268
2421
  try {
2269
2422
  if (action === 'doctor') await promptSystemCheck(config, file, prompts);
2270
2423
  if (action === 'account') await promptAccountStatus(config, file, prompts);
2424
+ if (action === 'install_claude_code') await promptInstallClaudeCode(config, file, prompts);
2271
2425
  if (action === 'mcp') await promptMcpConfig(config, file, prompts);
2272
2426
  if (action === 'sessions') await promptManageSessions(config, prompts);
2273
2427
  if (action === 'server') await promptSetServerUrl(config, file, prompts);
@@ -2342,8 +2496,9 @@ async function runSetupWizard(config, file, opts = {}) {
2342
2496
  const smoke = await runSetupSmoke(config, file, roomId, prompts);
2343
2497
 
2344
2498
  const client = await prompts.select({
2345
- message: 'MCP client config',
2499
+ message: 'MCP setup',
2346
2500
  options: [
2501
+ { value: 'claude-code', label: 'Install Claude Code MCP', hint: 'runs claude mcp add now' },
2347
2502
  { value: 'codex', label: 'Codex', hint: 'TOML config snippet' },
2348
2503
  { value: 'claude', label: 'Claude', hint: 'JSON config snippet' },
2349
2504
  { value: 'manual', label: 'Manual', hint: 'stdio command' },
@@ -2361,7 +2516,9 @@ async function runSetupWizard(config, file, opts = {}) {
2361
2516
  };
2362
2517
  saveConfig(config, file);
2363
2518
 
2364
- if (client !== 'skip') {
2519
+ if (client === 'claude-code') {
2520
+ await promptInstallClaudeCode(config, file, prompts, { cwd: activation?.cwd || process.cwd(), scope: 'local' });
2521
+ } else if (client !== 'skip') {
2365
2522
  prompts.note(mcpConfigText(client, file), `${client} MCP config`);
2366
2523
  }
2367
2524
  prompts.outro(`Ready. Config saved at ${file}`);
@@ -2432,6 +2589,7 @@ async function main() {
2432
2589
  if (sub === 'warmup') return console.log(JSON.stringify(await embeddingWarmup(), null, 2));
2433
2590
  }
2434
2591
  if (cmd === 'mcp' && sub === 'stdio') return runMcp(opts);
2592
+ if (cmd === 'mcp' && sub === 'install') return console.log(JSON.stringify(await installClaudeCodeMcp(config, file, opts), null, 2));
2435
2593
  if (cmd === 'mcp' && sub === 'smoke') return console.log(JSON.stringify(await runMcpSmoke(opts), null, 2));
2436
2594
  if (cmd === 'mcp' && sub === 'print-config') return printCodexConfig(opts);
2437
2595
  throw new Error(`unknown command: ${positionals.join(' ')}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supercollab/cli",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "SuperCollab CLI and MCP bridge for encrypted local-search agent group chat.",
5
5
  "type": "module",
6
6
  "bin": {