gnosys 5.4.0 → 5.4.2

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 (45) hide show
  1. package/README.md +114 -8
  2. package/dist/cli.js +157 -63
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +202 -99
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/config.d.ts +0 -5
  7. package/dist/lib/config.d.ts.map +1 -1
  8. package/dist/lib/config.js +71 -9
  9. package/dist/lib/config.js.map +1 -1
  10. package/dist/lib/dashboard.d.ts +14 -0
  11. package/dist/lib/dashboard.d.ts.map +1 -1
  12. package/dist/lib/dashboard.js +200 -94
  13. package/dist/lib/dashboard.js.map +1 -1
  14. package/dist/lib/db.d.ts +87 -3
  15. package/dist/lib/db.d.ts.map +1 -1
  16. package/dist/lib/db.js +236 -17
  17. package/dist/lib/db.js.map +1 -1
  18. package/dist/lib/desktopNotify.d.ts +31 -0
  19. package/dist/lib/desktopNotify.d.ts.map +1 -0
  20. package/dist/lib/desktopNotify.js +80 -0
  21. package/dist/lib/desktopNotify.js.map +1 -0
  22. package/dist/lib/dream.d.ts +12 -0
  23. package/dist/lib/dream.d.ts.map +1 -1
  24. package/dist/lib/dream.js +82 -2
  25. package/dist/lib/dream.js.map +1 -1
  26. package/dist/lib/ingest.d.ts.map +1 -1
  27. package/dist/lib/ingest.js +24 -4
  28. package/dist/lib/ingest.js.map +1 -1
  29. package/dist/lib/lock.d.ts +5 -0
  30. package/dist/lib/lock.d.ts.map +1 -1
  31. package/dist/lib/lock.js +9 -0
  32. package/dist/lib/lock.js.map +1 -1
  33. package/dist/lib/remote.d.ts +15 -0
  34. package/dist/lib/remote.d.ts.map +1 -1
  35. package/dist/lib/remote.js +85 -0
  36. package/dist/lib/remote.js.map +1 -1
  37. package/dist/lib/setup.d.ts +15 -0
  38. package/dist/lib/setup.d.ts.map +1 -1
  39. package/dist/lib/setup.js +228 -0
  40. package/dist/lib/setup.js.map +1 -1
  41. package/dist/lib/store.d.ts +2 -0
  42. package/dist/lib/store.d.ts.map +1 -1
  43. package/dist/lib/store.js +4 -11
  44. package/dist/lib/store.js.map +1 -1
  45. package/package.json +2 -1
package/README.md CHANGED
@@ -24,7 +24,7 @@
24
24
 
25
25
  Gnosys is **sandbox-first**: a persistent background process holds the database connection while agents import a tiny helper library and call memory operations like normal code — no MCP schemas, no round-trips, near-zero context cost. The central brain at `~/.gnosys/gnosys.db` unifies all projects, user preferences, and global knowledge. Federated search ranks results across scopes with tier boosting and recency awareness. The **Web Knowledge Base** turns any website into a searchable knowledge base for serverless chatbots — pre-computed JSON index, zero runtime dependencies. **Multimodal ingestion** handles PDFs, images, audio, and video. **Portfolio Dashboard** gives a bird's-eye view of all projects. Process tracing builds call chains from source code. Dream Mode consolidates knowledge during idle time. One-command export regenerates a full Obsidian vault.
26
26
 
27
- It also runs as a CLI and a complete MCP server that drops straight into Cursor, Claude Desktop, Claude Code, Cowork, Codex, or any MCP client.
27
+ It also runs as a CLI and a complete MCP server that drops straight into Cursor, Claude Desktop (Chat / Cowork / Code), Claude Code, Codex, Gemini CLI, Antigravity, or any MCP client.
28
28
 
29
29
  No vector DBs. No black boxes. No external services. Just SQLite and optional Obsidian export — the way knowledge should be.
30
30
 
@@ -53,7 +53,7 @@ Gnosys takes a different approach: the central brain is a single SQLite database
53
53
  - **Bulk import** — CSV, JSON, JSONL. Import entire datasets in seconds.
