gnosys 5.7.0 → 5.8.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.
Files changed (67) hide show
  1. package/README.md +29 -2
  2. package/dist/cli.js +249 -117
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +167 -25
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/ask.d.ts.map +1 -1
  7. package/dist/lib/ask.js +20 -4
  8. package/dist/lib/ask.js.map +1 -1
  9. package/dist/lib/chat/SlashPalette.d.ts +34 -0
  10. package/dist/lib/chat/SlashPalette.d.ts.map +1 -0
  11. package/dist/lib/chat/SlashPalette.js +49 -0
  12. package/dist/lib/chat/SlashPalette.js.map +1 -0
  13. package/dist/lib/chat/index.d.ts.map +1 -1
  14. package/dist/lib/chat/index.js +6 -4
  15. package/dist/lib/chat/index.js.map +1 -1
  16. package/dist/lib/chat/llmTurn.d.ts.map +1 -1
  17. package/dist/lib/chat/llmTurn.js +4 -1
  18. package/dist/lib/chat/llmTurn.js.map +1 -1
  19. package/dist/lib/chat/render.d.ts.map +1 -1
  20. package/dist/lib/chat/render.js +91 -10
  21. package/dist/lib/chat/render.js.map +1 -1
  22. package/dist/lib/config.d.ts +25 -1
  23. package/dist/lib/config.d.ts.map +1 -1
  24. package/dist/lib/config.js +30 -0
  25. package/dist/lib/config.js.map +1 -1
  26. package/dist/lib/db.d.ts +21 -0
  27. package/dist/lib/db.d.ts.map +1 -1
  28. package/dist/lib/db.js +44 -17
  29. package/dist/lib/db.js.map +1 -1
  30. package/dist/lib/heartbeat.d.ts +31 -0
  31. package/dist/lib/heartbeat.d.ts.map +1 -0
  32. package/dist/lib/heartbeat.js +91 -0
  33. package/dist/lib/heartbeat.js.map +1 -0
  34. package/dist/lib/idFormat.d.ts +41 -0
  35. package/dist/lib/idFormat.d.ts.map +1 -0
  36. package/dist/lib/idFormat.js +66 -0
  37. package/dist/lib/idFormat.js.map +1 -0
  38. package/dist/lib/import.d.ts.map +1 -1
  39. package/dist/lib/import.js +2 -1
  40. package/dist/lib/import.js.map +1 -1
  41. package/dist/lib/ingest.d.ts +7 -1
  42. package/dist/lib/ingest.d.ts.map +1 -1
  43. package/dist/lib/ingest.js +23 -4
  44. package/dist/lib/ingest.js.map +1 -1
  45. package/dist/lib/llm.d.ts +1 -1
  46. package/dist/lib/llm.d.ts.map +1 -1
  47. package/dist/lib/llm.js.map +1 -1
  48. package/dist/lib/progress.d.ts +54 -0
  49. package/dist/lib/progress.d.ts.map +1 -0
  50. package/dist/lib/progress.js +92 -0
  51. package/dist/lib/progress.js.map +1 -0
  52. package/dist/lib/remote.d.ts +14 -1
  53. package/dist/lib/remote.d.ts.map +1 -1
  54. package/dist/lib/remote.js +75 -28
  55. package/dist/lib/remote.js.map +1 -1
  56. package/dist/lib/setup/sections/routing.d.ts.map +1 -1
  57. package/dist/lib/setup/sections/routing.js +4 -2
  58. package/dist/lib/setup/sections/routing.js.map +1 -1
  59. package/dist/lib/setup.d.ts +5 -0
  60. package/dist/lib/setup.d.ts.map +1 -1
  61. package/dist/lib/setup.js +127 -0
  62. package/dist/lib/setup.js.map +1 -1
  63. package/dist/lib/upgrade.d.ts +38 -0
  64. package/dist/lib/upgrade.d.ts.map +1 -0
  65. package/dist/lib/upgrade.js +61 -0
  66. package/dist/lib/upgrade.js.map +1 -0
  67. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -10,27 +10,33 @@ import os from "os";
10
10
  import { fileURLToPath } from "url";
11
11
  import dotenv from "dotenv";
12
12
  import { readFileSync, existsSync, copyFileSync } from "fs";
13
+ // v5.8.0 (#4): only the lightweight modules are imported at top-level.
14
+ // Anything that pulls @huggingface/transformers, mammoth/pdf-parse/turndown,
15
+ // large file-walking machinery, or otherwise costs >100ms to load gets
16
+ // `await import(...)` inside its own action handler. This keeps
17
+ // `gnosys --help` and other lightweight commands fast.
13
18
  import { GnosysResolver } from "./lib/resolver.js";
14
19
  import { getGnosysHome } from "./lib/paths.js";
15
20
  import { GnosysSearch } from "./lib/search.js";
16
21
  import { GnosysTagRegistry } from "./lib/tags.js";
17
- import { GnosysIngestion } from "./lib/ingest.js";
18
22
  import { applyLens } from "./lib/lensing.js";
19
23
  import { getFileHistory, rollbackToCommit, hasGitHistory, getFileDiff } from "./lib/history.js";
