@supercollab/cli 0.4.5 → 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 CHANGED
@@ -149,6 +149,18 @@ 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 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
+
152
164
  Default server: `https://hyper.polynode.dev`.
153
165
 
154
166
  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.6';
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
+ '--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
+
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');
@@ -2100,6 +2212,7 @@ async function promptMcpConfig(config, file, prompts) {
2100
2212
  const client = await prompts.select({
2101
2213
  message: 'MCP client config',
2102
2214
  options: [
2215
+ { value: 'claude-code', label: 'Claude Code', hint: 'one-command installer' },
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' },
@@ -2110,6 +2223,42 @@ async function promptMcpConfig(config, file, prompts) {
2110
2223
  prompts.note(mcpConfigText(client, file), `${client} MCP config`);
2111
2224
  }
2112
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
+
2113
2262
  function sessionsFromResponse(data) {
2114
2263
  if (Array.isArray(data)) return data;
2115
2264
  if (Array.isArray(data?.sessions)) return data.sessions;
@@ -2258,6 +2407,7 @@ async function runSettingsMenu(config, file, prompts) {
2258
2407
  options: [
2259
2408
  { value: 'doctor', label: 'System check / install BGE model' },
2260
2409
  { value: 'account', label: 'Account and config status' },
2410
+ { value: 'install_claude_code', label: 'Install Claude Code MCP' },
2261
2411
  { value: 'mcp', label: 'Show MCP config' },
2262
2412
  { value: 'sessions', label: 'Manage sessions' },
2263
2413
  { value: 'server', label: 'Set server URL' },
@@ -2268,6 +2418,7 @@ async function runSettingsMenu(config, file, prompts) {
2268
2418
  try {
2269
2419
  if (action === 'doctor') await promptSystemCheck(config, file, prompts);
2270
2420
  if (action === 'account') await promptAccountStatus(config, file, prompts);
2421
+ if (action === 'install_claude_code') await promptInstallClaudeCode(config, file, prompts);
2271
2422
  if (action === 'mcp') await promptMcpConfig(config, file, prompts);
2272
2423
  if (action === 'sessions') await promptManageSessions(config, prompts);
2273
2424
  if (action === 'server') await promptSetServerUrl(config, file, prompts);
@@ -2344,6 +2495,7 @@ async function runSetupWizard(config, file, opts = {}) {
2344
2495
  const client = await prompts.select({
2345
2496
  message: 'MCP client config',
2346
2497
  options: [
2498
+ { value: 'claude-code', label: 'Claude Code', hint: 'one-command installer' },
2347
2499
  { value: 'codex', label: 'Codex', hint: 'TOML config snippet' },
2348
2500
  { value: 'claude', label: 'Claude', hint: 'JSON config snippet' },
2349
2501
  { value: 'manual', label: 'Manual', hint: 'stdio command' },
@@ -2432,6 +2584,7 @@ async function main() {
2432
2584
  if (sub === 'warmup') return console.log(JSON.stringify(await embeddingWarmup(), null, 2));
2433
2585
  }
2434
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));
2435
2588
  if (cmd === 'mcp' && sub === 'smoke') return console.log(JSON.stringify(await runMcpSmoke(opts), null, 2));
2436
2589
  if (cmd === 'mcp' && sub === 'print-config') return printCodexConfig(opts);
2437
2590
  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.6",
4
4
  "description": "SuperCollab CLI and MCP bridge for encrypted local-search agent group chat.",
5
5
  "type": "module",
6
6
  "bin": {