vexp-cli 1.2.29 → 1.3.0
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 +41 -215
- package/bin/vexp.js +0 -0
- package/dist/agent-config.d.ts +10 -0
- package/dist/agent-config.js +81 -0
- package/dist/agent-config.js.map +1 -1
- package/dist/binary.js +12 -1
- package/dist/binary.js.map +1 -1
- package/dist/cli.js +466 -20
- package/dist/cli.js.map +1 -1
- package/dist/license.d.ts +1 -0
- package/dist/license.js +4 -2
- package/dist/license.js.map +1 -1
- package/package.json +65 -13
package/dist/cli.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import ora from "ora";
|
|
5
|
-
import { spawn } from "child_process";
|
|
5
|
+
import { spawn, spawnSync } from "child_process";
|
|
6
6
|
import * as path from "path";
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import { checkbox } from "@inquirer/prompts";
|
|
7
9
|
import { getBinaryPath, getInstalledVersion } from "./binary.js";
|
|
8
|
-
import { detectAgents,
|
|
10
|
+
import { detectAgents, getAgentList, configureSelectedAgents } from "./agent-config.js";
|
|
9
11
|
import { CLI_VERSION } from "./version.js";
|
|
10
12
|
import { activateLicense, deactivateLicense, readLicenseLimits, } from "./license.js";
|
|
11
13
|
import { checkForUpdate } from "./update-check.js";
|
|
@@ -27,7 +29,17 @@ function ensureBinary() {
|
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
// Tier limits are now enforced directly by the Rust daemon via license.jwt verification.
|
|
32
|
+
// Track whether we're in interactive REPL mode (don't call process.exit)
|
|
33
|
+
let replMode = false;
|
|
30
34
|
function runBinary(binaryPath, args) {
|
|
35
|
+
if (replMode) {
|
|
36
|
+
// In REPL mode, run synchronously and don't exit the process
|
|
37
|
+
const result = spawnSync(binaryPath, args, { stdio: "inherit" });
|
|
38
|
+
if (result.error) {
|
|
39
|
+
console.error(chalk.red(`Error: ${result.error.message}`));
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
31
43
|
const child = spawn(binaryPath, args, {
|
|
32
44
|
stdio: "inherit",
|
|
33
45
|
});
|
|
@@ -147,6 +159,18 @@ program
|
|
|
147
159
|
args.push("--cross-repo");
|
|
148
160
|
runBinary(binaryPath, args);
|
|
149
161
|
});
|
|
162
|
+
program
|
|
163
|
+
.command("savings")
|
|
164
|
+
.description("Show token savings statistics")
|
|
165
|
+
.option("--days <n>", "Number of days to analyze", "30")
|
|
166
|
+
.option("--export", "Export report to .vexp/token-savings-report.md")
|
|
167
|
+
.action(async (opts) => {
|
|
168
|
+
const binaryPath = ensureBinary();
|
|
169
|
+
const args = ["savings", "--days", opts.days];
|
|
170
|
+
if (opts.export)
|
|
171
|
+
args.push("--export");
|
|
172
|
+
runBinary(binaryPath, args);
|
|
173
|
+
});
|
|
150
174
|
program
|
|
151
175
|
.command("hooks <action>")
|
|
152
176
|
.description("Manage git hooks (install, check, remove)")
|
|
@@ -249,35 +273,63 @@ program
|
|
|
249
273
|
console.log(chalk.dim(" .vexp/.gitignore set to ignore all (personal mode)"));
|
|
250
274
|
}
|
|
251
275
|
if (!isPersonal) {
|
|
252
|
-
// Step 3:
|
|
253
|
-
const
|
|
276
|
+
// Step 3: Select agents
|
|
277
|
+
const allAgents = getAgentList();
|
|
254
278
|
const detected = detectAgents(workspaceRoot);
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
279
|
+
const detectedNames = new Set(detected.map((a) => a.agent));
|
|
280
|
+
let selectedNames;
|
|
281
|
+
if (typeof opts.agents === "string") {
|
|
282
|
+
// Explicit list from --agents flag
|
|
283
|
+
selectedNames = opts.agents.split(",").map((s) => s.trim());
|
|
284
|
+
console.log(chalk.dim(` Agents (from flag): ${selectedNames.join(", ")}`));
|
|
258
285
|
}
|
|
259
286
|
else {
|
|
260
|
-
|
|
287
|
+
// Interactive selection
|
|
288
|
+
console.log(chalk.bold("\n Select AI agents to configure:\n"));
|
|
289
|
+
for (let i = 0; i < allAgents.length; i++) {
|
|
290
|
+
const isDetected = detectedNames.has(allAgents[i].agent);
|
|
291
|
+
const marker = isDetected ? chalk.green(" ● detected") : "";
|
|
292
|
+
console.log(` ${chalk.cyan(String(i + 1).padStart(2))}. ${allAgents[i].agent}${marker}`);
|
|
293
|
+
}
|
|
294
|
+
const readline = await import("readline");
|
|
295
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
296
|
+
const answer = await new Promise((resolve) => rl.question(chalk.cyan("\n Select (numbers separated by spaces, Enter for detected): "), resolve));
|
|
297
|
+
rl.close();
|
|
298
|
+
const trimmed = answer.trim();
|
|
299
|
+
if (trimmed === "") {
|
|
300
|
+
// Enter with no input → use detected agents
|
|
301
|
+
selectedNames = detected.map((a) => a.agent);
|
|
302
|
+
if (selectedNames.length === 0) {
|
|
303
|
+
console.log(chalk.yellow("\n No agents detected. Skipping agent configuration."));
|
|
304
|
+
console.log(chalk.dim(" Tip: run 'vexp setup-agents' to choose agents manually.\n"));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
// Parse numbers
|
|
309
|
+
const indices = trimmed.split(/[\s,]+/)
|
|
310
|
+
.map((s) => parseInt(s, 10) - 1)
|
|
311
|
+
.filter((i) => i >= 0 && i < allAgents.length);
|
|
312
|
+
selectedNames = indices.map((i) => allAgents[i].agent);
|
|
313
|
+
}
|
|
261
314
|
}
|
|
262
|
-
// Step 4: Configure agents
|
|
263
|
-
if (
|
|
315
|
+
// Step 4: Configure selected agents
|
|
316
|
+
if (selectedNames.length > 0) {
|
|
264
317
|
if (opts.dryRun) {
|
|
265
318
|
console.log(chalk.dim("\n --dry-run: would configure:"));
|
|
266
|
-
for (const
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
console.log(chalk.dim(`
|
|
319
|
+
for (const name of selectedNames) {
|
|
320
|
+
const agent = allAgents.find((a) => a.agent === name);
|
|
321
|
+
if (agent) {
|
|
322
|
+
console.log(chalk.dim(` ${agent.agent} → ${agent.configFile}`));
|
|
323
|
+
if (agent.mcpConfigFile)
|
|
324
|
+
console.log(chalk.dim(` MCP: ${agent.mcpConfigFile}`));
|
|
325
|
+
}
|
|
270
326
|
}
|
|
271
327
|
}
|
|
272
328
|
else {
|
|
273
329
|
const spinner4 = ora("Writing agent configurations...").start();
|
|
274
330
|
const version = (await getInstalledVersion()) ?? CLI_VERSION;
|
|
275
|
-
const
|
|
276
|
-
? opts.agents.split(",").map((s) => s.trim())
|
|
277
|
-
: undefined;
|
|
278
|
-
const result = configureAgents(workspaceRoot, binaryPath, version, agentFilter);
|
|
331
|
+
const result = configureSelectedAgents(workspaceRoot, binaryPath, version, selectedNames);
|
|
279
332
|
spinner4.succeed("Agent configurations written");
|
|
280
|
-
// Print summary
|
|
281
333
|
console.log("");
|
|
282
334
|
for (const agent of result.agents) {
|
|
283
335
|
const icon = agent.action === "skipped" ? "○" : "●";
|
|
@@ -316,6 +368,116 @@ program
|
|
|
316
368
|
console.log("");
|
|
317
369
|
});
|
|
318
370
|
// ────────────────────────────────────────────────────
|
|
371
|
+
// Command: setup-agents (interactive checkbox select)
|
|
372
|
+
// ────────────────────────────────────────────────────
|
|
373
|
+
program
|
|
374
|
+
.command("setup-agents [dir]")
|
|
375
|
+
.description("Configure AI coding agents to use vexp MCP (interactive multi-select)")
|
|
376
|
+
.action(async (dir) => {
|
|
377
|
+
await runSetupAgents(dir);
|
|
378
|
+
});
|
|
379
|
+
async function runSetupAgents(dir) {
|
|
380
|
+
const workspaceRoot = path.resolve(dir ?? process.cwd());
|
|
381
|
+
const binaryPath = ensureBinary();
|
|
382
|
+
console.log("");
|
|
383
|
+
console.log(chalk.bold(" Setup AI Coding Agents"));
|
|
384
|
+
console.log(chalk.dim(" Use ↑↓ to navigate, Space to toggle, Enter to confirm\n"));
|
|
385
|
+
const agents = getAgentList();
|
|
386
|
+
const selectedNames = await checkbox({
|
|
387
|
+
message: "Select agents to configure",
|
|
388
|
+
choices: agents.map((a) => {
|
|
389
|
+
const detected = fs.existsSync(path.join(workspaceRoot, a.detectPath));
|
|
390
|
+
return {
|
|
391
|
+
name: detected
|
|
392
|
+
? `${a.agent} ${chalk.green("● detected")}`
|
|
393
|
+
: a.agent,
|
|
394
|
+
value: a.agent,
|
|
395
|
+
checked: detected,
|
|
396
|
+
};
|
|
397
|
+
}),
|
|
398
|
+
theme: { prefix: chalk.cyan(" ") },
|
|
399
|
+
});
|
|
400
|
+
if (selectedNames.length === 0) {
|
|
401
|
+
console.log(chalk.dim("\n No agents selected.\n"));
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const spinner = ora(" Writing agent configurations...").start();
|
|
405
|
+
const version = (await getInstalledVersion()) ?? CLI_VERSION;
|
|
406
|
+
const result = configureSelectedAgents(workspaceRoot, binaryPath, version, selectedNames);
|
|
407
|
+
spinner.succeed("Agent configurations written");
|
|
408
|
+
console.log("");
|
|
409
|
+
for (const agent of result.agents) {
|
|
410
|
+
const actionLabel = agent.action === "created"
|
|
411
|
+
? chalk.green("created")
|
|
412
|
+
: agent.action === "updated"
|
|
413
|
+
? chalk.yellow("updated")
|
|
414
|
+
: agent.action === "appended"
|
|
415
|
+
? chalk.cyan("appended")
|
|
416
|
+
: chalk.dim("up to date");
|
|
417
|
+
console.log(` ${chalk.green("✓")} ${chalk.bold(agent.agent)}`);
|
|
418
|
+
console.log(` ${chalk.dim("→")} ${agent.configFile} (${actionLabel})`);
|
|
419
|
+
}
|
|
420
|
+
if (result.mcpConfigs.length > 0) {
|
|
421
|
+
console.log("");
|
|
422
|
+
console.log(chalk.dim(" MCP configs written:"));
|
|
423
|
+
for (const mcpFile of result.mcpConfigs) {
|
|
424
|
+
console.log(chalk.dim(` → ${mcpFile}`));
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
console.log(chalk.green(`\n ✓ ${result.agents.length} agent(s) configured\n`));
|
|
428
|
+
}
|
|
429
|
+
/** Full setup from REPL — reuses the existing readline to avoid stdin conflicts. */
|
|
430
|
+
async function runSetupInteractive(rl) {
|
|
431
|
+
const workspaceRoot = process.cwd();
|
|
432
|
+
const binaryPath = ensureBinary();
|
|
433
|
+
console.log(chalk.bold(`\n vexp setup — ${workspaceRoot}\n`));
|
|
434
|
+
// Step 1: Index
|
|
435
|
+
console.log(chalk.dim(" Indexing codebase...\n"));
|
|
436
|
+
runBinary(binaryPath, ["init", workspaceRoot]);
|
|
437
|
+
// Step 2: Select agents
|
|
438
|
+
const allAgents = getAgentList();
|
|
439
|
+
const detected = detectAgents(workspaceRoot);
|
|
440
|
+
const detectedNames = new Set(detected.map((a) => a.agent));
|
|
441
|
+
console.log(chalk.bold("\n Select AI agents to configure:\n"));
|
|
442
|
+
for (let i = 0; i < allAgents.length; i++) {
|
|
443
|
+
const isDetected = detectedNames.has(allAgents[i].agent);
|
|
444
|
+
const marker = isDetected ? chalk.green(" ● detected") : "";
|
|
445
|
+
console.log(` ${chalk.cyan(String(i + 1).padStart(2))}. ${allAgents[i].agent}${marker}`);
|
|
446
|
+
}
|
|
447
|
+
const answer = await ask(rl, chalk.cyan("\n Select (numbers separated by spaces, Enter for detected): "));
|
|
448
|
+
const trimmed = answer.trim();
|
|
449
|
+
let selectedNames;
|
|
450
|
+
if (trimmed === "") {
|
|
451
|
+
selectedNames = detected.map((a) => a.agent);
|
|
452
|
+
if (selectedNames.length === 0) {
|
|
453
|
+
console.log(chalk.yellow("\n No agents detected. Skipping."));
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
const indices = trimmed.split(/[\s,]+/)
|
|
459
|
+
.map((s) => parseInt(s, 10) - 1)
|
|
460
|
+
.filter((i) => i >= 0 && i < allAgents.length);
|
|
461
|
+
selectedNames = indices.map((i) => allAgents[i].agent);
|
|
462
|
+
}
|
|
463
|
+
if (selectedNames.length > 0) {
|
|
464
|
+
const version = (await getInstalledVersion()) ?? CLI_VERSION;
|
|
465
|
+
const result = configureSelectedAgents(workspaceRoot, binaryPath, version, selectedNames);
|
|
466
|
+
console.log("");
|
|
467
|
+
for (const agent of result.agents) {
|
|
468
|
+
const actionLabel = agent.action === "created" ? chalk.green("created")
|
|
469
|
+
: agent.action === "updated" ? chalk.yellow("updated")
|
|
470
|
+
: agent.action === "appended" ? chalk.cyan("appended")
|
|
471
|
+
: chalk.dim("up to date");
|
|
472
|
+
console.log(` ${chalk.green("✓")} ${chalk.bold(agent.agent)} — ${agent.configFile} (${actionLabel})`);
|
|
473
|
+
}
|
|
474
|
+
if (result.mcpConfigs.length > 0) {
|
|
475
|
+
console.log(chalk.dim(`\n MCP configs: ${result.mcpConfigs.join(", ")}`));
|
|
476
|
+
}
|
|
477
|
+
console.log(chalk.green(`\n ✓ Setup complete — ${result.agents.length} agent(s) configured\n`));
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// ────────────────────────────────────────────────────
|
|
319
481
|
// License management
|
|
320
482
|
// ────────────────────────────────────────────────────
|
|
321
483
|
program
|
|
@@ -364,6 +526,8 @@ program
|
|
|
364
526
|
console.log(` Max nodes: ${limits.maxNodes === 0 ? "unlimited" : limits.maxNodes.toLocaleString()}`);
|
|
365
527
|
console.log(` Max repos: ${limits.maxRepos === 0 ? "unlimited" : limits.maxRepos}`);
|
|
366
528
|
console.log(` All tools: ${limits.allTools ? "yes" : "no (7/10)"}`);
|
|
529
|
+
if (limits.renewsAt)
|
|
530
|
+
console.log(` Renews: ${limits.renewsAt.toLocaleDateString()}`);
|
|
367
531
|
console.log("");
|
|
368
532
|
});
|
|
369
533
|
// ────────────────────────────────────────────────────
|
|
@@ -378,5 +542,287 @@ program
|
|
|
378
542
|
console.log(`vexp core: ${installed ?? "not found"}`);
|
|
379
543
|
});
|
|
380
544
|
await checkForUpdate();
|
|
381
|
-
|
|
545
|
+
// ── Sub-menu: Setup & Config ──
|
|
546
|
+
const MENU_CONFIG = [
|
|
547
|
+
{ key: "1", label: "init", hint: "Create .vexp/ and run first index", description: "Creates .vexp/ directory, runs a full index, installs git hooks.\n Run once when adding vexp to a new project." },
|
|
548
|
+
{ key: "2", label: "index", hint: "Re-index the repository", description: "Scans all source files, builds the AST graph (nodes + edges),\n and saves it to .vexp/index.db.\n Output: file count, node count, edge count, time elapsed." },
|
|
549
|
+
{ key: "3", label: "setup", hint: "Full setup (index + agents + hooks)", description: "One-command bootstrap: indexes the codebase, detects AI agents,\n writes MCP configs, installs git hooks." },
|
|
550
|
+
{ key: "4", label: "setup-agents", hint: "Choose which AI agents to configure", description: "Select agents (Claude, Cursor, Windsurf, Copilot, …) to configure.\n Creates config directories and writes MCP settings for each." },
|
|
551
|
+
{ key: "5", label: "daemon-cmd", hint: "Manage daemon (start/stop/status/logs)", description: "Controls the background daemon:\n start → launches the daemon (indexes + watches for changes)\n stop → stops the running daemon\n status → shows node/edge/file counts\n logs → prints .vexp/vexp.log" },
|
|
552
|
+
];
|
|
553
|
+
// ── Sub-menu: Explore & Analyze ──
|
|
554
|
+
const MENU_EXPLORE = [
|
|
555
|
+
{ key: "1", label: "capsule", hint: "Generate context capsule for a task", description: "Given a natural-language query, returns the most relevant files\n and symbols as a compressed context capsule (markdown).\n 70-90% token savings vs reading full files." },
|
|
556
|
+
{ key: "2", label: "skeleton", hint: "Show a file's token-efficient skeleton", description: "Prints a compact outline of a file (functions, classes, signatures).\n Detail levels: minimal / standard / detailed." },
|
|
557
|
+
{ key: "3", label: "impact", hint: "Impact graph for a symbol", description: "Shows every symbol that depends on a given function/class\n (direct + transitive).\n Output: tree, list, or mermaid diagram." },
|
|
558
|
+
{ key: "4", label: "flow", hint: "Logic flow between two symbols", description: "Finds execution paths (call chains) from symbol A to symbol B.\n Output: up to N shortest paths with cost." },
|
|
559
|
+
];
|
|
560
|
+
// ── Sub-menu: Token Savings ──
|
|
561
|
+
const MENU_SAVINGS = [
|
|
562
|
+
{ key: "1", label: "summary", hint: "View savings summary", description: "Shows total tokens saved, average saving %, and breakdown by tool.\n Choose a period: 7 / 14 / 30 / 60 / 90 days." },
|
|
563
|
+
{ key: "2", label: "export", hint: "Export report to .vexp/token-savings-report.md", description: "Generates a markdown report with breakdown by tool and daily trend,\n then saves it to .vexp/token-savings-report.md." },
|
|
564
|
+
];
|
|
565
|
+
// ── Sub-menu: License ──
|
|
566
|
+
const MENU_LICENSE = [
|
|
567
|
+
{ key: "1", label: "status", hint: "Show current license plan and limits", description: "Displays your current plan (free / pro / team), node limits,\n repo limits, and license expiry." },
|
|
568
|
+
{ key: "2", label: "activate", hint: "Activate a Pro/Team license key", description: "Enter a license key to unlock Pro or Team features.\n Get your key at https://vexp.dev/#pricing" },
|
|
569
|
+
{ key: "3", label: "deactivate", hint: "Remove current license", description: "Removes the active license. Free plan limits will apply." },
|
|
570
|
+
];
|
|
571
|
+
function printBanner() {
|
|
572
|
+
const version = CLI_VERSION;
|
|
573
|
+
console.log("");
|
|
574
|
+
console.log(chalk.cyan.bold(" ██╗ ██╗███████╗██╗ ██╗██████╗"));
|
|
575
|
+
console.log(chalk.cyan.bold(" ██║ ██║██╔════╝╚██╗██╔╝██╔══██╗"));
|
|
576
|
+
console.log(chalk.cyan.bold(" ██║ ██║█████╗ ╚███╔╝ ██████╔╝"));
|
|
577
|
+
console.log(chalk.cyan(" ╚██╗ ██╔╝██╔══╝ ██╔██╗ ██╔═══╝"));
|
|
578
|
+
console.log(chalk.cyan(" ╚████╔╝ ███████╗██╔╝ ██╗██║"));
|
|
579
|
+
console.log(chalk.dim(" ╚═══╝ ╚══════╝╚═╝ ╚═╝╚═╝") + chalk.dim(` v${version}`));
|
|
580
|
+
console.log("");
|
|
581
|
+
console.log(chalk.dim(" Context Engine for AI Coding Agents"));
|
|
582
|
+
}
|
|
583
|
+
function printMainMenu() {
|
|
584
|
+
console.log("");
|
|
585
|
+
console.log(` ${chalk.cyan.bold("1")} ${chalk.green("Setup & Config".padEnd(20))} ${chalk.dim("Initialize, index, and configure agents")}`);
|
|
586
|
+
console.log(` ${chalk.cyan.bold("2")} ${chalk.green("Explore & Analyze".padEnd(20))} ${chalk.dim("Query context, skeletons, impact, flow")}`);
|
|
587
|
+
console.log(` ${chalk.cyan.bold("3")} ${chalk.green("Token Savings".padEnd(20))} ${chalk.dim("View savings stats, export report")}`);
|
|
588
|
+
console.log(` ${chalk.cyan.bold("4")} ${chalk.green("License".padEnd(20))} ${chalk.dim("View or activate your license")}`);
|
|
589
|
+
console.log(` ${chalk.cyan.bold("q")} ${chalk.red("Exit".padEnd(20))} ${chalk.dim("Quit")}`);
|
|
590
|
+
console.log("");
|
|
591
|
+
}
|
|
592
|
+
function printSubMenu(title, items) {
|
|
593
|
+
console.log("");
|
|
594
|
+
console.log(chalk.cyan(` ── ${title} ${"─".repeat(Math.max(0, 40 - title.length))}`));
|
|
595
|
+
console.log("");
|
|
596
|
+
for (const item of items) {
|
|
597
|
+
console.log(` ${chalk.cyan.bold(item.key)} ${chalk.green(item.label.padEnd(14))} ${chalk.dim(item.hint)}`);
|
|
598
|
+
}
|
|
599
|
+
console.log(` ${chalk.dim("b")} ${chalk.dim("← Back to main menu")}`);
|
|
600
|
+
console.log("");
|
|
601
|
+
}
|
|
602
|
+
function ask(rl, prompt) {
|
|
603
|
+
return new Promise((resolve) => rl.question(prompt, resolve));
|
|
604
|
+
}
|
|
605
|
+
async function interactiveMode() {
|
|
606
|
+
const readline = await import("readline");
|
|
607
|
+
printBanner();
|
|
608
|
+
printMainMenu();
|
|
609
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
610
|
+
rl.on("close", () => { console.log(""); process.exit(0); });
|
|
611
|
+
let currentMenu = "main";
|
|
612
|
+
const prompts = {
|
|
613
|
+
main: chalk.cyan.bold(" vexp> "),
|
|
614
|
+
config: chalk.cyan.bold(" vexp/config> "),
|
|
615
|
+
explore: chalk.cyan.bold(" vexp/explore> "),
|
|
616
|
+
savings: chalk.cyan.bold(" vexp/savings> "),
|
|
617
|
+
license: chalk.cyan.bold(" vexp/license> "),
|
|
618
|
+
};
|
|
619
|
+
while (true) {
|
|
620
|
+
const input = (await ask(rl, prompts[currentMenu])).trim().toLowerCase();
|
|
621
|
+
if (!input)
|
|
622
|
+
continue;
|
|
623
|
+
if (input === "q" || input === "exit" || input === "quit") {
|
|
624
|
+
rl.close();
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
// ── Main menu ──
|
|
628
|
+
if (currentMenu === "main") {
|
|
629
|
+
if (input === "?" || input === "help" || input === "menu") {
|
|
630
|
+
printMainMenu();
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
if (input === "1" || input === "config") {
|
|
634
|
+
currentMenu = "config";
|
|
635
|
+
printSubMenu("Setup & Config", MENU_CONFIG);
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
if (input === "2" || input === "explore") {
|
|
639
|
+
currentMenu = "explore";
|
|
640
|
+
printSubMenu("Explore & Analyze", MENU_EXPLORE);
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
if (input === "3" || input === "savings") {
|
|
644
|
+
currentMenu = "savings";
|
|
645
|
+
printSubMenu("Token Savings", MENU_SAVINGS);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
if (input === "4" || input === "license") {
|
|
649
|
+
currentMenu = "license";
|
|
650
|
+
printSubMenu("License", MENU_LICENSE);
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
// Allow direct command names from any sub-menu
|
|
654
|
+
const allItems = [...MENU_CONFIG, ...MENU_EXPLORE, ...MENU_SAVINGS, ...MENU_LICENSE];
|
|
655
|
+
const direct = allItems.find((m) => m.label === input);
|
|
656
|
+
if (direct) {
|
|
657
|
+
await executeCommand(direct.label, rl);
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
console.log(chalk.dim(" Choose 1-4 or type a command name. 'q' to exit.\n"));
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
// ── Sub-menus ──
|
|
664
|
+
if (input === "b" || input === "back") {
|
|
665
|
+
currentMenu = "main";
|
|
666
|
+
printMainMenu();
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (input === "?" || input === "help" || input === "menu") {
|
|
670
|
+
const menus = { config: MENU_CONFIG, explore: MENU_EXPLORE, savings: MENU_SAVINGS, license: MENU_LICENSE };
|
|
671
|
+
const titles = { config: "Setup & Config", explore: "Explore & Analyze", savings: "Token Savings", license: "License" };
|
|
672
|
+
printSubMenu(titles[currentMenu], menus[currentMenu]);
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
const menuMap = { config: MENU_CONFIG, explore: MENU_EXPLORE, savings: MENU_SAVINGS, license: MENU_LICENSE };
|
|
676
|
+
const items = menuMap[currentMenu];
|
|
677
|
+
const item = items.find((m) => m.key === input || m.label === input);
|
|
678
|
+
if (!item) {
|
|
679
|
+
console.log(chalk.dim(` Unknown option. Type a number (1-${items.length}), 'b' to go back, or 'q' to exit.\n`));
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
// Show description before executing
|
|
683
|
+
console.log(chalk.dim(`\n ${item.description}\n`));
|
|
684
|
+
await executeCommand(item.label, rl);
|
|
685
|
+
console.log("");
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
async function executeCommand(label, rl) {
|
|
689
|
+
try {
|
|
690
|
+
switch (label) {
|
|
691
|
+
// ── Config commands ──
|
|
692
|
+
case "init": {
|
|
693
|
+
const binaryPath = ensureBinary();
|
|
694
|
+
runBinary(binaryPath, ["init"]);
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
case "index": {
|
|
698
|
+
const binaryPath = ensureBinary();
|
|
699
|
+
runBinary(binaryPath, ["index"]);
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
case "setup": {
|
|
703
|
+
await runSetupInteractive(rl);
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
case "setup-agents": {
|
|
707
|
+
await runSetupAgents();
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
case "daemon-cmd": {
|
|
711
|
+
const action = await ask(rl, chalk.cyan(" Action (start/stop/status/logs): "));
|
|
712
|
+
const valid = ["start", "stop", "status", "logs"];
|
|
713
|
+
if (valid.includes(action.trim())) {
|
|
714
|
+
const binaryPath = ensureBinary();
|
|
715
|
+
runBinary(binaryPath, ["daemon-cmd", action.trim()]);
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
console.log(chalk.dim(" Cancelled."));
|
|
719
|
+
}
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
// ── Explore commands ──
|
|
723
|
+
case "capsule": {
|
|
724
|
+
const query = await ask(rl, chalk.cyan(" Describe your task: "));
|
|
725
|
+
if (query.trim()) {
|
|
726
|
+
const binaryPath = ensureBinary();
|
|
727
|
+
console.log(chalk.dim(" Generating context capsule…\n"));
|
|
728
|
+
runBinary(binaryPath, ["capsule", query.trim()]);
|
|
729
|
+
}
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
case "skeleton": {
|
|
733
|
+
const file = await ask(rl, chalk.cyan(" File path: "));
|
|
734
|
+
if (file.trim()) {
|
|
735
|
+
const binaryPath = ensureBinary();
|
|
736
|
+
console.log(chalk.dim(" Generating skeleton…\n"));
|
|
737
|
+
runBinary(binaryPath, ["skeleton", file.trim()]);
|
|
738
|
+
}
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
case "impact": {
|
|
742
|
+
const fqn = await ask(rl, chalk.cyan(" Symbol FQN (e.g. src/auth.ts::validateToken): "));
|
|
743
|
+
if (fqn.trim()) {
|
|
744
|
+
const binaryPath = ensureBinary();
|
|
745
|
+
console.log(chalk.dim(" Computing impact graph…\n"));
|
|
746
|
+
runBinary(binaryPath, ["impact", fqn.trim()]);
|
|
747
|
+
}
|
|
748
|
+
break;
|
|
749
|
+
}
|
|
750
|
+
case "flow": {
|
|
751
|
+
const start = await ask(rl, chalk.cyan(" Start symbol FQN: "));
|
|
752
|
+
const end = await ask(rl, chalk.cyan(" End symbol FQN: "));
|
|
753
|
+
if (start.trim() && end.trim()) {
|
|
754
|
+
const binaryPath = ensureBinary();
|
|
755
|
+
console.log(chalk.dim(" Finding execution paths…\n"));
|
|
756
|
+
runBinary(binaryPath, ["flow", start.trim(), end.trim()]);
|
|
757
|
+
}
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
case "savings": // fallthrough from direct command
|
|
761
|
+
case "summary": {
|
|
762
|
+
const daysInput = await ask(rl, chalk.cyan(" Period in days (7/14/30/60/90): "));
|
|
763
|
+
const days = parseInt(daysInput.trim(), 10) || 30;
|
|
764
|
+
const binaryPath = ensureBinary();
|
|
765
|
+
runBinary(binaryPath, ["savings", "--days", String(days)]);
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
case "export": {
|
|
769
|
+
const daysInput = await ask(rl, chalk.cyan(" Period in days (7/14/30/60/90): "));
|
|
770
|
+
const days = parseInt(daysInput.trim(), 10) || 30;
|
|
771
|
+
const binaryPath = ensureBinary();
|
|
772
|
+
runBinary(binaryPath, ["savings", "--days", String(days), "--export"]);
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
// ── License commands ──
|
|
776
|
+
case "status": {
|
|
777
|
+
const limits = readLicenseLimits();
|
|
778
|
+
console.log(` Plan: ${limits.plan}`);
|
|
779
|
+
if (limits.email)
|
|
780
|
+
console.log(` Email: ${limits.email}`);
|
|
781
|
+
console.log(` Max nodes: ${limits.maxNodes === 0 ? "unlimited" : limits.maxNodes.toLocaleString()}`);
|
|
782
|
+
console.log(` Max repos: ${limits.maxRepos === 0 ? "unlimited" : limits.maxRepos}`);
|
|
783
|
+
console.log(` All tools: ${limits.allTools ? "yes" : "no (7/10)"}`);
|
|
784
|
+
if (limits.renewsAt)
|
|
785
|
+
console.log(` Renews: ${limits.renewsAt.toLocaleDateString()}`);
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
case "activate": {
|
|
789
|
+
const key = await ask(rl, chalk.cyan(" License key: "));
|
|
790
|
+
if (key.trim()) {
|
|
791
|
+
try {
|
|
792
|
+
const claims = activateLicense(key.trim());
|
|
793
|
+
console.log(chalk.green("\n ✓ License activated!"));
|
|
794
|
+
console.log(` Plan: ${claims.plan}`);
|
|
795
|
+
console.log(` Email: ${claims.sub}`);
|
|
796
|
+
console.log(` Expires: ${new Date(claims.exp * 1000).toLocaleDateString()}`);
|
|
797
|
+
}
|
|
798
|
+
catch (err) {
|
|
799
|
+
console.error(chalk.red(` ✗ ${err instanceof Error ? err.message : err}`));
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
case "deactivate": {
|
|
805
|
+
deactivateLicense();
|
|
806
|
+
console.log(chalk.yellow(" License removed. Free plan limits are now active."));
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
catch (err) {
|
|
812
|
+
if (err instanceof Error) {
|
|
813
|
+
console.error(chalk.red(` Error: ${err.message}`));
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
// ────────────────────────────────────────────────────
|
|
818
|
+
// Entry point — interactive mode or CLI dispatch
|
|
819
|
+
// ────────────────────────────────────────────────────
|
|
820
|
+
if (process.argv.length <= 2) {
|
|
821
|
+
replMode = true;
|
|
822
|
+
program.exitOverride();
|
|
823
|
+
await interactiveMode();
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
program.parse();
|
|
827
|
+
}
|
|
382
828
|
//# sourceMappingURL=cli.js.map
|