20
24
  import { computeStats } from "./lib/timeline.js";
21
25
  import { buildLinkGraph, getBacklinks, getOutgoingLinks, formatGraphSummary } from "./lib/wikilinks.js";
22
- import { bootstrap, discoverFiles } from "./lib/bootstrap.js";
23
- import { performImport, formatImportSummary } from "./lib/import.js";
24
26
  import { loadConfig, generateConfigTemplate, DEFAULT_CONFIG, writeConfig, resolveTaskModel, ALL_PROVIDERS, getProviderModel } from "./lib/config.js";
25
- import { GnosysEmbeddings } from "./lib/embeddings.js";
26
- import { GnosysHybridSearch } from "./lib/hybridSearch.js";
27
- import { GnosysAsk } from "./lib/ask.js";
28
27
  import { getLLMProvider, isProviderAvailable } from "./lib/llm.js";
29
28
  import { GnosysDB } from "./lib/db.js";
30
- import { migrate, formatMigrationReport } from "./lib/migrate.js";
31
29
  import { createProjectIdentity, readProjectIdentity, findProjectIdentity, migrateProject } from "./lib/projectIdentity.js";
32
30
  import { setPreference, getPreference, getAllPreferences, deletePreference } from "./lib/preferences.js";
33
31
  import { syncToTarget } from "./lib/rulesGen.js";
32
+ // Lazy-loaded inside action handlers (each ~200ms-2.5s on cold cache):
33
+ // - ./lib/embeddings.js (@huggingface/transformers — 80MB)
34
+ // - ./lib/hybridSearch.js (depends on embeddings)
35
+ // - ./lib/ask.js (depends on hybridSearch)
36
+ // - ./lib/import.js (mammoth, pdf-parse, turndown)
37
+ // - ./lib/bootstrap.js (file walking — 2.5s)
38
+ // - ./lib/ingest.js (LLM machinery)
39
+ // - ./lib/migrate.js (only migrate-db needs it)
34
40
  // Load API keys from ~/.config/gnosys/.env (same as MCP server)
35
41
  // IMPORTANT: We use dotenv.parse() instead of dotenv.config() because
36
42
  // dotenv v17+ writes injection notices to stdout, which corrupts
@@ -225,6 +231,7 @@ program
225
231
  .option("--federated", "Use federated discovery with tier boosting (project > user > global)")
226
232
  .option("--scope <scope>", "Filter by scope: project, user, global (comma-separated for multiple)")
227
233
  .option("-d, --directory <dir>", "Project directory for context")
234
+ .option("--id-format <format>", "ID display format: short | long | raw (default: short)", "short")
228
235
  .action(async (query, opts) => {
229
236
  // Federated discover path
230
237
  if (opts.federated || opts.scope) {
@@ -279,11 +286,16 @@ program
279
286
  });
280
287
  return;
281
288
  }