54
54
  - **Obsidian-native** — `gnosys export` generates a full vault with YAML frontmatter, `[[wikilinks]]`, summaries, and graph data.
55
55
  - **Multi-machine sync (v5.3.0)** — share your `gnosys.db` across machines via NAS or shared drive. Local cache for speed, remote source of truth for consistency. Built-in conflict detection with skip-and-flag resolution. Run `gnosys remote configure` to set up.
56
- - **MCP-compatible** — also runs as a full MCP server that drops into Cursor, Claude Desktop, Claude Code, Cowork, Codex, or any MCP client.
56
+ - **MCP-compatible** — also runs as a full MCP server that drops into Cursor, Claude Desktop (Chat / Cowork / Code), Claude Code, Codex, Gemini CLI, Antigravity, or any MCP client.
57
57
  - **Zero infrastructure** — no external databases, no Docker (unless you want it), no cloud services. Just `npm install`.
58
58
 
59
59
  > For the complete CLI reference and detailed guides, see the **[User Guide](https://gnosys.ai/guide.html)**.
@@ -235,9 +235,81 @@ This improves your site's visibility in AI-powered search results and enables LL
235
235
 
236
236
  ## MCP Server Setup
237
237
 
238
- ### Claude Desktop
238
+ The fastest way to wire gnosys into any supported client is to run `gnosys init <ide>` from the project directory you want memory-enabled. Examples:
239
239
 
240
- Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
240
+ ```bash
241
+ gnosys init claude-desktop # Claude Desktop (covers Chat, Cowork, and Code)
242
+ gnosys init claude # Claude Code CLI
243
+ gnosys init cursor # Cursor
244
+ gnosys init codex # Codex
245
+ gnosys init gemini-cli # Gemini CLI
246
+ gnosys init antigravity # Google Antigravity
247
+ ```
248
+
249
+ This does two things at once:
250
+ 1. Wires gnosys into the IDE's MCP config (idempotent — safe to re-run).
251
+ 2. Initializes the current directory as a gnosys project (creates `.gnosys/gnosys.json`, registers it in the central DB) so your memories can be scoped to it.
252
+
253
+ ### One-time vs. per-project
254
+
255
+ The IDE wiring writes to a **user-level** config file, so it only needs to happen **once**. Re-running it in another project just re-merges the same `mcpServers.gnosys` entry — harmless.
256
+
257
+ The project registration is **per-directory**: every codebase you want to be memory-aware of needs its own `gnosys init`. From then on, agents pass `projectRoot: "/path/to/project"` to gnosys MCP tools to scope memory to that codebase.
258
+
259
+ ```bash
260
+ # Once, anywhere — wires Claude Desktop's MCP config:
261
+ gnosys init claude-desktop
262
+
263
+ # Once per project — registers the codebase in the central DB:
264
+ cd /path/to/project-a && gnosys init
265
+ cd /path/to/project-b && gnosys init
266
+ ```
267
+
268
+ > **Cowork users:** Cowork sessions don't have a working directory like a CLI does. The agent in Cowork uses whichever `projectRoot` it's told to use (typically auto-detected from open files or set via the system prompt). The "every working directory" question doesn't apply to Cowork itself — only to the projects you want memory-enabled. Run `gnosys init claude-desktop` once globally; run `gnosys init` per project.
269
+
270
+ ---
271
+
272
+ ## Setup Wizards
273
+
274
+ `gnosys setup` runs the full interactive wizard. To configure just one piece without walking the whole thing, use one of these subcommands:
275
+
276
+ ```bash
277
+ gnosys setup # full wizard (provider, models, IDE, remote sync, dream)
278
+ gnosys setup models # LLM provider + model only
279
+ gnosys setup remote # multi-machine sync (NAS/shared drive)
280
+ gnosys setup dream # Dream Mode designation, schedule, sub-tasks
281
+ ```
282
+
283
+ ### Dream Mode setup (v5.4.2+)
284
+
285
+ Dream Mode is the idle-time consolidation engine — confidence decay, summary generation, self-critique, relationship discovery. With multi-machine sync (v5.3.0+), running dream cycles from every machine wastes work and fights for SQLite write locks. v5.4.2 introduces a **single designated machine** model:
286
+
287
+ - Run `gnosys setup dream` on the machine you want to host dream cycles.
288
+ - The wizard validates your provider/model with a live API probe before saving.
289
+ - Other machines stay quiet — they see the designation in the central DB and skip the scheduler.
290
+ - `gnosys dream log` shows recent runs; `gnosys dashboard` has a `DREAM HEALTH` section with last-run timestamp, designated machine, and consecutive-failure counter.
291
+ - If the designated machine's LLM provider becomes unreachable, you'll see warnings at three layers: in audit log entries (`dream_provider_unreachable`), as stderr at MCP startup, and as a desktop notification after 3 consecutive failures.
292
+
293
+ ### Removed in v5.4.2
294
+
295
+ The following commands were removed in favor of the canonical `gnosys setup <thing>` form:
296
+
297
+ | Removed | Use instead |
298
+ |---|---|
299
+ | `gnosys models` | `gnosys setup models` |
300
+ | `gnosys remote configure` | `gnosys setup remote` |
301
+
302
+ `gnosys remote push|pull|sync|status` remain unchanged — only `configure` moved.
303
+
304
+ ---
305
+
306
+ ### Manual config (if you prefer)
307
+
308
+ If you'd rather edit configs by hand, here's where each client looks for MCP servers.
309
+
310
+ #### Claude Desktop (Chat, Cowork, and Code share the same config)
311
+
312
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS), `%APPDATA%\Claude\claude_desktop_config.json` (Windows), or `~/.config/Claude/claude_desktop_config.json` (Linux):
241
313
 
