@supercollab/cli 0.4.2 → 0.4.3
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 +17 -3
- package/bin/supercollab.js +410 -4
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -12,9 +12,23 @@ Install:
|
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npm install -g @supercollab/cli
|
|
15
|
+
supercollab
|
|
15
16
|
```
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
Running `supercollab` opens the guided setup flow. It detects your OS/CPU/Node
|
|
19
|
+
runtime, verifies native SQLite and sqlite-vec, downloads and warms the BGE model
|
|
20
|
+
locally, writes the selected local engine into `~/.supercollab/config.json`,
|
|
21
|
+
creates or logs into your account, registers the local agent, creates or joins a
|
|
22
|
+
room, activates a project directory, and prints MCP config.
|
|
23
|
+
|
|
24
|
+
You can also run the checks directly:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
supercollab doctor
|
|
28
|
+
supercollab doctor --json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Manual account setup:
|
|
18
32
|
|
|
19
33
|
```bash
|
|
20
34
|
supercollab register --username your_name
|
|
@@ -83,8 +97,8 @@ hybrid: reciprocal-rank fusion over keyword and vector results
|
|
|
83
97
|
```
|
|
84
98
|
|
|
85
99
|
The hosted SuperCollab service never computes embeddings and never receives the
|
|
86
|
-
room key.
|
|
87
|
-
|
|
100
|
+
room key. Guided setup downloads and verifies the BGE-small ONNX model into the
|
|
101
|
+
local Hugging Face cache. To verify or prewarm the local embedding system:
|
|
88
102
|
|
|
89
103
|
```bash
|
|
90
104
|
supercollab embeddings status
|
package/bin/supercollab.js
CHANGED
|
@@ -3,10 +3,11 @@ 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
7
|
import * as readlineCore from 'node:readline';
|
|
7
8
|
import { stdin as input, stdout as output } from 'node:process';
|
|
8
9
|
|
|
9
|
-
const VERSION = '0.4.
|
|
10
|
+
const VERSION = '0.4.3';
|
|
10
11
|
const DEFAULT_SERVER = process.env.SUPERCOLLAB_URL || 'https://hyper.polynode.dev';
|
|
11
12
|
const DEFAULT_CONFIG = process.env.SUPERCOLLAB_CONFIG || path.join(os.homedir(), '.supercollab', 'config.json');
|
|
12
13
|
const SESSION_TTL_SKEW = 60;
|
|
@@ -33,6 +34,8 @@ function printHelp() {
|
|
|
33
34
|
console.log(`SuperCollab CLI ${VERSION}
|
|
34
35
|
|
|
35
36
|
Usage:
|
|
37
|
+
supercollab setup
|
|
38
|
+
supercollab doctor [--json] [--skip-model]
|
|
36
39
|
supercollab register --username NAME [--password PASS] [--label LABEL]
|
|
37
40
|
supercollab login --username NAME [--password PASS]
|
|
38
41
|
supercollab whoami
|
|
@@ -65,6 +68,7 @@ Options:
|
|
|
65
68
|
Environment:
|
|
66
69
|
SUPERCOLLAB_PASSWORD can provide password non-interactively.
|
|
67
70
|
SUPERCOLLAB_CONFIG can override config path.
|
|
71
|
+
SUPERCOLLAB_MODEL_CACHE can override the local Hugging Face model cache.
|
|
68
72
|
SUPERCOLLAB_WORKDIR sets the local workspace directory for MCP activation checks.
|
|
69
73
|
`);
|
|
70
74
|
}
|
|
@@ -363,6 +367,78 @@ async function doLogin(config, file, opts) {
|
|
|
363
367
|
return { ok: true, username: config.username, user_id: config.userId, config: file };
|
|
364
368
|
}
|
|
365
369
|
|
|
370
|
+
function commandExists(command, args = ['--version']) {
|
|
371
|
+
const result = spawnSync(command, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
372
|
+
return {
|
|
373
|
+
ok: result.status === 0,
|
|
374
|
+
command,
|
|
375
|
+
status: result.status,
|
|
376
|
+
output: String(result.stdout || result.stderr || '').trim().split('\n')[0] || '',
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function installAdvice(profile, checks = {}) {
|
|
381
|
+
const advice = [];
|
|
382
|
+
const platform = profile.platform;
|
|
383
|
+
if (!checks.node_supported?.ok) {
|
|
384
|
+
advice.push('Install Node.js 20 LTS or newer, then reinstall @supercollab/cli.');
|
|
385
|
+
}
|
|
386
|
+
if (!checks.native_sqlite_vec?.ok) {
|
|
387
|
+
if (profile.tools?.npm_ignore_scripts?.output === 'true') {
|
|
388
|
+
advice.push('Your npm config has `ignore-scripts=true`, which prevents native SQLite from installing. Run `npm config set ignore-scripts false`, then reinstall or run `npm rebuild -g better-sqlite3 --ignore-scripts=false`.');
|
|
389
|
+
}
|
|
390
|
+
if (platform === 'darwin') {
|
|
391
|
+
advice.push('Install Apple command line tools with `xcode-select --install`, then run `npm rebuild -g better-sqlite3`.');
|
|
392
|
+
} else if (platform === 'linux') {
|
|
393
|
+
advice.push('Install Python 3, make, and a C/C++ compiler, then run `npm rebuild -g better-sqlite3`.');
|
|
394
|
+
} else if (platform === 'win32') {
|
|
395
|
+
advice.push('Install Microsoft Visual Studio Build Tools with the C++ workload and Python, then run `npm rebuild -g better-sqlite3`.');
|
|
396
|
+
} else {
|
|
397
|
+
advice.push('Install a native build toolchain for your OS, then run `npm rebuild -g better-sqlite3`.');
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (!checks.bge_model?.ok && checks.bge_model?.error) {
|
|
401
|
+
advice.push('Check internet access to Hugging Face model downloads, then run `supercollab embeddings warmup`.');
|
|
402
|
+
}
|
|
403
|
+
return advice;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function detectSystemProfile() {
|
|
407
|
+
const cpus = os.cpus() || [];
|
|
408
|
+
const nodeMajor = Number(process.versions.node.split('.')[0]);
|
|
409
|
+
const tools = {
|
|
410
|
+
npm: commandExists('npm', ['--version']),
|
|
411
|
+
npm_ignore_scripts: commandExists('npm', ['config', 'get', 'ignore-scripts']),
|
|
412
|
+
};
|
|
413
|
+
if (process.platform === 'darwin') {
|
|
414
|
+
tools.xcode_select = commandExists('xcode-select', ['-p']);
|
|
415
|
+
tools.clang = commandExists('clang', ['--version']);
|
|
416
|
+
} else if (process.platform === 'linux') {
|
|
417
|
+
tools.python3 = commandExists('python3', ['--version']);
|
|
418
|
+
tools.make = commandExists('make', ['--version']);
|
|
419
|
+
tools.cc = commandExists('cc', ['--version']);
|
|
420
|
+
tools.gpp = commandExists('g++', ['--version']);
|
|
421
|
+
} else if (process.platform === 'win32') {
|
|
422
|
+
tools.python = commandExists('python', ['--version']);
|
|
423
|
+
tools.node_gyp = commandExists('node-gyp', ['--version']);
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
detected_at: nowIso(),
|
|
427
|
+
cli_version: VERSION,
|
|
428
|
+
platform: process.platform,
|
|
429
|
+
arch: process.arch,
|
|
430
|
+
os_type: os.type(),
|
|
431
|
+
os_release: os.release(),
|
|
432
|
+
hostname: os.hostname(),
|
|
433
|
+
node: process.versions.node,
|
|
434
|
+
node_modules_abi: process.versions.modules,
|
|
435
|
+
node_supported: nodeMajor >= 20,
|
|
436
|
+
cpu_model: cpus[0]?.model || null,
|
|
437
|
+
cpu_count: cpus.length,
|
|
438
|
+
tools,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
366
442
|
let nativeSqlitePromise = null;
|
|
367
443
|
|
|
368
444
|
async function loadNativeSqlite() {
|
|
@@ -1094,14 +1170,14 @@ async function runMcp(opts) {
|
|
|
1094
1170
|
|
|
1095
1171
|
function printCodexConfig(opts) {
|
|
1096
1172
|
const file = configPath(opts);
|
|
1097
|
-
console.log(
|
|
1173
|
+
console.log(mcpConfigText(String(opts.client || 'codex'), file));
|
|
1098
1174
|
}
|
|
1099
1175
|
|
|
1100
1176
|
async function embeddingStatus() {
|
|
1101
1177
|
return {
|
|
1102
1178
|
ok: true,
|
|
1103
1179
|
profile: EMBEDDING_PROFILE,
|
|
1104
|
-
model_download: '
|
|
1180
|
+
model_download: 'installed during setup, or manually via `supercollab embeddings warmup`',
|
|
1105
1181
|
cache_dir: process.env.SUPERCOLLAB_MODEL_CACHE || 'default @huggingface/transformers cache',
|
|
1106
1182
|
};
|
|
1107
1183
|
}
|
|
@@ -1115,14 +1191,344 @@ async function embeddingWarmup() {
|
|
|
1115
1191
|
};
|
|
1116
1192
|
}
|
|
1117
1193
|
|
|
1194
|
+
async function nativeEngineCheck() {
|
|
1195
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'supercollab-doctor-'));
|
|
1196
|
+
const dbPath = path.join(tempDir, 'native.sqlite');
|
|
1197
|
+
let db = null;
|
|
1198
|
+
try {
|
|
1199
|
+
const { Database, sqliteVec } = await loadNativeSqlite();
|
|
1200
|
+
db = new Database(dbPath);
|
|
1201
|
+
sqliteVec.load(db);
|
|
1202
|
+
const version = db.prepare('SELECT vec_version() AS version').get()?.version;
|
|
1203
|
+
db.exec(`CREATE VIRTUAL TABLE vec_check USING vec0(id TEXT PRIMARY KEY, embedding float[${EMBEDDING_DIMS}] distance_metric=cosine)`);
|
|
1204
|
+
db.prepare('INSERT INTO vec_check(id, embedding) VALUES(?, ?)').run('ok', new Float32Array(new Array(EMBEDDING_DIMS).fill(0).map((_, i) => i === 0 ? 1 : 0)));
|
|
1205
|
+
const rows = db.prepare('SELECT id, distance FROM vec_check WHERE embedding MATCH ? AND k = 1').all(new Float32Array(new Array(EMBEDDING_DIMS).fill(0).map((_, i) => i === 0 ? 1 : 0)));
|
|
1206
|
+
if (rows[0]?.id !== 'ok') throw new Error('sqlite-vec query did not return expected row');
|
|
1207
|
+
return {
|
|
1208
|
+
ok: true,
|
|
1209
|
+
engine: 'native-sqlite-vec',
|
|
1210
|
+
sqlite_vec_version: version,
|
|
1211
|
+
dims: EMBEDDING_DIMS,
|
|
1212
|
+
};
|
|
1213
|
+
} catch (err) {
|
|
1214
|
+
return {
|
|
1215
|
+
ok: false,
|
|
1216
|
+
engine: 'native-sqlite-vec',
|
|
1217
|
+
error: err.message || String(err),
|
|
1218
|
+
};
|
|
1219
|
+
} finally {
|
|
1220
|
+
try { db?.close(); } catch {}
|
|
1221
|
+
try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch {}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
async function runDoctor(config, file, opts = {}) {
|
|
1226
|
+
const profile = detectSystemProfile();
|
|
1227
|
+
const checks = {
|
|
1228
|
+
node_supported: {
|
|
1229
|
+
ok: profile.node_supported,
|
|
1230
|
+
version: profile.node,
|
|
1231
|
+
required: '>=20',
|
|
1232
|
+
},
|
|
1233
|
+
native_sqlite_vec: await nativeEngineCheck(),
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
if (!opts['skip-model']) {
|
|
1237
|
+
try {
|
|
1238
|
+
const warmed = await embeddingWarmup();
|
|
1239
|
+
checks.bge_model = {
|
|
1240
|
+
ok: warmed.ok && warmed.dims === EMBEDDING_DIMS,
|
|
1241
|
+
dims: warmed.dims,
|
|
1242
|
+
profile: EMBEDDING_PROFILE,
|
|
1243
|
+
cache_dir: process.env.SUPERCOLLAB_MODEL_CACHE || 'default @huggingface/transformers cache',
|
|
1244
|
+
};
|
|
1245
|
+
} catch (err) {
|
|
1246
|
+
checks.bge_model = {
|
|
1247
|
+
ok: false,
|
|
1248
|
+
profile: EMBEDDING_PROFILE,
|
|
1249
|
+
error: err.message || String(err),
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
} else {
|
|
1253
|
+
checks.bge_model = {
|
|
1254
|
+
ok: null,
|
|
1255
|
+
skipped: true,
|
|
1256
|
+
profile: EMBEDDING_PROFILE,
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
const ok = checks.node_supported.ok && checks.native_sqlite_vec.ok && (checks.bge_model.ok === true || checks.bge_model.skipped === true);
|
|
1261
|
+
const result = {
|
|
1262
|
+
ok,
|
|
1263
|
+
checked_at: nowIso(),
|
|
1264
|
+
system: profile,
|
|
1265
|
+
checks,
|
|
1266
|
+
local_engine: {
|
|
1267
|
+
id: 'native-sqlite-vec',
|
|
1268
|
+
transcript_store: 'better-sqlite3',
|
|
1269
|
+
vector_store: 'sqlite-vec',
|
|
1270
|
+
vector_version: checks.native_sqlite_vec.sqlite_vec_version || null,
|
|
1271
|
+
embedding_profile_id: EMBEDDING_PROFILE.id,
|
|
1272
|
+
},
|
|
1273
|
+
advice: installAdvice(profile, checks),
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1276
|
+
config.systemProfile = profile;
|
|
1277
|
+
config.localEngine = result.local_engine;
|
|
1278
|
+
config.embeddingProfile = EMBEDDING_PROFILE;
|
|
1279
|
+
config.setupChecks = {
|
|
1280
|
+
ok,
|
|
1281
|
+
checked_at: result.checked_at,
|
|
1282
|
+
checks,
|
|
1283
|
+
advice: result.advice,
|
|
1284
|
+
};
|
|
1285
|
+
saveConfig(config, file);
|
|
1286
|
+
return result;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
function printDoctor(result) {
|
|
1290
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
async function loadPrompts() {
|
|
1294
|
+
return import('@clack/prompts');
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
function isConfigured(config) {
|
|
1298
|
+
return Boolean(config.userToken && config.agentId && config.agentPrivateKeyPem);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
function promptRequired(value) {
|
|
1302
|
+
return String(value || '').trim() ? undefined : 'Required';
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
async function ensureAgentForSetup(config, file, prompts) {
|
|
1306
|
+
if (config.agentId && config.agentPrivateKeyPem) return null;
|
|
1307
|
+
const label = await prompts.text({
|
|
1308
|
+
message: 'Name this local agent',
|
|
1309
|
+
placeholder: `${os.hostname()}-agent`,
|
|
1310
|
+
defaultValue: `${os.hostname()}-agent`,
|
|
1311
|
+
validate: promptRequired,
|
|
1312
|
+
});
|
|
1313
|
+
if (prompts.isCancel(label)) throw new Error('cancelled');
|
|
1314
|
+
const agent = await registerAgent(config, String(label));
|
|
1315
|
+
saveConfig(config, file);
|
|
1316
|
+
return agent;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
async function runAuthSetup(config, file, prompts) {
|
|
1320
|
+
if (isConfigured(config)) {
|
|
1321
|
+
const reuse = await prompts.confirm({
|
|
1322
|
+
message: `Use existing login as ${config.username || 'current user'}?`,
|
|
1323
|
+
initialValue: true,
|
|
1324
|
+
});
|
|
1325
|
+
if (prompts.isCancel(reuse)) throw new Error('cancelled');
|
|
1326
|
+
if (reuse) return { reused: true };
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
const mode = await prompts.select({
|
|
1330
|
+
message: 'Account setup',
|
|
1331
|
+
options: [
|
|
1332
|
+
{ value: 'register', label: 'Create account', hint: 'new SuperCollab username' },
|
|
1333
|
+
{ value: 'login', label: 'Log in', hint: 'existing username' },
|
|
1334
|
+
],
|
|
1335
|
+
});
|
|
1336
|
+
if (prompts.isCancel(mode)) throw new Error('cancelled');
|
|
1337
|
+
|
|
1338
|
+
const username = await prompts.text({
|
|
1339
|
+
message: mode === 'register' ? 'Choose a username' : 'Username',
|
|
1340
|
+
validate: promptRequired,
|
|
1341
|
+
});
|
|
1342
|
+
if (prompts.isCancel(username)) throw new Error('cancelled');
|
|
1343
|
+
|
|
1344
|
+
const pass = await prompts.password({
|
|
1345
|
+
message: mode === 'register' ? 'Choose a password' : 'Password',
|
|
1346
|
+
validate: (value) => String(value || '').length >= 8 ? undefined : 'Use at least 8 characters',
|
|
1347
|
+
});
|
|
1348
|
+
if (prompts.isCancel(pass)) throw new Error('cancelled');
|
|
1349
|
+
|
|
1350
|
+
if (mode === 'register') {
|
|
1351
|
+
return doRegister(config, file, { username: String(username), password: String(pass), label: `${os.hostname()}-agent` });
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
const login = await doLogin(config, file, { username: String(username), password: String(pass) });
|
|
1355
|
+
const agent = await ensureAgentForSetup(config, file, prompts);
|
|
1356
|
+
return { ...login, agent_id: agent?.agent_id || config.agentId, fingerprint: agent?.fingerprint || config.agentFingerprint };
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
async function runRoomSetup(config, file, prompts) {
|
|
1360
|
+
const choice = await prompts.select({
|
|
1361
|
+
message: 'Room setup',
|
|
1362
|
+
options: [
|
|
1363
|
+
{ value: 'create', label: 'Create a new room', hint: 'start solo or invite agents later' },
|
|
1364
|
+
{ value: 'join', label: 'Join with private invite', hint: 'paste sci_...sck_...' },
|
|
1365
|
+
{ value: 'skip', label: 'Skip for now', hint: 'set up auth and local engine only' },
|
|
1366
|
+
],
|
|
1367
|
+
});
|
|
1368
|
+
if (prompts.isCancel(choice)) throw new Error('cancelled');
|
|
1369
|
+
if (choice === 'skip') return null;
|
|
1370
|
+
|
|
1371
|
+
if (choice === 'join') {
|
|
1372
|
+
const invite = await prompts.text({
|
|
1373
|
+
message: 'Paste private invite',
|
|
1374
|
+
placeholder: 'sci_....sck_...',
|
|
1375
|
+
validate: promptRequired,
|
|
1376
|
+
});
|
|
1377
|
+
if (prompts.isCancel(invite)) throw new Error('cancelled');
|
|
1378
|
+
const joined = await doRoomJoin(config, file, { invite: String(invite).trim() });
|
|
1379
|
+
return { room_id: joined.room_id || joined.workspace_id, joined };
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
const title = await prompts.text({
|
|
1383
|
+
message: 'Room title',
|
|
1384
|
+
placeholder: 'Launch Room',
|
|
1385
|
+
validate: promptRequired,
|
|
1386
|
+
});
|
|
1387
|
+
if (prompts.isCancel(title)) throw new Error('cancelled');
|
|
1388
|
+
const goal = await prompts.text({
|
|
1389
|
+
message: 'Room goal',
|
|
1390
|
+
placeholder: 'Coordinate agents on this project',
|
|
1391
|
+
validate: promptRequired,
|
|
1392
|
+
});
|
|
1393
|
+
if (prompts.isCancel(goal)) throw new Error('cancelled');
|
|
1394
|
+
const created = await doRoomCreate(config, file, { title: String(title), goal: String(goal) });
|
|
1395
|
+
return { room_id: created.room_id || created.id, created };
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
async function runActivationSetup(config, file, roomId, prompts) {
|
|
1399
|
+
if (!roomId) return null;
|
|
1400
|
+
const shouldActivate = await prompts.confirm({
|
|
1401
|
+
message: 'Activate SuperCollab for a local project directory now?',
|
|
1402
|
+
initialValue: true,
|
|
1403
|
+
});
|
|
1404
|
+
if (prompts.isCancel(shouldActivate)) throw new Error('cancelled');
|
|
1405
|
+
if (!shouldActivate) return null;
|
|
1406
|
+
const cwd = await prompts.text({
|
|
1407
|
+
message: 'Project directory',
|
|
1408
|
+
defaultValue: process.cwd(),
|
|
1409
|
+
placeholder: process.cwd(),
|
|
1410
|
+
validate: (value) => {
|
|
1411
|
+
const resolved = path.resolve(String(value || ''));
|
|
1412
|
+
return fs.existsSync(resolved) ? undefined : 'Directory does not exist';
|
|
1413
|
+
},
|
|
1414
|
+
});
|
|
1415
|
+
if (prompts.isCancel(cwd)) throw new Error('cancelled');
|
|
1416
|
+
return activate(config, file, { room: roomId, cwd: String(cwd) });
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
async function runSetupSmoke(config, file, roomId, prompts) {
|
|
1420
|
+
if (!roomId) return null;
|
|
1421
|
+
const shouldSmoke = await prompts.confirm({
|
|
1422
|
+
message: 'Send a setup note and verify local BGE search?',
|
|
1423
|
+
initialValue: true,
|
|
1424
|
+
});
|
|
1425
|
+
if (prompts.isCancel(shouldSmoke)) throw new Error('cancelled');
|
|
1426
|
+
if (!shouldSmoke) return null;
|
|
1427
|
+
const marker = `setup-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
|
|
1428
|
+
const text = `SuperCollab setup complete on ${os.hostname()} (${marker})`;
|
|
1429
|
+
await doChatSend(config, file, { room: roomId, text, channel: 'agents', kind: 'setup.note' });
|
|
1430
|
+
const search = await doChatSearch(config, file, { room: roomId, query: marker, mode: 'hybrid', limit: 5 });
|
|
1431
|
+
return {
|
|
1432
|
+
ok: search.results.some((row) => String(row.body || '').includes(marker)),
|
|
1433
|
+
marker,
|
|
1434
|
+
search_count: search.results.length,
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
function mcpConfigText(client, file) {
|
|
1439
|
+
const escaped = file.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
|
|
1440
|
+
if (client === 'claude') {
|
|
1441
|
+
return JSON.stringify({
|
|
1442
|
+
mcpServers: {
|
|
1443
|
+
supercollab: {
|
|
1444
|
+
command: 'supercollab',
|
|
1445
|
+
args: ['mcp', 'stdio', '--config', file],
|
|
1446
|
+
},
|
|
1447
|
+
},
|
|
1448
|
+
}, null, 2);
|
|
1449
|
+
}
|
|
1450
|
+
if (client === 'codex') {
|
|
1451
|
+
return `[mcp_servers.supercollab]\ncommand = "supercollab"\nargs = ["mcp", "stdio", "--config", "${escaped}"]`;
|
|
1452
|
+
}
|
|
1453
|
+
return `supercollab mcp stdio --config "${escaped}"`;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
async function runSetupWizard(config, file, opts = {}) {
|
|
1457
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1458
|
+
throw new Error('interactive setup requires a TTY; run `supercollab doctor --json` for non-interactive diagnostics');
|
|
1459
|
+
}
|
|
1460
|
+
const prompts = await loadPrompts();
|
|
1461
|
+
prompts.intro(`SuperCollab ${VERSION}`);
|
|
1462
|
+
try {
|
|
1463
|
+
const spin = prompts.spinner();
|
|
1464
|
+
spin.start('Checking this machine and installing the local BGE model');
|
|
1465
|
+
const doctor = await runDoctor(config, file, {});
|
|
1466
|
+
if (doctor.ok) {
|
|
1467
|
+
spin.stop(`Local engine ready: ${doctor.local_engine.id}, ${doctor.local_engine.vector_version || 'sqlite-vec'}`);
|
|
1468
|
+
} else {
|
|
1469
|
+
spin.stop('Local engine check needs attention');
|
|
1470
|
+
for (const line of doctor.advice || []) prompts.note(line, 'Fix');
|
|
1471
|
+
const cont = await prompts.confirm({ message: 'Continue setup anyway?', initialValue: false });
|
|
1472
|
+
if (prompts.isCancel(cont) || !cont) throw new Error('cancelled');
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
await runAuthSetup(config, file, prompts);
|
|
1476
|
+
const room = await runRoomSetup(config, file, prompts);
|
|
1477
|
+
const roomId = room?.room_id || null;
|
|
1478
|
+
const activation = await runActivationSetup(config, file, roomId, prompts);
|
|
1479
|
+
const smoke = await runSetupSmoke(config, file, roomId, prompts);
|
|
1480
|
+
|
|
1481
|
+
const client = await prompts.select({
|
|
1482
|
+
message: 'MCP client config',
|
|
1483
|
+
options: [
|
|
1484
|
+
{ value: 'codex', label: 'Codex', hint: 'TOML config snippet' },
|
|
1485
|
+
{ value: 'claude', label: 'Claude', hint: 'JSON config snippet' },
|
|
1486
|
+
{ value: 'manual', label: 'Manual', hint: 'stdio command' },
|
|
1487
|
+
{ value: 'skip', label: 'Skip', hint: 'show later with mcp print-config' },
|
|
1488
|
+
],
|
|
1489
|
+
});
|
|
1490
|
+
if (prompts.isCancel(client)) throw new Error('cancelled');
|
|
1491
|
+
|
|
1492
|
+
config.onboarding = {
|
|
1493
|
+
completed_at: nowIso(),
|
|
1494
|
+
cli_version: VERSION,
|
|
1495
|
+
room_id: roomId,
|
|
1496
|
+
activation_root: activation?.cwd || null,
|
|
1497
|
+
smoke,
|
|
1498
|
+
};
|
|
1499
|
+
saveConfig(config, file);
|
|
1500
|
+
|
|
1501
|
+
if (client !== 'skip') {
|
|
1502
|
+
prompts.note(mcpConfigText(client, file), `${client} MCP config`);
|
|
1503
|
+
}
|
|
1504
|
+
prompts.outro(`Ready. Config saved at ${file}`);
|
|
1505
|
+
return { ok: true, room_id: roomId, activation, smoke, config: file };
|
|
1506
|
+
} catch (err) {
|
|
1507
|
+
prompts.cancel(err.message === 'cancelled' ? 'Setup cancelled.' : `Setup stopped: ${err.message}`);
|
|
1508
|
+
throw err;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1118
1512
|
async function main() {
|
|
1119
1513
|
const { positionals, opts } = parse(process.argv.slice(2));
|
|
1120
|
-
if (opts.help
|
|
1514
|
+
if (opts.help) { printHelp(); return; }
|
|
1121
1515
|
const [cmd, sub] = positionals;
|
|
1122
1516
|
const file = configPath(opts);
|
|
1123
1517
|
const config = attachRuntimeConfig(loadConfig(file), file);
|
|
1124
1518
|
config.serverUrl = opts.server || config.serverUrl || DEFAULT_SERVER;
|
|
1125
1519
|
|
|
1520
|
+
if (positionals.length === 0) {
|
|
1521
|
+
if (process.stdin.isTTY && process.stdout.isTTY) return runSetupWizard(config, file, opts);
|
|
1522
|
+
printHelp();
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
if (cmd === 'setup') return runSetupWizard(config, file, opts);
|
|
1526
|
+
if (cmd === 'doctor') {
|
|
1527
|
+
const data = await runDoctor(config, file, opts);
|
|
1528
|
+
if (opts.json) return console.log(JSON.stringify(data, null, 2));
|
|
1529
|
+
printDoctor(data);
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1126
1532
|
if (cmd === 'register') return console.log(JSON.stringify(await doRegister(config, file, opts), null, 2));
|
|
1127
1533
|
if (cmd === 'login') return console.log(JSON.stringify(await doLogin(config, file, opts), null, 2));
|
|
1128
1534
|
if (cmd === 'whoami') return console.log(JSON.stringify(await api(config, 'GET', '/v1/me'), null, 2));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supercollab/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "SuperCollab CLI and MCP bridge for encrypted local-search agent group chat.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"node": ">=20"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
+
"@clack/prompts": "0.11.0",
|
|
17
18
|
"@huggingface/transformers": "3.8.1",
|
|
18
19
|
"better-sqlite3": "12.11.1",
|
|
19
20
|
"sqlite-vec": "0.1.9"
|