289
+ const { formatMemoryId, buildProjectNameLookup, parseIdFormat } = await import("./lib/idFormat.js");
290
+ const idFormat = parseIdFormat(opts.idFormat);
291
+ const projectNames = buildProjectNameLookup(centralDb);
282
292
  outputResult(!!opts.json, { query, count: results.length, results }, () => {
283
293
  console.log(`Found ${results.length} relevant memories for "${query}":\n`);
284
294
  for (const r of results) {
295
+ const projectName = r.project_id ? projectNames.get(r.project_id) || null : null;
296
+ const displayId = formatMemoryId(r.id, projectName, idFormat);
285
297
  console.log(` ${r.title}`);
286
- console.log(` id: ${r.id}`);
298
+ console.log(` id: ${displayId}`);
287
299
  if (r.relevance)
288
300
  console.log(` Relevance: ${r.relevance}`);
289
301
  console.log();
@@ -307,6 +319,7 @@ program
307
319
  .option("--federated", "Use federated search with tier boosting (project > user > global)")
308
320
  .option("--scope <scope>", "Filter by scope: project, user, global (comma-separated for multiple)")
309
321
  .option("-d, --directory <dir>", "Project directory for context")
322
+ .option("--id-format <format>", "ID display format: short | long | raw (default: short)", "short")
310
323
  .action(async (query, opts) => {
311
324
  // Federated search path — uses central DB with tier boosting
312
325
  if (opts.federated || opts.scope) {
@@ -365,11 +378,16 @@ program
365
378
  });
366
379
  return;
367
380
  }
381
+ const { formatMemoryId, buildProjectNameLookup, parseIdFormat } = await import("./lib/idFormat.js");
382
+ const idFormat = parseIdFormat(opts.idFormat);
383
+ const projectNames = buildProjectNameLookup(centralDb);
368
384
  outputResult(!!opts.json, { query, count: results.length, results }, () => {
369
385
  console.log(`Found ${results.length} results for "${query}":\n`);
370
386
  for (const r of results) {
387
+ const projectName = r.project_id ? projectNames.get(r.project_id) || null : null;
388
+ const displayId = formatMemoryId(r.id, projectName, idFormat);
371
389
  console.log(` ${r.title}`);
372
- console.log(` id: ${r.id}`);
390
+ console.log(` id: ${displayId}`);
373
391
  console.log(` ${r.snippet.replace(/>>>/g, "").replace(/<<</g, "")}`);
374
392
  console.log();
375
393
  }
@@ -391,6 +409,7 @@ program
391
409
  .option("-t, --tag <tag>", "Filter by tag")
392
410
  .option("-s, --store <store>", "Filter by store layer (project|user|global)")
393
411
  .option("--json", "Output as JSON")
412
+ .option("--id-format <format>", "ID display format: short | long | raw (default: short)", "short")
394
413
  .action(async (opts) => {
395
414
  let centralDb = null;
396
415
  try {
@@ -424,6 +443,9 @@ program
424
443
  }
425
444
  });
426
445
  }
446
+ const { formatMemoryId, buildProjectNameLookup, parseIdFormat } = await import("./lib/idFormat.js");
447
+ const idFormat = parseIdFormat(opts.idFormat);
448
+ const projectNames = buildProjectNameLookup(centralDb);
427
449
  outputResult(!!opts.json, {
428
450
  count: memories.length,
429
451
  memories: memories.map((m) => ({
@@ -433,12 +455,15 @@ program
433
455
  status: m.status,
434
456
  scope: m.scope,
435
457
  confidence: m.confidence,
458
+ project: m.project_id ? projectNames.get(m.project_id) || null : null,
436
459
  })),
437
460
  }, () => {
438
461
  console.log(`${memories.length} memories:\n`);
439
462
  for (const m of memories) {
463
+ const projectName = m.project_id ? projectNames.get(m.project_id) || null : null;
464
+ const displayId = formatMemoryId(m.id, projectName, idFormat);
440
465
  console.log(` [${m.scope}] [${m.status}] ${m.title}`);
441
- console.log(` id: ${m.id} | category: ${m.category} | confidence: ${m.confidence}`);
466
+ console.log(` id: ${displayId} | category: ${m.category} | confidence: ${m.confidence}`);
442
467
  console.log();
443
468
  }
444
469
  });
@@ -497,6 +522,7 @@ program
497
522
  }
498
523
  const tagRegistry = new GnosysTagRegistry(writeTarget.store.getStorePath());
499
524
  await tagRegistry.load();
525
+ const { GnosysIngestion } = await import("./lib/ingest.js");
500
526
  const ingestion = new GnosysIngestion(writeTarget.store, tagRegistry);
501
527
  if (!ingestion.isLLMAvailable) {
502
528
  console.error("Error: No LLM provider available. Add an API key to ~/.config/gnosys/.env or use a local model: gnosys config set provider ollama");
@@ -654,8 +680,9 @@ setupRemoteCmd
654
680
  return;
655
681
  }
656
682
  const { RemoteSync, formatStatus } = await import("./lib/remote.js");
683
+ const { withHeartbeat } = await import("./lib/heartbeat.js");
657
684
  const sync = new RemoteSync(centralDb, remotePath);
658
- const status = await sync.getStatus();
685
+ const status = await withHeartbeat("Checking remote sync status", () => sync.getStatus());
659
686
  sync.closeRemote();
660
687
  if (opts.json) {
661
688
  console.log(JSON.stringify(status, null, 2));
@@ -685,6 +712,7 @@ setupRemoteCmd
685
712
  .command("push")
686
713
  .description("Push local changes to remote")
687
714
  .option("--newer-wins", "Auto-resolve conflicts by taking the newer version")
715
+ .option("--verbose", "Stream per-memory progress to stderr")
688
716
  .action(async (opts) => {
689
717
  let centralDb = null;
690
718
  try {
@@ -699,8 +727,18 @@ setupRemoteCmd
699
727
  process.exit(1);
700
728
  }
701
729
  const { RemoteSync } = await import("./lib/remote.js");
730
+ const { withHeartbeat } = await import("./lib/heartbeat.js");
731
+ const { createProgress } = await import("./lib/progress.js");
732
+ const progress = createProgress(!!opts.verbose);
702
733
  const sync = new RemoteSync(centralDb, remotePath);
703
- const result = await sync.push({ strategy: opts.newerWins ? "newer-wins" : "skip-and-flag" });
734
+ // Suppress heartbeat when verbose is on (progress already streams).
735
+ const runPush = () => sync.push({
736
+ strategy: opts.newerWins ? "newer-wins" : "skip-and-flag",
737
+ onProgress: progress.noop ? undefined : progress.emit.bind(progress),
738
+ });
739
+ const result = opts.verbose
740
+ ? await runPush()
741
+ : await withHeartbeat("Pushing to remote", runPush);
704
742
  sync.closeRemote();
705
743
  const projParts = (result.projectsPushed || 0) > 0 ? ` | Projects pushed: ${result.projectsPushed}` : "";
706
744
  const auditParts = (result.auditPushed || 0) > 0 ? ` | Audit pushed: ${result.auditPushed}` : "";
@@ -728,6 +766,7 @@ setupRemoteCmd
728
766
  .command("pull")
729
767
  .description("Pull remote changes to local")
730
768
  .option("--newer-wins", "Auto-resolve conflicts by taking the newer version")
769
+ .option("--verbose", "Stream per-memory progress to stderr")
731
770
  .action(async (opts) => {
732
771
  let centralDb = null;
733
772
  try {
@@ -742,8 +781,17 @@ setupRemoteCmd
742
781
  process.exit(1);
743
782
  }
744
783
  const { RemoteSync } = await import("./lib/remote.js");
784
+ const { withHeartbeat } = await import("./lib/heartbeat.js");
785
+ const { createProgress } = await import("./lib/progress.js");
786
+ const progress = createProgress(!!opts.verbose);
745
787
  const sync = new RemoteSync(centralDb, remotePath);
746
- const result = await sync.pull({ strategy: opts.newerWins ? "newer-wins" : "skip-and-flag" });
788
+ const runPull = () => sync.pull({
789
+ strategy: opts.newerWins ? "newer-wins" : "skip-and-flag",
790
+ onProgress: progress.noop ? undefined : progress.emit.bind(progress),
791
+ });
792
+ const result = opts.verbose
793
+ ? await runPull()
794
+ : await withHeartbeat("Pulling from remote", runPull);
747
795
  sync.closeRemote();
748
796
  const projParts = (result.projectsPulled || 0) > 0 ? ` | Projects pulled: ${result.projectsPulled}` : "";
749
797
  const auditParts = (result.auditPulled || 0) > 0 ? ` | Audit pulled: ${result.auditPulled}` : "";
@@ -767,6 +815,7 @@ setupRemoteCmd
767
815
  .description("Two-way sync: push local changes then pull remote changes")
768
816
  .option("--auto", "Run silently for cron/LaunchAgent (skip-and-flag for conflicts)")
769
817
  .option("--newer-wins", "Auto-resolve conflicts by taking the newer version")
818
+ .option("--verbose", "Stream per-memory progress to stderr")
770
819
  .action(async (opts) => {
771
820
  let centralDb = null;
772
821
  try {
@@ -783,11 +832,20 @@ setupRemoteCmd
783
832
  process.exit(opts.auto ? 0 : 1);
784
833
  }
785
834
  const { RemoteSync } = await import("./lib/remote.js");
835
+ const { withHeartbeat } = await import("./lib/heartbeat.js");
836
+ const { createProgress } = await import("./lib/progress.js");
837
+ const progress = createProgress(!!opts.verbose);
786
838
  const sync = new RemoteSync(centralDb, remotePath);
787
- const result = await sync.sync({
839
+ const runSync = () => sync.sync({
788
840
  auto: opts.auto,
789
841
  strategy: opts.newerWins ? "newer-wins" : "skip-and-flag",
842
+ onProgress: progress.noop ? undefined : progress.emit.bind(progress),
790
843
  });
844
+ // Auto mode + verbose mode both bypass the heartbeat. Auto mode is
845
+ // for non-interactive runs (no spinner). Verbose streams its own output.
846
+ const result = opts.auto || opts.verbose
847
+ ? await runSync()
848
+ : await withHeartbeat("Syncing with remote", runSync);
791
849
  sync.closeRemote();
792
850
  if (!opts.auto || result.conflicts.length > 0 || result.errors.length > 0) {
793
851
  const pp = result.projectsPushed || 0;
@@ -865,6 +923,14 @@ setupCmd
865
923
  const { runDreamSetup } = await import("./lib/setup.js");
866
924
  await runDreamSetup({ directory: process.cwd() });
867
925
  });
926
+ // `gnosys setup chat` — configure chat TUI (provider, recall, tools, prefix)
927
+ setupCmd
928
+ .command("chat")
929
+ .description("Configure the chat TUI — provider/model, recall behavior, tools, system-prompt prefix")
930
+ .action(async () => {
931
+ const { runChatSetup } = await import("./lib/setup.js");
932
+ await runChatSetup({ directory: process.cwd() });
933
+ });
868
934
  // `gnosys setup ides` — configure IDE / MCP integrations standalone
869
935
  setupCmd
870
936
  .command("ides")
@@ -1730,6 +1796,7 @@ program
1730
1796
  }
1731
1797
  const tagRegistry = new GnosysTagRegistry(writeTarget.store.getStorePath());
1732
1798
  await tagRegistry.load();
1799
+ const { GnosysIngestion } = await import("./lib/ingest.js");
1733
1800
  const ingestion = new GnosysIngestion(writeTarget.store, tagRegistry);
1734
1801
  if (!ingestion.isLLMAvailable) {
1735
1802
  console.error("Error: No LLM provider available. Add an API key to ~/.config/gnosys/.env or use a local model: gnosys config set provider ollama");
@@ -2279,6 +2346,7 @@ program
2279
2346
  process.exit(1);
2280
2347
  }
2281
2348
  // Show what we'll scan
2349
+ const { bootstrap, discoverFiles } = await import("./lib/bootstrap.js");
2282
2350
  const files = await discoverFiles(sourceDir, opts.pattern);
2283
2351
  console.log(`Found ${files.length} files in ${sourceDir}\n`);
2284
2352
  if (files.length === 0) {
@@ -2366,6 +2434,8 @@ const importCmd = program
2366
2434
  }
2367
2435
  const tagRegistry = new GnosysTagRegistry(writeTarget.store.getStorePath());
2368
2436
  await tagRegistry.load();
2437
+ const { GnosysIngestion } = await import("./lib/ingest.js");
2438
+ const { performImport, formatImportSummary } = await import("./lib/import.js");
2369
2439
  const ingestion = new GnosysIngestion(writeTarget.store, tagRegistry);
2370
2440
  const format = opts.format;
2371
2441
  const mode = opts.mode;
@@ -2485,6 +2555,8 @@ program
2485
2555
  for (const s of stores) {
2486
2556
  await search.addStoreMemories(s.store, s.label);
2487
2557
  }
2558
+ const { GnosysEmbeddings } = await import("./lib/embeddings.js");
2559
+ const { GnosysHybridSearch } = await import("./lib/hybridSearch.js");
2488
2560
  const embeddings = new GnosysEmbeddings(storePath);
2489
2561
  const hybridSearch = new GnosysHybridSearch(search, embeddings, resolver, storePath);
2490
2562
  console.log("Building semantic embeddings (downloading model on first run)...");
@@ -2561,6 +2633,8 @@ program
2561
2633
  for (const s of stores) {
2562
2634
  await search.addStoreMemories(s.store, s.label);
2563
2635
  }
2636
+ const { GnosysEmbeddings } = await import("./lib/embeddings.js");
2637
+ const { GnosysHybridSearch } = await import("./lib/hybridSearch.js");
2564
2638
  const embeddings = new GnosysEmbeddings(storePath);
2565
2639
  const hybridSearch = new GnosysHybridSearch(search, embeddings, resolver, storePath);
2566
2640
  const mode = opts.mode;
@@ -2608,6 +2682,8 @@ program
2608
2682
  for (const s of stores) {
2609
2683
  await search.addStoreMemories(s.store, s.label);
2610
2684
  }
2685
+ const { GnosysEmbeddings } = await import("./lib/embeddings.js");
2686
+ const { GnosysHybridSearch } = await import("./lib/hybridSearch.js");
2611
2687
  const embeddings = new GnosysEmbeddings(storePath);
2612
2688
  const hybridSearch = new GnosysHybridSearch(search, embeddings, resolver, storePath);
2613
2689
  const results = await hybridSearch.hybridSearch(query, parseInt(opts.limit), "semantic");
@@ -2656,11 +2732,30 @@ program
2656
2732
  for (const s of stores) {
2657
2733
  await search.addStoreMemories(s.store, s.label);
2658
2734
  }
2735
+ const { GnosysEmbeddings } = await import("./lib/embeddings.js");
2736
+ const { GnosysHybridSearch } = await import("./lib/hybridSearch.js");
2737
+ const { GnosysAsk } = await import("./lib/ask.js");
2659
2738
  const embeddings = new GnosysEmbeddings(storePath);
2660
2739
  const hybridSearch = new GnosysHybridSearch(search, embeddings, resolver, storePath);
2661
2740
  const ask = new GnosysAsk(hybridSearch, cliConfig, resolver, storePath);
2662
2741
  if (!ask.isLLMAvailable) {
2663
- console.error("No LLM provider available. Set ANTHROPIC_API_KEY or switch to Ollama: gnosys config set provider ollama");
2742
+ // v5.8.0 (#8): provider-aware error instead of hardcoded ANTHROPIC_API_KEY.
2743
+ const providerName = cliConfig.llm.defaultProvider;
2744
+ const envVarMap = {
2745
+ anthropic: "ANTHROPIC_API_KEY",
2746
+ openai: "OPENAI_API_KEY",
2747
+ groq: "GROQ_API_KEY",
2748
+ xai: "XAI_API_KEY",
2749
+ mistral: "MISTRAL_API_KEY",
2750
+ };
2751
+ const envVar = envVarMap[providerName];
2752
+ if (envVar) {
2753
+ console.error(`No LLM provider available. Configured default is "${providerName}" but its key wasn't found. ` +
2754
+ `Set ${envVar}, run 'gnosys setup' to store one in the macOS Keychain, or add llm.${providerName}.apiKey to gnosys.json.`);
2755
+ }
2756
+ else {
2757
+ console.error(`No LLM provider available. Provider "${providerName}" is not reachable. Run 'gnosys setup' to configure one.`);
2758
+ }
2664
2759
  process.exit(1);
2665
2760
  }
2666
2761
  // If --federated, pre-retrieve from central DB and inject as context
@@ -3007,41 +3102,9 @@ program
3007
3102
  console.log("");
3008
3103
  console.log(formatGraphStats(stats));
3009
3104
  });
3010
- // ─── gnosys dashboard ───────────────────────────────────────────────────
3011
- // v5.7.0: kept as a thin alias of `gnosys status --system`. Will be removed
3012
- // in a future release; use `gnosys status` instead.
3013
- program
3014
- .command("dashboard")
3015
- .description("(alias) Show system health — equivalent to 'gnosys status --system'")
3016
- .option("--json", "Output as JSON instead of pretty table")
3017
- .action(async (opts) => {
3018
- const { collectDashboardData, formatDashboard, formatDashboardJSON } = await import("./lib/dashboard.js");
3019
- const resolver = await getResolver();
3020
- const stores = resolver.getStores();
3021
- if (stores.length === 0) {
3022
- console.error("No Gnosys stores found. Run gnosys init first.");
3023
- process.exit(1);
3024
- }
3025
- const cfg = await loadConfig(stores[0].path);
3026
- // v5.1: Use central DB for dashboard stats
3027
- let dashDb;
3028
- try {
3029
- const db = GnosysDB.openCentral();
3030
- if (db.isAvailable() && db.isMigrated()) {
3031
- dashDb = db;
3032
- }
3033
- }
3034
- catch {
3035
- // Central DB not available — legacy dashboard only
3036
- }
3037
- const data = await collectDashboardData(resolver, cfg, pkg.version, dashDb);
3038
- if (opts.json) {
3039
- console.log(formatDashboardJSON(data));
3040
- }
3041
- else {
3042
- console.log(formatDashboard(data));
3043
- }
3044
- });
3105
+ // `gnosys dashboard` was removed in v5.7.1.
3106
+ // Use `gnosys status --system` instead. Hard removal — commander will emit
3107
+ // the standard "unknown command" error.
3045
3108
  // ─── gnosys maintain ─────────────────────────────────────────────────────
3046
3109
  program
3047
3110
  .command("maintain")
@@ -3125,12 +3188,19 @@ program
3125
3188
  }
3126
3189
  });
3127
3190
  // NOTE: gnosys migrate is defined below (near the end) with --to-central support
3128
- // ─── gnosys upgrade ─────────────────────────────────────────────────────
3129
- program
3130
- .command("upgrade")
3131
- .description("Re-initialize all registered projects after a Gnosys version upgrade. Updates agent rules, project registry, stamps the central DB, and regenerates the portfolio dashboard.")
3132
- .option("--skip-dashboard", "Skip regenerating the portfolio dashboard")
3133
- .action(async (opts) => {
3191
+ // ─── gnosys upgrade + gnosys setup sync-projects ──────────────────────
3192
+ //
3193
+ // v5.7.1 (#15) split this command:
3194
+ //
3195
+ // gnosys upgrade — upgrade the gnosys CLI/MCP itself
3196
+ // (npm install + restart signal to MCPs)
3197
+ // gnosys setup sync-projects — what the old `gnosys upgrade` used to do
3198
+ // (re-init project identities, agent rules,
3199
+ // central DB stamp, portfolio dashboard)
3200
+ //
3201
+ // The body of the legacy command is preserved verbatim below as
3202
+ // `syncProjectsAction`, called from the new `setup sync-projects` command.
3203
+ async function syncProjectsAction(opts) {
3134
3204
  const currentVersion = pkg.version;
3135
3205
  console.log(`Gnosys v${currentVersion} — upgrading registered projects...\n`);
3136
3206
  // 1. Read registered projects from file registry AND central DB
@@ -3349,6 +3419,76 @@ program
3349
3419
  console.log(`\n Could not regenerate portfolio dashboard`);
3350
3420
  }
3351
3421
  }
3422
+ }
3423
+ // `gnosys setup sync-projects` — re-init project identities + agent rules.
3424
+ // (This is what `gnosys upgrade` used to do; renamed in v5.7.1.)
3425
+ setupCmd
3426
+ .command("sync-projects")
3427
+ .description("Re-initialize all registered projects after upgrading gnosys: refresh agent rules, project registry, central DB stamp, and portfolio dashboard.")
3428
+ .option("--skip-dashboard", "Skip regenerating the portfolio dashboard")
3429
+ .action(syncProjectsAction);
3430
+ // `gnosys upgrade` — upgrade the gnosys CLI/MCP itself, then prompt the
3431
+ // user to run sync-projects. Writes ~/.gnosys/last-upgrade-at so running
3432
+ // MCP servers exit cleanly and the host respawns them against the new
3433
+ // global binary (see src/lib/upgrade.ts).
3434
+ program
3435
+ .command("upgrade")
3436
+ .description("Upgrade gnosys itself (npm install -g gnosys@latest) and signal running MCP servers to restart. After upgrading, suggests running 'gnosys setup sync-projects'.")
3437
+ .option("--yes", "Skip the post-upgrade sync-projects prompt and exit")
3438
+ .option("--no-sync", "Don't suggest running sync-projects afterward")
3439
+ .action(async (opts) => {
3440
+ const currentVersion = pkg.version;
3441
+ console.log(`Gnosys CLI: currently v${currentVersion}`);
3442
+ console.log(`Running: npm install -g gnosys@latest ...`);
3443
+ const { execSync } = await import("child_process");
3444
+ try {
3445
+ execSync("npm install -g gnosys@latest", { stdio: "inherit" });
3446
+ }
3447
+ catch (err) {
3448
+ console.error(`\nUpgrade failed: ${err instanceof Error ? err.message : err}`);
3449
+ console.error(`Try running 'npm install -g gnosys@latest' manually.`);
3450
+ process.exit(1);
3451
+ }
3452
+ // Read the newly-installed version (best-effort — we may still be the
3453
+ // old binary in-process; this is purely informational).
3454
+ let newVersion = "(see npm output)";
3455
+ try {
3456
+ const out = execSync("npm ls -g gnosys --depth=0 --json", { encoding: "utf8" });
3457
+ const parsed = JSON.parse(out);
3458
+ newVersion = parsed?.dependencies?.gnosys?.version || newVersion;
3459
+ }
3460
+ catch {
3461
+ // Best-effort lookup only.
3462
+ }
3463
+ // Write the marker so any running MCP servers exit and respawn.
3464
+ const { writeUpgradeMarker } = await import("./lib/upgrade.js");
3465
+ try {
3466
+ writeUpgradeMarker(typeof newVersion === "string" && newVersion !== "(see npm output)"
3467
+ ? newVersion
3468
+ : currentVersion);
3469
+ console.log(`\n✓ Upgrade marker written: ~/.gnosys/last-upgrade-at`);
3470
+ console.log(` Any running MCP servers will detect this within 10s and restart cleanly.`);
3471
+ console.log(` (Your MCP client — Claude Code, Cursor, VS Code — will auto-respawn.)`);
3472
+ }
3473
+ catch (err) {
3474
+ console.error(`\nCould not write upgrade marker: ${err instanceof Error ? err.message : err}`);
3475
+ console.error(`Running MCP servers will need to be restarted manually.`);
3476
+ }
3477
+ if (opts.sync === false || opts.yes) {
3478
+ console.log(`\nDone. Run 'gnosys setup sync-projects' when you're ready to refresh registered projects.`);
3479
+ return;
3480
+ }
3481
+ // Prompt for sync-projects.
3482
+ const readline = await import("readline");
3483
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
3484
+ const answer = await new Promise((resolve) => rl.question(`\nRun 'gnosys setup sync-projects' now to refresh registered projects? [Y/n] `, resolve));
3485
+ rl.close();
3486
+ if (answer.trim().toLowerCase() === "n" || answer.trim().toLowerCase() === "no") {
3487
+ console.log(`Done. You can run 'gnosys setup sync-projects' later.`);
3488
+ return;
3489
+ }
3490
+ console.log(``);
3491
+ await syncProjectsAction({});
3352
3492
  });
3353
3493
  // ─── gnosys doctor ──────────────────────────────────────────────────────
3354
3494
  program
@@ -3494,6 +3634,7 @@ program
3494
3634
  // Check embeddings
3495
3635
  if (stores.length > 0) {
3496
3636
  console.log("Embeddings:");
3637
+ const { GnosysEmbeddings } = await import("./lib/embeddings.js");
3497
3638
  const embeddings = new GnosysEmbeddings(stores[0].path);
3498
3639
  try {
3499
3640
  const stats = embeddings.getStats();
@@ -4299,6 +4440,7 @@ program
4299
4440
  console.error("No writable store found. Run 'gnosys init' first.");
4300
4441
  process.exit(1);
4301
4442
  }
4443
+ const { migrate, formatMigrationReport } = await import("./lib/migrate.js");
4302
4444
  const stats = await migrate(writeTarget.store.getStorePath(), { verbose: opts.verbose });
4303
4445
  console.log(formatMigrationReport(stats));
4304
4446
  return;
@@ -4858,80 +5000,70 @@ program
4858
5000
  centralDb?.close();
4859
5001
  }
4860
5002
  });
4861
- // ─── gnosys portfolio ───────────────────────────────────────────────────
5003
+ // `gnosys portfolio` was removed in v5.7.1.
5004
+ // Use `gnosys status --projects` (formerly --global) for the projects
5005
+ // overview, or `gnosys status --web` for the HTML dashboard, or
5006
+ // `gnosys status --projects --output file.html` to write to disk.
5007
+ // ─── gnosys status ──────────────────────────────────────────────────────
5008
+ // v5.7.1 (#11): the catch-all status command. Section flags select what to
5009
+ // show; output flags control format. Default (no flag) is the current
5010
+ // project. `dashboard` and `portfolio` were removed in v5.7.1 — their
5011
+ // content lives under `--system` and `--projects` respectively.
4862
5012
  program
4863
- .command("portfolio")
4864
- .description("Portfolio dashboard all projects with status, roadmap, and recent activity")
4865
- .option("-o, --output <file>", "Write dashboard to a file (auto-detects format from extension)")
4866
- .option("--html", "Output as HTML dashboard")
5013
+ .command("status")
5014
+ .description("Show status. Sections: --projects (all projects) · --remote (sync) · --system (memory/LLM health) · default: current project. Output: --web · --json. Note: 'gnosys dashboard' and 'gnosys portfolio' were removed in v5.7.1 — use 'gnosys status --system' and 'gnosys status --projects' instead.")
5015
+ .option("-d, --directory <dir>", "Project directory (auto-detects if omitted)")
5016
+ .option("-p, --project <id>", "Project ID")
5017
+ .option("-g, --global", "(deprecated alias for --projects)")
5018
+ .option("--projects", "Show all projects portfolio (replaces the old 'gnosys portfolio')")
5019
+ .option("-r, --remote", "Show remote sync status (alias for 'gnosys setup remote status')")
5020
+ .option("-w, --web", "Open the HTML dashboard in the browser")
5021
+ .option("-s, --system", "Show system health (memory count, LLM connectivity, embeddings, archive)")
4867
5022
  .option("--json", "Output as JSON")
4868
5023
  .action(async (opts) => {
4869
- let centralDb = null;
4870
- try {
4871
- centralDb = GnosysDB.openCentral();
4872
- if (!centralDb.isAvailable()) {
4873
- console.error("Central DB not available.");
4874
- process.exit(1);
4875
- }
4876
- const { generatePortfolio, formatPortfolioMarkdown } = await import("./lib/portfolio.js");
4877
- const report = generatePortfolio(centralDb);
4878
- // Detect format from output extension if not explicitly set
4879
- const useHtml = opts.html || (opts.output?.endsWith(".html") ?? false);
4880
- const useJson = opts.json || (opts.output?.endsWith(".json") ?? false);
4881
- if (useJson) {
4882
- const json = JSON.stringify(report, null, 2);
4883
- if (opts.output) {
4884
- const { writeFileSync } = await import("fs");
4885
- writeFileSync(opts.output, json, "utf-8");
4886
- console.log(`Portfolio written to ${opts.output}`);
5024
+ // v5.7.1: --projects supersedes --global (kept as alias).
5025
+ if (opts.projects)
5026
+ opts.global = true;
5027
+ // v5.7.1: --remote — dispatch to RemoteSync.getStatus()
5028
+ if (opts.remote) {
5029
+ let remoteCentralDb = null;
5030
+ try {
5031
+ remoteCentralDb = GnosysDB.openLocal();
5032
+ if (!remoteCentralDb.isAvailable()) {
5033
+ console.error("Central DB not available.");
5034
+ process.exit(1);
4887
5035
  }
4888
- else {
4889
- console.log(json);
5036
+ const remotePath = remoteCentralDb.getMeta("remote_path");
5037
+ if (!remotePath) {
5038
+ if (opts.json) {
5039
+ console.log(JSON.stringify({ configured: false, message: "Remote not configured. Run 'gnosys setup remote'." }, null, 2));
5040
+ }
5041
+ else {
5042
+ console.log("Remote sync: not configured. Run 'gnosys setup remote' to set up multi-machine sync.");
5043
+ }
5044
+ return;
4890
5045
  }
4891
- return;
4892
- }
4893
- if (useHtml) {
4894
- const { generatePortfolioHtml } = await import("./lib/portfolioHtml.js");
4895
- const html = generatePortfolioHtml(report, opts.output);
4896
- if (opts.output) {
4897
- const { writeFileSync } = await import("fs");
4898
- writeFileSync(opts.output, html, "utf-8");
4899
- console.log(`Portfolio dashboard written to ${opts.output}`);
5046
+ const { RemoteSync, formatStatus } = await import("./lib/remote.js");
5047
+ const { withHeartbeat } = await import("./lib/heartbeat.js");
5048
+ const sync = new RemoteSync(remoteCentralDb, remotePath);
5049
+ const status = await withHeartbeat("Checking remote sync status", () => sync.getStatus());
5050
+ sync.closeRemote();
5051
+ if (opts.json) {
5052
+ console.log(JSON.stringify(status, null, 2));
4900
5053
  }
4901
5054
  else {
4902
- console.log(html);
5055
+ console.log(formatStatus(status));
4903
5056
  }
4904
5057
  return;
4905
5058
  }
4906
- const markdown = formatPortfolioMarkdown(report);
4907
- if (opts.output) {
4908
- const { writeFileSync } = await import("fs");
4909
- writeFileSync(opts.output, markdown, "utf-8");
4910
- console.log(`Portfolio dashboard written to ${opts.output}`);
5059
+ catch (err) {
5060
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
5061
+ process.exit(1);
4911
5062
  }
4912
- else {
4913
- console.log(markdown);
5063
+ finally {
5064
+ remoteCentralDb?.close();
4914
5065
  }
4915
5066
  }
4916
- catch (err) {
4917
- console.error(`Error: ${err instanceof Error ? err.message : err}`);
4918
- process.exit(1);
4919
- }
4920
- finally {
4921
- centralDb?.close();
4922
- }
4923
- });
4924
- // ─── gnosys status ──────────────────────────────────────────────────────
4925
- program
4926
- .command("status")
4927
- .description("Show project status (--global: all projects, --web: HTML dashboard, --system: memory/LLM health)")
4928
- .option("-d, --directory <dir>", "Project directory (auto-detects if omitted)")
4929
- .option("-p, --project <id>", "Project ID")
4930
- .option("-g, --global", "Show all projects")
4931
- .option("-w, --web", "Open the HTML dashboard in the browser")
4932
- .option("-s, --system", "Show system health (memory count, LLM connectivity, embeddings, archive)")
4933
- .option("--json", "Output as JSON")
4934
- .action(async (opts) => {
4935
5067
  // --system delegates to the dashboard formatter (formerly `gnosys dashboard`).
4936
5068
  if (opts.system) {
4937
5069
  const { collectDashboardData, formatDashboard, formatDashboardJSON } = await import("./lib/dashboard.js");