242
314
  ```json
243
315
  {
@@ -250,9 +322,11 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
250
322
  }
251
323
  ```
252
324
 
253
- ### Cursor
325
+ Restart Claude Desktop after editing.
326
+
327
+ #### Cursor
254
328
 
255
- Add to `.cursor/mcp.json`:
329
+ Add to `.cursor/mcp.json` in your project:
256
330
 
257
331
  ```json
258
332
  {
@@ -265,13 +339,13 @@ Add to `.cursor/mcp.json`:
265
339
  }
266
340
  ```
267
341
 
268
- ### Claude Code
342
+ #### Claude Code
269
343
 
270
344
  ```bash
271
345
  claude mcp add gnosys gnosys serve
272
346
  ```
273
347
 
274
- ### Codex
348
+ #### Codex
275
349
 
276
350
  Add to `.codex/config.toml`:
277
351
 
@@ -281,6 +355,38 @@ type = "local"
281
355
  command = ["gnosys", "serve"]
282
356
  ```
283
357
 
358
+ #### Gemini CLI
359
+
360
+ Add to `~/.gemini/settings.json` (preserves any existing settings):
361
+
362
+ ```json
363
+ {
364
+ "mcpServers": {
365
+ "gnosys": {
366
+ "command": "gnosys",
367
+ "args": ["serve"]
368
+ }
369
+ }
370
+ }
371
+ ```
372
+
373
+ #### Antigravity
374
+
375
+ Add to `~/.gemini/antigravity/mcp_config.json`:
376
+
377
+ ```json
378
+ {
379
+ "mcpServers": {
380
+ "gnosys": {
381
+ "command": "gnosys",
382
+ "args": ["serve"]
383
+ }
384
+ }
385
+ }
386
+ ```
387
+
388
+ Antigravity reloads MCP servers automatically when you save the file.
389
+
284
390
  > **Note:** API keys are configured via `gnosys setup` (macOS Keychain, environment variable, or `~/.config/gnosys/.env`). See [LLM Provider Setup](https://gnosys.ai/guide.html#guide-llm-provider-setup) in the User Guide.
285
391
 
286
392
  ---
package/dist/cli.js CHANGED
@@ -503,11 +503,12 @@ setupCmd
503
503
  // `gnosys setup remote` — configure remote sync (alias for `gnosys remote configure`)
504
504
  setupCmd
505
505
  .command("remote")
506
- .description("Configure multi-machine sync (alias for 'gnosys remote configure')")
506
+ .description("Configure multi-machine sync (NAS/shared drive)")
507
507
  .option("--path <path>", "Set remote path directly (non-interactive)")
508
508
  .action(async (opts) => {
509
509
  const { GnosysDB } = await import("./lib/db.js");
510
- const db = GnosysDB.openCentral();
510
+ // Sync configuration needs explicit local DB access (not auto-routed remote).
511
+ const db = GnosysDB.openLocal();
511
512
  if (!db.isAvailable()) {
512
513
  console.error("Central DB not available.");
513
514
  db.close();
@@ -527,17 +528,18 @@ setupCmd
527
528
  db.close();
528
529
  }
529
530
  });
530
- // ─── gnosys models (top-level shortcut) ─────────────────────────────────
531
- program
532
- .command("models")
533
- .description("Quick model operationslist available, refresh cache, or set the default")
534
- .option("--list", "List available models for the current provider")
535
- .option("--refresh", "Refresh model list from OpenRouter (clears the cache)")
536
- .option("--set <model>", "Set the default model for the current provider")
537
- .action(async (opts) => {
538
- const { runModelsCommand } = await import("./lib/setup.js");
539
- await runModelsCommand(opts);
531
+ // `gnosys setup dream` — configure dream mode (designation, provider, schedule)
532
+ setupCmd
533
+ .command("dream")
534
+ .description("Configure Dream Modedesignate this machine, pick provider/model, set schedule")
535
+ .action(async () => {
536
+ const { runDreamSetup } = await import("./lib/setup.js");
537
+ await runDreamSetup({ directory: process.cwd() });
540
538
  });
539
+ // v5.4.2 removal: `gnosys models` (top-level shortcut) was removed in favor
540
+ // of the canonical `gnosys setup models` form. The implementation function
541
+ // runModelsCommand() in setup.ts is no longer wired but kept for now in case
542
+ // we need to revive a top-level shortcut later.
541
543
  // ─── gnosys init ─────────────────────────────────────────────────────────
542
544
  program
543
545
  .command("init [ide]")
@@ -1713,14 +1715,60 @@ program
1713
1715
  .command("graph")
1714
1716
  .description("Show the full cross-reference graph across all memories")
1715
1717
  .action(async () => {
1716
- const resolver = await getResolver();
1717
- const allMemories = await resolver.getAllMemories();
1718
- if (allMemories.length === 0) {
1719
- console.log("No memories found.");
1720
- return;
1718
+ // v5.4.1: Query the central DB directly. Previously this used the
1719
+ // filesystem resolver, which returns nothing in v5.x DB-only mode
1720
+ // because memories no longer live as markdown files.
1721
+ let centralDb = null;
1722
+ try {
1723
+ centralDb = GnosysDB.openCentral();
1724
+ if (!centralDb.isAvailable()) {
1725
+ console.error("Central DB not available.");
1726
+ process.exit(1);
1727
+ }
1728
+ const dbMemories = centralDb.getAllMemories();
1729
+ if (dbMemories.length === 0) {
1730
+ console.log("No memories found.");
1731
+ return;
1732
+ }
1733
+ // Adapt DbMemory → legacy Memory shape that buildLinkGraph expects.
1734
+ // The graph builder only reads id, title, content, and synthesises
1735
+ // a filesystem-style path for display.
1736
+ const adapted = dbMemories.map((m) => {
1737
+ let parsedTags = [];
1738
+ try {
1739
+ parsedTags = JSON.parse(m.tags);
1740
+ }
1741
+ catch {
1742
+ parsedTags = [];
1743
+ }
1744
+ const relativePath = `${m.category}/${m.id}.md`;
1745
+ return {
1746
+ frontmatter: {
1747
+ id: m.id,
1748
+ title: m.title,
1749
+ category: m.category,
1750
+ tags: parsedTags,
1751
+ relevance: m.relevance,
1752
+ author: m.author,
1753
+ authority: m.authority,
1754
+ confidence: m.confidence,
1755
+ created: m.created,
1756
+ modified: m.modified,
1757
+ last_reviewed: m.modified,
1758
+ status: m.status,
1759
+ supersedes: m.supersedes,
1760
+ },
1761
+ content: m.content,
1762
+ filePath: relativePath,
1763
+ relativePath,
1764
+ };
1765
+ });
1766
+ const graph = buildLinkGraph(adapted);
1767
+ console.log(formatGraphSummary(graph));
1768
+ }
1769
+ finally {
1770
+ centralDb?.close();
1721
1771
  }
1722
- const graph = buildLinkGraph(allMemories);
1723
- console.log(formatGraphSummary(graph));
1724
1772
  });
1725
1773
  // ─── gnosys bootstrap <sourceDir> ────────────────────────────────────────
1726
1774
  program
@@ -2541,7 +2589,8 @@ remoteCmd
2541
2589
  .action(async (opts) => {
2542
2590
  let centralDb = null;
2543
2591
  try {
2544
- centralDb = GnosysDB.openCentral();
2592
+ // Sync operations need explicit local DB access (not auto-routed remote).
2593
+ centralDb = GnosysDB.openLocal();
2545
2594
  if (!centralDb.isAvailable()) {
2546
2595
  console.error("Central DB not available.");
2547
2596
  process.exit(1);
@@ -2592,7 +2641,7 @@ remoteCmd
2592
2641
  .action(async (opts) => {
2593
2642
  let centralDb = null;
2594
2643
  try {
2595
- centralDb = GnosysDB.openCentral();
2644
+ centralDb = GnosysDB.openLocal();
2596
2645
  if (!centralDb.isAvailable()) {
2597
2646
  console.error("Central DB not available.");
2598
2647
  process.exit(1);
@@ -2606,7 +2655,8 @@ remoteCmd
2606
2655
  const sync = new RemoteSync(centralDb, remotePath);
2607
2656
  const result = await sync.push({ strategy: opts.newerWins ? "newer-wins" : "skip-and-flag" });
2608
2657
  sync.closeRemote();
2609
- console.log(`Pushed: ${result.pushed} | Skipped: ${result.skipped} | Conflicts: ${result.conflicts.length}`);
2658
+ const projParts = (result.projectsPushed || 0) > 0 ? ` | Projects pushed: ${result.projectsPushed}` : "";
2659
+ console.log(`Pushed: ${result.pushed} | Skipped: ${result.skipped} | Conflicts: ${result.conflicts.length}${projParts}`);
2610
2660
  if (result.errors.length > 0) {
2611
2661
  console.log("\nErrors:");
2612
2662
  for (const e of result.errors)
@@ -2633,7 +2683,7 @@ remoteCmd
2633
2683
  .action(async (opts) => {
2634
2684
  let centralDb = null;
2635
2685
  try {
2636
- centralDb = GnosysDB.openCentral();
2686
+ centralDb = GnosysDB.openLocal();
2637
2687
  if (!centralDb.isAvailable()) {
2638
2688
  console.error("Central DB not available.");
2639
2689
  process.exit(1);
@@ -2647,7 +2697,8 @@ remoteCmd
2647
2697
  const sync = new RemoteSync(centralDb, remotePath);
2648
2698
  const result = await sync.pull({ strategy: opts.newerWins ? "newer-wins" : "skip-and-flag" });
2649
2699
  sync.closeRemote();
2650
- console.log(`Pulled: ${result.pulled} | Skipped: ${result.skipped} | Conflicts: ${result.conflicts.length}`);
2700
+ const projParts = (result.projectsPulled || 0) > 0 ? ` | Projects pulled: ${result.projectsPulled}` : "";
2701
+ console.log(`Pulled: ${result.pulled} | Skipped: ${result.skipped} | Conflicts: ${result.conflicts.length}${projParts}`);
2651
2702
  if (result.errors.length > 0) {
2652
2703
  console.log("\nErrors:");
2653
2704
  for (const e of result.errors)
@@ -2670,7 +2721,7 @@ remoteCmd
2670
2721
  .action(async (opts) => {
2671
2722
  let centralDb = null;
2672
2723
  try {
2673
- centralDb = GnosysDB.openCentral();
2724
+ centralDb = GnosysDB.openLocal();
2674
2725
  if (!centralDb.isAvailable()) {
2675
2726
  if (!opts.auto)
2676
2727
  console.error("Central DB not available.");
@@ -2690,7 +2741,10 @@ remoteCmd
2690
2741
  });
2691
2742
  sync.closeRemote();
2692
2743
  if (!opts.auto || result.conflicts.length > 0 || result.errors.length > 0) {
2693
- console.log(`Pushed: ${result.pushed} | Pulled: ${result.pulled} | Conflicts: ${result.conflicts.length}`);
2744
+ const pp = result.projectsPushed || 0;
2745
+ const pl = result.projectsPulled || 0;
2746
+ const projParts = (pp + pl) > 0 ? ` | Projects: ↑${pp}/↓${pl}` : "";
2747
+ console.log(`Pushed: ${result.pushed} | Pulled: ${result.pulled} | Conflicts: ${result.conflicts.length}${projParts}`);
2694
2748
  if (result.errors.length > 0) {
2695
2749
  console.log("\nErrors:");
2696
2750
  for (const e of result.errors)
@@ -2717,7 +2771,7 @@ remoteCmd
2717
2771
  .action(async (memoryId, opts) => {
2718
2772
  let centralDb = null;
2719
2773
  try {
2720
- centralDb = GnosysDB.openCentral();
2774
+ centralDb = GnosysDB.openLocal();
2721
2775
  if (!centralDb.isAvailable()) {
2722
2776
  console.error("Central DB not available.");
2723
2777
  process.exit(1);
@@ -2751,39 +2805,10 @@ remoteCmd
2751
2805
  centralDb?.close();
2752
2806
  }
2753
2807
  });
2754
- remoteCmd
2755
- .command("configure")
2756
- .description("Configure or change the remote sync location (interactive wizard)")
2757
- .option("--path <path>", "Set remote path non-interactively (skips wizard)")
2758
- .option("--migrate", "Copy current local DB to remote on first setup (with --path)")
2759
- .action(async (opts) => {
2760
- let centralDb = null;
2761
- try {
2762
- centralDb = GnosysDB.openCentral();
2763
- if (!centralDb.isAvailable()) {
2764
- console.error("Central DB not available.");
2765
- process.exit(1);
2766
- }
2767
- const { runConfigureWizard, configureFromPath } = await import("./lib/remoteWizard.js");
2768
- if (opts.path) {
2769
- // Non-interactive mode
2770
- const ok = await configureFromPath(centralDb, opts.path, { migrate: opts.migrate });
2771
- process.exit(ok ? 0 : 1);
2772
- }
2773
- else {
2774
- // Interactive wizard
2775
- const ok = await runConfigureWizard(centralDb);
2776
- process.exit(ok ? 0 : 1);
2777
- }
2778
- }
2779
- catch (err) {
2780
- console.error(`Error: ${err instanceof Error ? err.message : err}`);
2781
- process.exit(1);
2782
- }
2783
- finally {
2784
- centralDb?.close();
2785
- }
2786
- });
2808
+ // v5.4.2 removal: `gnosys remote configure` was removed in favor of the
2809
+ // canonical `gnosys setup remote` form (which calls the same wizard
2810
+ // helpers from lib/remoteWizard.ts). Sync operations like push/pull/sync
2811
+ // remain under the `remote` parent.
2787
2812
  // ─── gnosys upgrade ─────────────────────────────────────────────────────
2788
2813
  program
2789
2814
  .command("upgrade")
@@ -3275,10 +3300,12 @@ program
3275
3300
  }
3276
3301
  console.log();
3277
3302
  });
3278
- // ─── gnosys dream ────────────────────────────────────────────────────────
3279
- program
3303
+ // ─── gnosys dream (parent command) ───────────────────────────────────────
3304
+ const dreamCmd = program
3280
3305
  .command("dream")
3281
- .description("Run a Dream Mode cycle — idle-time consolidation (decay, summaries, self-critique, relationships)")
3306
+ .description("Dream Mode — idle-time consolidation (run a cycle, view log)");
3307
+ // Bare `gnosys dream` runs a cycle now (preserves v5.4.1 behavior).
3308
+ dreamCmd
3282
3309
  .option("--max-runtime <minutes>", "Max runtime in minutes (default: 30)")
3283
3310
  .option("--no-critique", "Skip self-critique phase")
3284
3311
  .option("--no-summaries", "Skip summary generation")
@@ -3325,6 +3352,73 @@ program
3325
3352
  }
3326
3353
  db.close();
3327
3354
  });
3355
+ // `gnosys dream log` — view recent dream runs from audit_log
3356
+ dreamCmd
3357
+ .command("log")
3358
+ .description("Show recent dream runs from the audit log (default: last 20)")
3359
+ .option("--last <N>", "Number of most recent runs to show", "20")
3360
+ .option("--since <YYYY-MM-DD>", "Only runs since this date")
3361
+ .option("--failures-only", "Only runs with errors or unreachable provider")
3362
+ .option("--json", "Output raw audit rows as JSON")
3363
+ .action(async function (opts) {
3364
+ let centralDb = null;
3365
+ try {
3366
+ centralDb = GnosysDB.openCentral();
3367
+ if (!centralDb.isAvailable()) {
3368
+ console.error("Central DB not available.");
3369
+ process.exit(1);
3370
+ }
3371
+ const limit = Math.max(1, parseInt(opts.last) || 20);
3372
+ const sinceIso = opts.since ? `${opts.since}T00:00:00Z` : undefined;
3373
+ const runs = centralDb.getRecentDreamRuns(limit, {
3374
+ failuresOnly: !!opts.failuresOnly,
3375
+ sinceIso,
3376
+ });
3377
+ // Commander v13 hoists `--json` to the parent when both parent and
3378
+ // subcommand define it. Check the parent (dreamCmd) too so users can
3379
+ // type `gnosys dream log --json` and get JSON output.
3380
+ const wantJson = !!opts.json || !!(this.parent?.opts().json);
3381
+ // JSON path always emits a structured response — including empty runs.
3382
+ if (wantJson) {
3383
+ console.log(JSON.stringify({ count: runs.length, runs }, null, 2));
3384
+ return;
3385
+ }
3386
+ if (runs.length === 0) {
3387
+ console.log("No dream runs recorded.");
3388
+ return;
3389
+ }
3390
+ const DIM = "\x1b[2m";
3391
+ const RESET = "\x1b[0m";
3392
+ const RED = "\x1b[31m";
3393
+ const GREEN = "\x1b[32m";
3394
+ console.log(`${runs.length} dream run(s):\n`);
3395
+ for (const r of runs) {
3396
+ const d = r.details;
3397
+ const dur = r.durationMs != null ? `${(r.durationMs / 1000).toFixed(1)}s` : "—";
3398
+ const summaries = Number(d.summariesGenerated || 0);
3399
+ const decays = Number(d.decayUpdated || 0);
3400
+ const reviews = Number(d.reviewSuggestions || 0);
3401
+ const rels = Number(d.relationshipsDiscovered || 0);
3402
+ const errors = Number(d.errors || 0);
3403
+ const unreachable = Boolean(d.providerUnreachable);
3404
+ const status = unreachable
3405
+ ? `${RED}provider unreachable${RESET}`
3406
+ : errors > 0
3407
+ ? `${RED}${errors} error(s)${RESET}`
3408
+ : summaries + decays + rels > 0
3409
+ ? `${GREEN}did work${RESET}`
3410
+ : `${DIM}no LLM work${RESET}`;
3411
+ console.log(` ${r.completed} ${DIM}(${dur})${RESET} ${status}`);
3412
+ console.log(` decays=${decays} summaries=${summaries} reviews=${reviews} relations=${rels}`);
3413
+ if (d.provider) {
3414
+ console.log(` ${DIM}provider=${d.provider}${d.model ? "/" + d.model : ""}${RESET}`);
3415
+ }
3416
+ }
3417
+ }
3418
+ finally {
3419
+ centralDb?.close();
3420
+ }
3421
+ });
3328
3422
  // ─── gnosys export ───────────────────────────────────────────────────────
3329
3423
  program
3330
3424
  .command("export")