fivocell 4.2.4 → 4.3.1
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/dist/__tests__/repl.test.d.ts +2 -0
- package/dist/__tests__/repl.test.d.ts.map +1 -0
- package/dist/__tests__/repl.test.js +77 -0
- package/dist/__tests__/repl.test.js.map +1 -0
- package/dist/__tests__/setup.test.d.ts +2 -0
- package/dist/__tests__/setup.test.d.ts.map +1 -0
- package/dist/__tests__/setup.test.js +186 -0
- package/dist/__tests__/setup.test.js.map +1 -0
- package/dist/cli-repl.d.ts +27 -0
- package/dist/cli-repl.d.ts.map +1 -0
- package/dist/cli-repl.js +65 -0
- package/dist/cli-repl.js.map +1 -0
- package/dist/cli.js +348 -53
- package/dist/cli.js.map +1 -1
- package/dist/core/setup.d.ts +53 -0
- package/dist/core/setup.d.ts.map +1 -0
- package/dist/core/setup.js +371 -0
- package/dist/core/setup.js.map +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -45,6 +45,7 @@ const figlet_1 = __importDefault(require("figlet"));
|
|
|
45
45
|
const gradient_string_1 = __importDefault(require("gradient-string"));
|
|
46
46
|
const database_1 = require("./core/database");
|
|
47
47
|
const layers_1 = require("./layers");
|
|
48
|
+
const cli_repl_1 = require("./cli-repl");
|
|
48
49
|
(0, database_1.initializeDatabase)();
|
|
49
50
|
const C = {
|
|
50
51
|
primary: chalk_1.default.hex('#FF6B35'),
|
|
@@ -99,6 +100,13 @@ switch (cmd) {
|
|
|
99
100
|
case 'stop':
|
|
100
101
|
doStop();
|
|
101
102
|
break;
|
|
103
|
+
case 'setup':
|
|
104
|
+
doSetup().catch((e) => {
|
|
105
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
106
|
+
console.log(C.warn(' Setup crashed: ' + msg));
|
|
107
|
+
process.exit(1);
|
|
108
|
+
});
|
|
109
|
+
break;
|
|
102
110
|
case 'scan':
|
|
103
111
|
doScan();
|
|
104
112
|
break;
|
|
@@ -132,13 +140,24 @@ switch (cmd) {
|
|
|
132
140
|
case 'mcp-config':
|
|
133
141
|
doMcpConfig();
|
|
134
142
|
break;
|
|
143
|
+
case 'repl':
|
|
144
|
+
case '-i':
|
|
145
|
+
case '--interactive':
|
|
146
|
+
doRepl();
|
|
147
|
+
break;
|
|
135
148
|
case 'help':
|
|
136
149
|
case '--help':
|
|
137
150
|
case '-h':
|
|
138
151
|
doHelp();
|
|
139
152
|
break;
|
|
140
153
|
default:
|
|
141
|
-
|
|
154
|
+
// If no command and TTY → open interactive REPL; else show status (safe default for scripts/automation)
|
|
155
|
+
if (!cmd && process.stdin.isTTY) {
|
|
156
|
+
doRepl();
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
doStatus();
|
|
160
|
+
}
|
|
142
161
|
break;
|
|
143
162
|
}
|
|
144
163
|
// ─── cell start ─────────────────────────────────────────────────────────────
|
|
@@ -152,6 +171,37 @@ function doStart() {
|
|
|
152
171
|
firstRunGreeting();
|
|
153
172
|
}
|
|
154
173
|
catch { }
|
|
174
|
+
// 1. Check if daemon is already running (cheap /health probe) — if so,
|
|
175
|
+
// skip restart and just print status. This makes `cell start` re-run safe.
|
|
176
|
+
const http = require('http');
|
|
177
|
+
const probeReq = http.request({
|
|
178
|
+
hostname: '127.0.0.1', port: 9876, path: '/health', method: 'GET', timeout: 1000,
|
|
179
|
+
}, (res) => {
|
|
180
|
+
if (res.statusCode === 200) {
|
|
181
|
+
let data = '';
|
|
182
|
+
res.on('data', (chunk) => { data += chunk ? chunk.toString() : ''; });
|
|
183
|
+
res.on('end', () => {
|
|
184
|
+
let up = 0;
|
|
185
|
+
try {
|
|
186
|
+
up = JSON.parse(data).uptimeSeconds || 0;
|
|
187
|
+
}
|
|
188
|
+
catch { }
|
|
189
|
+
const upH = Math.floor(up / 3600);
|
|
190
|
+
const upM = Math.floor((up % 3600) / 60);
|
|
191
|
+
const upStr = upH > 0 ? `${upH}h ${upM}m` : upM > 0 ? `${upM}m` : `${up}s`;
|
|
192
|
+
console.log(C.success(` Daemon: already running on port 9876 (uptime ${upStr})`));
|
|
193
|
+
printProjectStatusAndExit();
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
startDaemon();
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
probeReq.on('error', () => { startDaemon(); });
|
|
201
|
+
probeReq.on('timeout', () => { probeReq.destroy(); startDaemon(); });
|
|
202
|
+
probeReq.end();
|
|
203
|
+
}
|
|
204
|
+
function startDaemon() {
|
|
155
205
|
// 1. Kill any stale daemon (clean restart)
|
|
156
206
|
try {
|
|
157
207
|
const { stopDaemon } = require('./daemon/lifecycle');
|
|
@@ -196,64 +246,45 @@ function doStart() {
|
|
|
196
246
|
console.log(C.dim(' Daemon + MCP: http://localhost:9876'));
|
|
197
247
|
console.log(C.dim(' MCP endpoint: POST http://localhost:9876/mcp'));
|
|
198
248
|
console.log();
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
catch { /* ignore */ }
|
|
227
|
-
console.log();
|
|
228
|
-
console.log(C.dim(' Next: cell scan (scan codebase + build layers)'));
|
|
229
|
-
console.log(C.dim(' Then: cell status'));
|
|
230
|
-
console.log();
|
|
231
|
-
process.exit(0);
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
req.on('error', () => {
|
|
235
|
-
console.log(C.dim(' Next: cell scan (scan codebase + build layers)'));
|
|
236
|
-
console.log(C.dim(' Then: cell status'));
|
|
237
|
-
console.log();
|
|
238
|
-
process.exit(0);
|
|
239
|
-
});
|
|
240
|
-
req.setTimeout(3000, () => { req.destroy(); process.exit(0); });
|
|
241
|
-
req.write(body);
|
|
242
|
-
req.end();
|
|
243
|
-
return;
|
|
249
|
+
// Give the daemon a moment to bind to the port, then print project status.
|
|
250
|
+
setTimeout(() => { printProjectStatusAndExit(); }, 500);
|
|
251
|
+
}
|
|
252
|
+
catch (e) {
|
|
253
|
+
console.log(C.warn(' Start failed: ' + String(e)));
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function printProjectStatusAndExit() {
|
|
258
|
+
try {
|
|
259
|
+
const { detectProject, checkScanState, checkWatchState, checkMcpState, isDaemonRunning } = require('./core/setup');
|
|
260
|
+
const httpMod = require('http');
|
|
261
|
+
const project = detectProject(process.cwd());
|
|
262
|
+
const scan = checkScanState(process.cwd());
|
|
263
|
+
const watch = checkWatchState(project.name);
|
|
264
|
+
const mcp = checkMcpState(os.homedir());
|
|
265
|
+
console.log();
|
|
266
|
+
console.log(C.bold(' Project:'), C.num(project.name), C.dim(`(${project.source})`));
|
|
267
|
+
console.log();
|
|
268
|
+
console.log(` ${scan.done ? C.success('OK') : C.dim('--')} scan: ${scan.reason}`);
|
|
269
|
+
console.log(` ${watch.done ? C.success('OK') : C.dim('--')} watch: ${watch.reason}`);
|
|
270
|
+
console.log(` ${mcp.done === mcp.total ? C.success('OK') : C.dim('--')} mcp: ${mcp.done}/${mcp.total} IDEs wired${mcp.needsWrite.length > 0 ? C.warn(' (need: ' + mcp.needsWrite.join(', ') + ')') : ''}`);
|
|
271
|
+
console.log();
|
|
272
|
+
const allReady = scan.done && watch.done && mcp.done === mcp.total;
|
|
273
|
+
if (allReady) {
|
|
274
|
+
console.log(C.dim(' Cell is fully ready. Use @cell in your AI chat.'));
|
|
244
275
|
}
|
|
245
|
-
|
|
246
|
-
|
|
276
|
+
else {
|
|
277
|
+
console.log(C.primary(' Run: cell setup (one-time, idempotent) to finish setup'));
|
|
247
278
|
}
|
|
279
|
+
console.log();
|
|
280
|
+
process.exit(0);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
248
283
|
console.log(C.dim(' Next: cell scan (scan codebase + build layers)'));
|
|
249
284
|
console.log(C.dim(' Then: cell status'));
|
|
250
285
|
console.log();
|
|
251
286
|
process.exit(0);
|
|
252
287
|
}
|
|
253
|
-
catch (e) {
|
|
254
|
-
console.log(C.warn(' Start failed: ' + String(e)));
|
|
255
|
-
process.exit(1);
|
|
256
|
-
}
|
|
257
288
|
}
|
|
258
289
|
// ─── cell stop ──────────────────────────────────────────────────────────────
|
|
259
290
|
function doStop() {
|
|
@@ -401,6 +432,129 @@ function doScan() {
|
|
|
401
432
|
console.log();
|
|
402
433
|
}
|
|
403
434
|
}
|
|
435
|
+
// ─── cell setup (idempotent one-time project setup) ─────────────────────────
|
|
436
|
+
/**
|
|
437
|
+
* One-command setup for a new project. Runs scan + watcher + MCP config
|
|
438
|
+
* with full idempotency — safe to re-run any number of times. Skips steps
|
|
439
|
+
* that are already done and only does the missing pieces.
|
|
440
|
+
*
|
|
441
|
+
* Flags:
|
|
442
|
+
* --project <name> Override project name (default: package.json name or folder basename)
|
|
443
|
+
* --no-rules Skip writing AGENTS.md / .cursorrules
|
|
444
|
+
* --force-rules Overwrite existing AGENTS.md / .cursorrules
|
|
445
|
+
* --skip-scan Don't run scan
|
|
446
|
+
* --skip-watch Don't start watcher
|
|
447
|
+
* --skip-mcp Don't write MCP configs
|
|
448
|
+
* --dir <path> Watch dir (default: cwd)
|
|
449
|
+
* --dry-run Print what would happen, don't change anything
|
|
450
|
+
*/
|
|
451
|
+
async function doSetup() {
|
|
452
|
+
const banner = figlet_1.default.textSync('CELL', { font: 'ANSI Shadow', horizontalLayout: 'fitted' });
|
|
453
|
+
console.log((0, gradient_string_1.default)(['#FF6B35', '#FFAB91'])(banner));
|
|
454
|
+
console.log(C.dim(' Setting up project (idempotent)...\n'));
|
|
455
|
+
// ─── Flag parsing ─────────────────────────────────────────────────────
|
|
456
|
+
const noRules = args.includes('--no-rules');
|
|
457
|
+
const forceRules = args.includes('--force-rules');
|
|
458
|
+
const skipScan = args.includes('--skip-scan');
|
|
459
|
+
const skipWatch = args.includes('--skip-watch');
|
|
460
|
+
const skipMcp = args.includes('--skip-mcp');
|
|
461
|
+
const dryRun = args.includes('--dry-run');
|
|
462
|
+
const projectIdx = args.indexOf('--project');
|
|
463
|
+
const projectName = projectIdx > 0 ? args[projectIdx + 1] : undefined;
|
|
464
|
+
const dirIdx = args.indexOf('--dir');
|
|
465
|
+
const dir = dirIdx > 0 ? args[dirIdx + 1] : undefined;
|
|
466
|
+
try {
|
|
467
|
+
const { runSetup, detectProject, checkScanState, checkWatchState, checkMcpState, isDaemonRunning, } = require('./core/setup');
|
|
468
|
+
const cwd = process.cwd();
|
|
469
|
+
const project = detectProject(cwd, projectName);
|
|
470
|
+
console.log(C.bold(` Project: ${C.num(project.name)}`));
|
|
471
|
+
console.log(C.dim(` Source: ${project.source}${project.packageJsonPath ? ' (' + project.packageJsonPath + ')' : ''}`));
|
|
472
|
+
console.log(C.dim(` Cwd: ${cwd}`));
|
|
473
|
+
if (dryRun)
|
|
474
|
+
console.log(C.warn(' Mode: DRY RUN (no changes will be made)'));
|
|
475
|
+
console.log();
|
|
476
|
+
// ─── Daemon check ────────────────────────────────────────────────────
|
|
477
|
+
const daemonUp = await isDaemonRunning();
|
|
478
|
+
if (daemonUp) {
|
|
479
|
+
console.log(C.success(' [daemon] running on http://localhost:9876'));
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
console.log(C.warn(' [daemon] not running — watcher will be skipped'));
|
|
483
|
+
console.log(C.dim(' run `cell start` to bring it up, then re-run `cell setup`'));
|
|
484
|
+
}
|
|
485
|
+
console.log();
|
|
486
|
+
// ─── Pre-flight state report ────────────────────────────────────────
|
|
487
|
+
if (!dryRun) {
|
|
488
|
+
const scan = checkScanState(cwd);
|
|
489
|
+
const watch = daemonUp ? checkWatchState(project.name) : { done: false, reason: 'daemon not running' };
|
|
490
|
+
const mcp = checkMcpState(os.homedir());
|
|
491
|
+
console.log(C.bold(' Pre-flight:'));
|
|
492
|
+
console.log(` scan: ${scan.done ? C.success('done') : C.dim('missing')} (${scan.reason})`);
|
|
493
|
+
console.log(` watch: ${watch.done ? C.success('active') : C.dim('inactive')} (${watch.reason})`);
|
|
494
|
+
console.log(` mcp: ${mcp.done}/${mcp.total} IDEs wired${mcp.needsWrite.length > 0 ? C.warn(' (need: ' + mcp.needsWrite.join(', ') + ')') : ''}`);
|
|
495
|
+
console.log();
|
|
496
|
+
const allDone = scan.done && watch.done && mcp.done === mcp.total;
|
|
497
|
+
if (allDone) {
|
|
498
|
+
console.log(C.success(' Already fully set up — nothing to do.'));
|
|
499
|
+
console.log(C.dim(' Tip: re-run anytime; it is safe and idempotent.'));
|
|
500
|
+
console.log();
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
// ─── Run setup ───────────────────────────────────────────────────────
|
|
505
|
+
const start = Date.now();
|
|
506
|
+
const result = await runSetup({
|
|
507
|
+
cwd,
|
|
508
|
+
homedir: os.homedir(),
|
|
509
|
+
noRules,
|
|
510
|
+
forceRules,
|
|
511
|
+
projectName,
|
|
512
|
+
skipScan,
|
|
513
|
+
skipWatch: skipWatch || !daemonUp,
|
|
514
|
+
skipMcp,
|
|
515
|
+
dir,
|
|
516
|
+
dryRun,
|
|
517
|
+
});
|
|
518
|
+
const ms = Date.now() - start;
|
|
519
|
+
// ─── Result summary ─────────────────────────────────────────────────
|
|
520
|
+
console.log(C.bold(' Result:'));
|
|
521
|
+
console.log(` ${result.scan.done ? C.success('OK') : C.warn('FAIL')} scan: ${result.scan.reason}`);
|
|
522
|
+
console.log(` ${result.watch.done ? C.success('OK') : C.warn('FAIL')} watch: ${result.watch.reason}`);
|
|
523
|
+
const mcpOk = result.mcp.done === result.mcp.total;
|
|
524
|
+
console.log(` ${mcpOk ? C.success('OK') : C.warn('PARTIAL')} mcp: ${result.mcp.done}/${result.mcp.total} IDEs wired${result.mcp.written.length > 0 ? ' (' + result.mcp.written.length + ' just written)' : ''}`);
|
|
525
|
+
console.log();
|
|
526
|
+
if (result.warnings.length > 0) {
|
|
527
|
+
console.log(C.warn(' Warnings:'));
|
|
528
|
+
for (const w of result.warnings)
|
|
529
|
+
console.log(C.warn(` ! ${w}`));
|
|
530
|
+
console.log();
|
|
531
|
+
}
|
|
532
|
+
if (result.errors.length > 0) {
|
|
533
|
+
console.log(C.warn(' Errors:'));
|
|
534
|
+
for (const e of result.errors)
|
|
535
|
+
console.log(C.warn(` x ${e}`));
|
|
536
|
+
console.log();
|
|
537
|
+
}
|
|
538
|
+
// ─── Final verdict ──────────────────────────────────────────────────
|
|
539
|
+
const allGood = result.scan.done && result.watch.done && mcpOk && result.errors.length === 0;
|
|
540
|
+
if (allGood) {
|
|
541
|
+
console.log(C.success(` Setup complete in ${ms}ms. Cell is ready.`));
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
console.log(C.warn(` Setup finished in ${ms}ms with warnings (see above).`));
|
|
545
|
+
}
|
|
546
|
+
console.log();
|
|
547
|
+
console.log(C.dim(' Run: cell status Check what Cell knows about you'));
|
|
548
|
+
console.log(C.dim(' Run: cell context Inject @cell block into your AI prompt'));
|
|
549
|
+
console.log();
|
|
550
|
+
}
|
|
551
|
+
catch (e) {
|
|
552
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
553
|
+
console.log(C.warn(' Setup failed: ' + msg));
|
|
554
|
+
console.log();
|
|
555
|
+
}
|
|
556
|
+
process.exit(0);
|
|
557
|
+
}
|
|
404
558
|
// ─── cell status ────────────────────────────────────────────────────────────
|
|
405
559
|
function semiLabel(s) {
|
|
406
560
|
if (s === 'with semicolons' || s === 'always')
|
|
@@ -1362,14 +1516,154 @@ function doMcpConfig() {
|
|
|
1362
1516
|
console.log(C.dim(' Make sure `cell start` is running for tools to connect.'));
|
|
1363
1517
|
console.log();
|
|
1364
1518
|
}
|
|
1519
|
+
// ─── cell repl (interactive mode with tab completion) ────────────────────────
|
|
1520
|
+
/**
|
|
1521
|
+
* Interactive REPL — opened by `cell` (no args) in a TTY, or by `cell repl`.
|
|
1522
|
+
* User types a command (with or without leading `/`), press Enter to run.
|
|
1523
|
+
* Type `/` then Tab to see all commands. Type `exit`/`quit` to leave.
|
|
1524
|
+
*
|
|
1525
|
+
* Tab completion: when the input starts with `/`, returns matching commands.
|
|
1526
|
+
* Otherwise no completion (user is typing a command name without prefix).
|
|
1527
|
+
*/
|
|
1528
|
+
function showReplHelp() {
|
|
1529
|
+
console.log();
|
|
1530
|
+
console.log(C.bold(' Commands:'));
|
|
1531
|
+
console.log(C.dim(' ────────'));
|
|
1532
|
+
for (const [name, desc] of cli_repl_1.REPL_COMMANDS) {
|
|
1533
|
+
const padded = name.padEnd(14);
|
|
1534
|
+
console.log(` ${C.primary('/' + padded)} ${C.dim(desc)}`);
|
|
1535
|
+
}
|
|
1536
|
+
console.log();
|
|
1537
|
+
console.log(C.dim(' Type a command and press Enter. Use /<TAB> to filter.'));
|
|
1538
|
+
console.log(C.dim(' Tab completion works after `/` — try typing `/s` then Tab.'));
|
|
1539
|
+
console.log();
|
|
1540
|
+
}
|
|
1541
|
+
function doRepl() {
|
|
1542
|
+
// Only run if stdin is a TTY. If piped (e.g. `echo help | cell`), fall back to status.
|
|
1543
|
+
if (!process.stdin.isTTY) {
|
|
1544
|
+
doStatus();
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
const readline = require('readline');
|
|
1548
|
+
const { spawn } = require('child_process');
|
|
1549
|
+
const pathMod = require('path');
|
|
1550
|
+
// Show banner
|
|
1551
|
+
console.log();
|
|
1552
|
+
const banner = figlet_1.default.textSync('CELL', { font: 'ANSI Shadow', horizontalLayout: 'fitted' });
|
|
1553
|
+
console.log((0, gradient_string_1.default)(['#FF6B35', '#FFAB91'])(banner));
|
|
1554
|
+
console.log(C.dim(' Interactive REPL — type a command, / for list, exit to quit'));
|
|
1555
|
+
console.log();
|
|
1556
|
+
// First-run greeting
|
|
1557
|
+
try {
|
|
1558
|
+
const { firstRunGreeting } = require('./first-run');
|
|
1559
|
+
firstRunGreeting();
|
|
1560
|
+
}
|
|
1561
|
+
catch { }
|
|
1562
|
+
const rl = readline.createInterface({
|
|
1563
|
+
input: process.stdin,
|
|
1564
|
+
output: process.stdout,
|
|
1565
|
+
prompt: C.primary('cell> '),
|
|
1566
|
+
completer: cli_repl_1.replCompleter,
|
|
1567
|
+
terminal: true,
|
|
1568
|
+
});
|
|
1569
|
+
// Track command history
|
|
1570
|
+
const historyFile = pathMod.join(os.homedir(), '.fivo', 'cell', 'repl_history');
|
|
1571
|
+
try {
|
|
1572
|
+
const fsMod = require('fs');
|
|
1573
|
+
if (fsMod.existsSync(historyFile)) {
|
|
1574
|
+
const lines = fsMod.readFileSync(historyFile, 'utf8').split('\n').filter(Boolean);
|
|
1575
|
+
// readline.history is an array; pushing to it adds to up-arrow memory
|
|
1576
|
+
for (const l of lines)
|
|
1577
|
+
rl.history.push(l);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
catch { }
|
|
1581
|
+
const persistHistory = () => {
|
|
1582
|
+
try {
|
|
1583
|
+
const fsMod = require('fs');
|
|
1584
|
+
const pathMod2 = require('path');
|
|
1585
|
+
fsMod.mkdirSync(pathMod2.dirname(historyFile), { recursive: true });
|
|
1586
|
+
// Keep last 100 commands
|
|
1587
|
+
const recent = rl.history.slice(-100);
|
|
1588
|
+
fsMod.writeFileSync(historyFile, recent.join('\n') + '\n', 'utf8');
|
|
1589
|
+
}
|
|
1590
|
+
catch { }
|
|
1591
|
+
};
|
|
1592
|
+
rl.prompt();
|
|
1593
|
+
rl.on('line', (line) => {
|
|
1594
|
+
const trimmed = line.trim();
|
|
1595
|
+
if (!trimmed) {
|
|
1596
|
+
rl.prompt();
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
// Handle exit
|
|
1600
|
+
if (trimmed === 'exit' || trimmed === 'quit' || trimmed === 'q') {
|
|
1601
|
+
persistHistory();
|
|
1602
|
+
console.log(C.dim(' Bye!'));
|
|
1603
|
+
rl.close();
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
// Strip leading `/` if present
|
|
1607
|
+
const cmd = (0, cli_repl_1.stripSlash)(trimmed);
|
|
1608
|
+
// In-REPL help (no spawn overhead)
|
|
1609
|
+
if (cmd === 'help' || cmd === '?') {
|
|
1610
|
+
showReplHelp();
|
|
1611
|
+
rl.prompt();
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
// In-REPL version (avoid child spawn for trivial info)
|
|
1615
|
+
if (cmd === 'version' || cmd === '--version' || cmd === '-v') {
|
|
1616
|
+
try {
|
|
1617
|
+
const pkg = require('../package.json');
|
|
1618
|
+
console.log(`${C.primary('cell')} v${C.num(pkg.version)}`);
|
|
1619
|
+
}
|
|
1620
|
+
catch { }
|
|
1621
|
+
rl.prompt();
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
// Validate the command is known (so typos get a hint instead of a silent no-op)
|
|
1625
|
+
const firstToken = cmd.split(/\s+/)[0];
|
|
1626
|
+
if (!(0, cli_repl_1.isKnownCommand)(firstToken)) {
|
|
1627
|
+
console.log(C.warn(` Unknown command: ${firstToken}`));
|
|
1628
|
+
console.log(C.dim(' Type `help` for the list, or `/` then Tab.'));
|
|
1629
|
+
console.log();
|
|
1630
|
+
rl.prompt();
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
// Spawn cell <cmd> as child process (shares stdio so output is real-time)
|
|
1634
|
+
const cellPath = pathMod.join(__dirname, 'cli.js');
|
|
1635
|
+
const childArgs = cmd.split(/\s+/);
|
|
1636
|
+
const child = spawn(process.execPath, [cellPath, ...childArgs], {
|
|
1637
|
+
stdio: 'inherit',
|
|
1638
|
+
});
|
|
1639
|
+
child.on('exit', (code) => {
|
|
1640
|
+
if (code !== 0 && code !== null) {
|
|
1641
|
+
console.log(C.dim(` (exit ${code})`));
|
|
1642
|
+
}
|
|
1643
|
+
console.log();
|
|
1644
|
+
rl.prompt();
|
|
1645
|
+
});
|
|
1646
|
+
child.on('error', (err) => {
|
|
1647
|
+
console.log(C.warn(` Failed to run command: ${err.message}`));
|
|
1648
|
+
console.log();
|
|
1649
|
+
rl.prompt();
|
|
1650
|
+
});
|
|
1651
|
+
});
|
|
1652
|
+
rl.on('close', () => {
|
|
1653
|
+
persistHistory();
|
|
1654
|
+
console.log();
|
|
1655
|
+
process.exit(0);
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1365
1658
|
// ─── cell help ──────────────────────────────────────────────────────────────
|
|
1366
1659
|
function doHelp() {
|
|
1367
1660
|
console.log();
|
|
1368
1661
|
console.log(C.bold(' Cell Commands'));
|
|
1369
1662
|
console.log(C.dim(' ─────────────'));
|
|
1370
1663
|
console.log();
|
|
1371
|
-
console.log(` ${C.primary('cell start')} Start daemon + MCP (port 9876)`);
|
|
1664
|
+
console.log(` ${C.primary('cell start')} Start daemon + MCP (port 9876) — re-run safe`);
|
|
1372
1665
|
console.log(` ${C.primary('cell stop')} Stop daemon`);
|
|
1666
|
+
console.log(` ${C.primary('cell setup')} One-time project setup: scan + watcher + MCP (idempotent)`);
|
|
1373
1667
|
console.log(` ${C.primary('cell --version')} Print version + daemon status`);
|
|
1374
1668
|
console.log(` ${C.primary('cell mcp-config')} Auto-register cell MCP in Cursor/Antigravity/Codex/OpenCode`);
|
|
1375
1669
|
console.log(` ${C.primary('cell scan')} Scan codebase + build layers`);
|
|
@@ -1385,6 +1679,7 @@ function doHelp() {
|
|
|
1385
1679
|
console.log(` ${C.primary('cell watch daemon [proj] [dir]')} Run as long-lived watcher (for .bat)`);
|
|
1386
1680
|
console.log(` ${C.primary('cell blindspots [dir] [maxFiles]')} Scan for blind spots (15+ types)`);
|
|
1387
1681
|
console.log(` ${C.primary('cell context [project] [tool]')} Inject @cell context block`);
|
|
1682
|
+
console.log(` ${C.primary('cell repl')} Interactive REPL with / completion (or just run \`cell\` in a TTY)`);
|
|
1388
1683
|
console.log(` ${C.primary('cell help')} Show this help`);
|
|
1389
1684
|
console.log();
|
|
1390
1685
|
console.log(C.dim(' Install: npm i -g fivocell'));
|