gnosys 5.4.3 → 5.6.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 (70) hide show
  1. package/README.md +52 -8
  2. package/dist/cli.js +246 -27
  3. package/dist/cli.js.map +1 -1
  4. package/dist/lib/audioExtract.d.ts +1 -1
  5. package/dist/lib/audioExtract.js +6 -6
  6. package/dist/lib/audioExtract.js.map +1 -1
  7. package/dist/lib/chat/choose.d.ts +75 -0
  8. package/dist/lib/chat/choose.d.ts.map +1 -0
  9. package/dist/lib/chat/choose.js +146 -0
  10. package/dist/lib/chat/choose.js.map +1 -0
  11. package/dist/lib/chat/commands.d.ts +96 -0
  12. package/dist/lib/chat/commands.d.ts.map +1 -0
  13. package/dist/lib/chat/commands.js +367 -0
  14. package/dist/lib/chat/commands.js.map +1 -0
  15. package/dist/lib/chat/focus.d.ts +70 -0
  16. package/dist/lib/chat/focus.d.ts.map +1 -0
  17. package/dist/lib/chat/focus.js +120 -0
  18. package/dist/lib/chat/focus.js.map +1 -0
  19. package/dist/lib/chat/index.d.ts +32 -0
  20. package/dist/lib/chat/index.d.ts.map +1 -0
  21. package/dist/lib/chat/index.js +151 -0
  22. package/dist/lib/chat/index.js.map +1 -0
  23. package/dist/lib/chat/intent.d.ts +100 -0
  24. package/dist/lib/chat/intent.d.ts.map +1 -0
  25. package/dist/lib/chat/intent.js +192 -0
  26. package/dist/lib/chat/intent.js.map +1 -0
  27. package/dist/lib/chat/llmTurn.d.ts +37 -0
  28. package/dist/lib/chat/llmTurn.d.ts.map +1 -0
  29. package/dist/lib/chat/llmTurn.js +61 -0
  30. package/dist/lib/chat/llmTurn.js.map +1 -0
  31. package/dist/lib/chat/recall.d.ts +58 -0
  32. package/dist/lib/chat/recall.d.ts.map +1 -0
  33. package/dist/lib/chat/recall.js +109 -0
  34. package/dist/lib/chat/recall.js.map +1 -0
  35. package/dist/lib/chat/render.d.ts +30 -0
  36. package/dist/lib/chat/render.d.ts.map +1 -0
  37. package/dist/lib/chat/render.js +737 -0
  38. package/dist/lib/chat/render.js.map +1 -0
  39. package/dist/lib/chat/session.d.ts +121 -0
  40. package/dist/lib/chat/session.d.ts.map +1 -0
  41. package/dist/lib/chat/session.js +148 -0
  42. package/dist/lib/chat/session.js.map +1 -0
  43. package/dist/lib/chat/types.d.ts +42 -0
  44. package/dist/lib/chat/types.d.ts.map +1 -0
  45. package/dist/lib/chat/types.js +6 -0
  46. package/dist/lib/chat/types.js.map +1 -0
  47. package/dist/lib/chat/write.d.ts +66 -0
  48. package/dist/lib/chat/write.d.ts.map +1 -0
  49. package/dist/lib/chat/write.js +203 -0
  50. package/dist/lib/chat/write.js.map +1 -0
  51. package/dist/lib/config.d.ts.map +1 -1
  52. package/dist/lib/config.js +6 -2
  53. package/dist/lib/config.js.map +1 -1
  54. package/dist/lib/db.d.ts +3 -1
  55. package/dist/lib/db.d.ts.map +1 -1
  56. package/dist/lib/db.js +18 -2
  57. package/dist/lib/db.js.map +1 -1
  58. package/dist/lib/embeddings.d.ts +1 -1
  59. package/dist/lib/embeddings.d.ts.map +1 -1
  60. package/dist/lib/embeddings.js +9 -4
  61. package/dist/lib/embeddings.js.map +1 -1
  62. package/dist/lib/exportProject.d.ts +51 -0
  63. package/dist/lib/exportProject.d.ts.map +1 -0
  64. package/dist/lib/exportProject.js +72 -0
  65. package/dist/lib/exportProject.js.map +1 -0
  66. package/dist/lib/importProject.d.ts +35 -0
  67. package/dist/lib/importProject.d.ts.map +1 -0
  68. package/dist/lib/importProject.js +135 -0
  69. package/dist/lib/importProject.js.map +1 -0
  70. package/package.json +8 -2
package/README.md CHANGED
@@ -52,7 +52,7 @@ Gnosys takes a different approach: the central brain is a single SQLite database
52
52
  - **Reflection API** — `gnosys.reflect(outcome)` updates confidence and consolidates memories based on real-world outcomes.
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
- - **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.
55
+ - **Multi-machine sync** — share your `gnosys.db` across machines via NAS or shared drive. Remote NAS is the canonical source of truth; local DB is an offline-resilience cache. Built-in conflict detection with skip-and-flag resolution. Run `gnosys setup remote` to set up.
56
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
 
@@ -96,6 +96,50 @@ The helper auto-starts the sandbox if it's not running. No MCP required.
96
96
 
97
97
  ---
98
98
 
99
+ ## Interactive Chat (TUI)
100
+
101
+ `gnosys chat` opens a memory-aware terminal chat. Every prompt triggers federated recall against the central brain; the LLM sees relevant memories in context and cites them in its answers.
102
+
103
+ ```bash
104
+ gnosys chat # new session
105
+ gnosys chat --resume <id> # continue an earlier session
106
+ gnosys chat --list # see all sessions
107
+ gnosys chat --search <query> # full-text search across session logs
108
+ ```
109
+
110
+ **Free-text or slash commands.** "remember that flag default is OFF" works the same as `/remember flag default is OFF`. The TUI also recognizes "what did we decide about ULIDs?" → `/recall`, "thanks, that's all" → `/quit`. Destructive intents always confirm; non-destructive ones auto-accept after 5 confirmations of the same pattern.
111
+
112
+ **24 slash commands** across reading, recall, writing, focus, and polish — type `/help` inside the TUI for the full list. Highlights:
113
+
114
+ - `/pin <id>`, `/scope`, `/threshold`, `/recall <q>` — tune what shows up in context
115
+ - `/remember <text>`, `/save-turn`, `/attach <file>` — promote chat content to gnosys memory (PDFs, images, audio all auto-pin to the session)
116
+ - `/focus <topic>`, `/branch`, `/resume-focus` — replace the "new chat" model with cheap focus boundaries; one continuous session log, instant pivot
117
+ - `/export <file.md>`, `/search-chats <q>`, `/dream-here` — round-trip the session, find old chats, or trigger a focused dream cycle
118
+
119
+ **Multiple choice.** When the model needs you to pick from a small set, it emits a fenced `gnosys-choose` block. The TUI parses it and shows an arrow-key selectable list — provider-agnostic, no tool-use API required.
120
+
121
+ Sessions live as append-only JSONL at `~/.gnosys/chat-sessions/`; promoted memories carry `session:<id>`, `from-chat:true`, and `source:remember|save-turn|auto|attach` provenance tags so you can find them later via federated search.
122
+
123
+ ---
124
+
125
+ ## Per-Project Bundles
126
+
127
+ Move a single project's memories between machines without dragging the whole central DB.
128
+
129
+ ```bash
130
+ gnosys export project --to ./gnosys-public.json.gz # auto-detects current project
131
+ gnosys export project <projectId> --to <bundle> # explicit
132
+ gnosys import project <bundle> --strategy merge # default — skip existing
133
+ gnosys import project <bundle> --strategy replace # wipe target project first
134
+ gnosys import project <bundle> --strategy new-id # remap to a fresh project ID
135
+ ```
136
+
137
+ Bundles are gzipped JSON containing the project row, memories (with embeddings inline), relationships, and audit log. Lossless round-trip with the same DB schema; partially compatible across versions via the bundle manifest.
138
+
139
+ For an Obsidian-compatible markdown vault, use `gnosys export vault --to <dir>` (the v5.5.x form `gnosys export --to <dir>` keeps working).
140
+
141
+ ---
142
+
99
143
  ## Web Knowledge Base
100
144
 
101
145
  Turn any website into a searchable knowledge base for AI chatbots. No database required. Works on Vercel, Netlify, Cloudflare Pages, or any platform that can serve files.
@@ -461,19 +505,19 @@ All memories live in a single `~/.gnosys/gnosys.db` with `project_id` and `scope
461
505
 
462
506
  ### Multi-Machine Sync
463
507
 
464
- Gnosys v5.3.0 supports running across multiple machines with a shared database on a NAS or network share.
508
+ Gnosys supports running across multiple machines with a shared database on a NAS or network share.
465
509
 
466
510
  **How it works:**
467
- - Local DB at `~/.gnosys/gnosys.db` is your fast working cache
468
511
  - Remote DB on a network share (e.g. `/Volumes/nas/gnosys/`) is the canonical source of truth
469
- - Reads always hit local for speed
470
- - Writes go to local first, then sync to remote
471
- - Per-memory `modified` timestamps detect conflicts
512
+ - Local DB at `~/.gnosys/gnosys.db` is an offline-resilience cache, not a performance optimization
513
+ - Reads hit remote when reachable; fall back to local cache when remote is offline
514
+ - Writes go to remote first; queue locally and auto-flush when offline
515
+ - Per-memory `modified` timestamps detect conflicts; ULID memory IDs prevent collisions across concurrent writers
472
516
  - Skip-and-flag is the safe default; `--newer-wins` for unattended sync
473
517
 
474
518
  **Setup:**
475
519
  ```bash
476
- gnosys remote configure
520
+ gnosys setup remote
477
521
  # interactive: validates path, tests SQLite locking, checks latency
478
522
  ```
479
523
 
@@ -616,7 +660,7 @@ All commands support `--json` for programmatic output. See the [User Guide](http
616
660
 
617
661
  **Web knowledge base:** `web init`, `web ingest`, `web build-index`, `web build`, `web add`, `web remove`, `web status`
618
662
 
619
- **Multi-machine sync:** `remote configure`, `remote status`, `remote sync`, `remote push`, `remote pull`, `remote resolve`
663
+ **Multi-machine sync:** `remote status`, `remote sync`, `remote push`, `remote pull`, `remote resolve` (configure via `gnosys setup remote`)
620
664
 
621
665
  **Server:** `serve`, `serve --with-maintenance`
622
666
 
package/dist/cli.js CHANGED
@@ -108,6 +108,32 @@ function maybePrintUpgradeNudge() {
108
108
  }
109
109
  }
110
110
  maybePrintUpgradeNudge();
111
+ /**
112
+ * v5.6.0 back-compat shim: rewrite `gnosys export --to <dir>` →
113
+ * `gnosys export vault --to <dir>` before commander parses argv. The v5.6.0
114
+ * restructure made `export` a parent command with `vault` and `project`
115
+ * subcommands; without this shim, the bare `--to` form prints usage instead
116
+ * of running the vault export.
117
+ *
118
+ * Pattern: argv[2]==="export" AND argv[3] is not a known subcommand AND any
119
+ * of the v5.5.x flags appear (`--to`, `--all`, `--overwrite`, etc.).
120
+ */
121
+ function rewriteLegacyExport() {
122
+ if (process.argv[2] !== "export")
123
+ return;
124
+ const next = process.argv[3];
125
+ if (next === "vault" || next === "project" || next === "--help" || next === "-h")
126
+ return;
127
+ // Any v5.5.x-style flag → assume legacy vault invocation
128
+ const looksLegacy = process.argv.slice(3).some((a) => a === "--to" || a.startsWith("--to=") ||
129
+ a === "--all" || a === "--overwrite" ||
130
+ a === "--no-summaries" || a === "--no-reviews" || a === "--no-graph" ||
131
+ a === "--json");
132
+ if (looksLegacy) {
133
+ process.argv.splice(3, 0, "vault");
134
+ }
135
+ }
136
+ rewriteLegacyExport();
111
137
  async function getResolver() {
112
138
  const resolver = new GnosysResolver();
113
139
  await resolver.resolve();
@@ -1218,6 +1244,45 @@ program
1218
1244
  centralDb?.close();
1219
1245
  }
1220
1246
  });
1247
+ // ─── gnosys chat (TUI) ───────────────────────────────────────────────────
1248
+ program
1249
+ .command("chat")
1250
+ .description("Interactive memory-aware terminal chat (TUI)")
1251
+ .option("--resume <sessionId>", "Resume an existing chat session")
1252
+ .option("--list", "List recent chat sessions and exit")
1253
+ .option("--search <query>", "Full-text search across session logs")
1254
+ .option("--provider <name>", "Override LLM provider (anthropic, openai, groq, ollama, …)")
1255
+ .option("--model <name>", "Override LLM model name")
1256
+ .option("--limit <n>", "Limit for --list / --search (default 20)", "20")
1257
+ .action(async (opts) => {
1258
+ const limit = parseInt(opts.limit, 10) || 20;
1259
+ const chat = await import("./lib/chat/index.js");
1260
+ if (opts.list) {
1261
+ chat.printSessionList(limit);
1262
+ return;
1263
+ }
1264
+ if (opts.search) {
1265
+ chat.printSearchResults(opts.search, limit);
1266
+ return;
1267
+ }
1268
+ // Interactive chat
1269
+ const resolver = await getResolver();
1270
+ const stores = resolver.getStores();
1271
+ const storePath = stores[0]?.path ?? process.cwd();
1272
+ let cliConfig;
1273
+ try {
1274
+ cliConfig = await loadConfig(storePath);
1275
+ }
1276
+ catch {
1277
+ cliConfig = (await import("./lib/config.js")).DEFAULT_CONFIG;
1278
+ }
1279
+ await chat.startChat({
1280
+ config: cliConfig,
1281
+ resume: opts.resume,
1282
+ providerName: opts.provider,
1283
+ modelName: opts.model,
1284
+ });
1285
+ });
1221
1286
  // ─── gnosys ingest <file> ─────────────────────────────────────────────────
1222
1287
  program
1223
1288
  .command("ingest <fileOrGlob>")
@@ -1876,12 +1941,13 @@ program
1876
1941
  }
1877
1942
  }
1878
1943
  });
1879
- // ─── gnosys import <file> ────────────────────────────────────────────────
1880
- program
1881
- .command("import <fileOrUrl>")
1882
- .description("Bulk import structured data (CSV, JSON, JSONL) into Gnosys memories")
1883
- .requiredOption("--format <format>", "Data format: csv, json, jsonl")
1884
- .requiredOption("--mapping <json>", 'Field mapping as JSON: \'{"source_field":"gnosys_field"}\'. Valid targets: title, category, content, tags, relevance')
1944
+ // ─── gnosys import (parent + subcommands) ───────────────────────────────
1945
+ const importCmd = program
1946
+ .command("import [fileOrUrl]")
1947
+ .enablePositionalOptions()
1948
+ .description("Import data into Gnosys (bulk CSV/JSON/JSONL — see also: 'gnosys import project <bundle>')")
1949
+ .option("--format <format>", "Data format: csv, json, jsonl (required for bulk import)")
1950
+ .option("--mapping <json>", 'Field mapping as JSON: \'{"source_field":"gnosys_field"}\'. Valid targets: title, category, content, tags, relevance')
1885
1951
  .option("--mode <mode>", "Processing mode: llm or structured", "structured")
1886
1952
  .option("--limit <n>", "Max records to import", parseInt)
1887
1953
  .option("--offset <n>", "Skip first N records", parseInt)
@@ -1892,6 +1958,17 @@ program
1892
1958
  .option("--dry-run", "Preview without writing")
1893
1959
  .option("--store <store>", "Target store: project, personal, global", "project")
1894
1960
  .action(async (fileOrUrl, opts) => {
1961
+ if (!fileOrUrl) {
1962
+ console.error("Usage:");
1963
+ console.error(" gnosys import <file> --format csv|json|jsonl --mapping '{...}' (bulk)");
1964
+ console.error(" gnosys import project <bundle.json.gz> (project bundle)");
1965
+ process.exit(1);
1966
+ }
1967
+ if (!opts.format || !opts.mapping) {
1968
+ console.error("Error: --format and --mapping are required for bulk imports.");
1969
+ console.error(" For project bundles, use 'gnosys import project <bundle>'.");
1970
+ process.exit(1);
1971
+ }
1895
1972
  // Parse mapping JSON
1896
1973
  let mapping;
1897
1974
  try {
@@ -1966,6 +2043,51 @@ program
1966
2043
  process.exit(1);
1967
2044
  }
1968
2045
  });
2046
+ // `gnosys import project <bundle>` — restore a portable .json.gz bundle
2047
+ importCmd
2048
+ .command("project <bundlePath>")
2049
+ .description("Import a project bundle (.json.gz) created by 'gnosys export project'")
2050
+ .option("--strategy <strategy>", "Conflict handling: merge (default), replace, new-id", "merge")
2051
+ .option("--working-directory <dir>", "Override the bundle's working_directory (e.g. when restoring on a different machine)")
2052
+ .option("--json", "Output the result as JSON")
2053
+ .action(async (bundlePath, opts) => {
2054
+ const validStrategies = ["merge", "replace", "new-id"];
2055
+ if (!validStrategies.includes(opts.strategy)) {
2056
+ console.error(`Invalid strategy: ${opts.strategy}. Use one of: ${validStrategies.join(", ")}`);
2057
+ process.exit(1);
2058
+ }
2059
+ const { GnosysDB: DbClass } = await import("./lib/db.js");
2060
+ const { importProject } = await import("./lib/importProject.js");
2061
+ const centralDb = DbClass.openCentral();
2062
+ if (!centralDb.isAvailable()) {
2063
+ console.error("Central DB unavailable.");
2064
+ process.exit(1);
2065
+ }
2066
+ try {
2067
+ const result = importProject(centralDb, {
2068
+ bundlePath: path.resolve(bundlePath),
2069
+ strategy: opts.strategy,
2070
+ workingDirectoryOverride: opts.workingDirectory,
2071
+ });
2072
+ if (opts.json) {
2073
+ console.log(JSON.stringify(result, null, 2));
2074
+ }
2075
+ else {
2076
+ console.log(`Imported project ${result.projectName} (${result.projectId})`);
2077
+ console.log(` Strategy: ${result.strategy}`);
2078
+ console.log(` Memories: ${result.memoriesInserted} inserted, ${result.memoriesSkipped} skipped, ${result.memoriesReplaced} replaced`);
2079
+ console.log(` Relationships: ${result.relationshipsInserted}`);
2080
+ console.log(` Audit entries: ${result.auditEntriesInserted}`);
2081
+ }
2082
+ }
2083
+ catch (err) {
2084
+ console.error(`Import failed: ${err instanceof Error ? err.message : String(err)}`);
2085
+ process.exit(1);
2086
+ }
2087
+ finally {
2088
+ centralDb.close();
2089
+ }
2090
+ });
1969
2091
  // ─── gnosys reindex ──────────────────────────────────────────────────────
1970
2092
  program
1971
2093
  .command("reindex")
@@ -3347,14 +3469,7 @@ program
3347
3469
  const dreamCmd = program
3348
3470
  .command("dream")
3349
3471
  .description("Dream Mode — idle-time consolidation (run a cycle, view log)");
3350
- // Bare `gnosys dream` runs a cycle now (preserves v5.4.1 behavior).
3351
- dreamCmd
3352
- .option("--max-runtime <minutes>", "Max runtime in minutes (default: 30)")
3353
- .option("--no-critique", "Skip self-critique phase")
3354
- .option("--no-summaries", "Skip summary generation")
3355
- .option("--no-relationships", "Skip relationship discovery")
3356
- .option("--json", "Output raw JSON report")
3357
- .action(async (opts) => {
3472
+ async function runDreamCycle(opts) {
3358
3473
  const resolver = new GnosysResolver();
3359
3474
  await resolver.resolve();
3360
3475
  const stores = resolver.getStores();
@@ -3364,6 +3479,7 @@ dreamCmd
3364
3479
  }
3365
3480
  const { GnosysDB: DbClass } = await import("./lib/db.js");
3366
3481
  const { GnosysDreamEngine, formatDreamReport } = await import("./lib/dream.js");
3482
+ const { getMachineId } = await import("./lib/remote.js");
3367
3483
  const storePath = stores[0].path;
3368
3484
  const cfg = await loadConfig(storePath);
3369
3485
  const db = new DbClass(storePath);
@@ -3371,6 +3487,24 @@ dreamCmd
3371
3487
  console.error("Dream Mode requires gnosys.db (v2.0). Run 'gnosys migrate' first.");
3372
3488
  process.exit(1);
3373
3489
  }
3490
+ // Designation gate — warn (and exit unless --force) if this isn't the
3491
+ // designated dream machine. Manual runs from non-designated machines are
3492
+ // useful for testing but shouldn't happen by accident on shared brains.
3493
+ const centralDb = GnosysDB.openCentral();
3494
+ if (centralDb.isAvailable()) {
3495
+ const designated = centralDb.getDreamMachineId();
3496
+ if (designated) {
3497
+ const localId = getMachineId(centralDb);
3498
+ if (designated !== localId && !opts.force) {
3499
+ console.error(`Dream is designated to machine ${designated}, but this is ${localId}.\n` +
3500
+ `Pass --force to run anyway, or run 'gnosys setup dream' to redesignate.`);
3501
+ centralDb.close();
3502
+ db.close();
3503
+ process.exit(1);
3504
+ }
3505
+ }
3506
+ centralDb.close();
3507
+ }
3374
3508
  const dreamConfig = {
3375
3509
  enabled: true,
3376
3510
  idleMinutes: 0,
@@ -3394,7 +3528,28 @@ dreamCmd
3394
3528
  console.log(formatDreamReport(report));
3395
3529
  }
3396
3530
  db.close();
3397
- });
3531
+ }
3532
+ // Bare `gnosys dream` runs a cycle (preserves v5.4.1 behavior).
3533
+ dreamCmd
3534
+ .option("--max-runtime <minutes>", "Max runtime in minutes (default: 30)")
3535
+ .option("--no-critique", "Skip self-critique phase")
3536
+ .option("--no-summaries", "Skip summary generation")
3537
+ .option("--no-relationships", "Skip relationship discovery")
3538
+ .option("--force", "Run even if this machine is not the designated dream node")
3539
+ .option("--json", "Output raw JSON report")
3540
+ .action(runDreamCycle);
3541
+ // `gnosys dream run` — explicit alias matching the `gnosys dream log|run`
3542
+ // pattern. Same options + behavior as the bare command.
3543
+ dreamCmd
3544
+ .command("run")
3545
+ .description("Force a dream cycle now (manual trigger)")
3546
+ .option("--max-runtime <minutes>", "Max runtime in minutes (default: 30)")
3547
+ .option("--no-critique", "Skip self-critique phase")
3548
+ .option("--no-summaries", "Skip summary generation")
3549
+ .option("--no-relationships", "Skip relationship discovery")
3550
+ .option("--force", "Run even if this machine is not the designated dream node")
3551
+ .option("--json", "Output raw JSON report")
3552
+ .action(runDreamCycle);
3398
3553
  // `gnosys dream log` — view recent dream runs from audit_log
3399
3554
  dreamCmd
3400
3555
  .command("log")
@@ -3462,18 +3617,7 @@ dreamCmd
3462
3617
  centralDb?.close();
3463
3618
  }
3464
3619
  });
3465
- // ─── gnosys export ───────────────────────────────────────────────────────
3466
- program
3467
- .command("export")
3468
- .description("Export gnosys.db to Obsidian-compatible vault (one-way)")
3469
- .requiredOption("--to <dir>", "Target directory for export")
3470
- .option("--all", "Export all memories (active + archived)")
3471
- .option("--overwrite", "Overwrite existing files")
3472
- .option("--no-summaries", "Skip category summaries")
3473
- .option("--no-reviews", "Skip review suggestions")
3474
- .option("--no-graph", "Skip relationship graph")
3475
- .option("--json", "Output raw JSON report")
3476
- .action(async (opts) => {
3620
+ async function runVaultExport(opts) {
3477
3621
  const resolver = new GnosysResolver();
3478
3622
  await resolver.resolve();
3479
3623
  const stores = resolver.getStores();
@@ -3512,6 +3656,81 @@ program
3512
3656
  console.log(formatExportReport(report));
3513
3657
  }
3514
3658
  db.close();
3659
+ }
3660
+ const exportCmd = program
3661
+ .command("export")
3662
+ .description("Export memory to a vault (markdown) or a project bundle (.json.gz)")
3663
+ .enablePositionalOptions();
3664
+ // Bare `gnosys export` shows the canonical subcommand forms. Back-compat for
3665
+ // the v5.5.x form `gnosys export --to <dir>` is handled in a pre-parse shim
3666
+ // at the top of the file (rewrites argv to insert "vault" before "--to").
3667
+ exportCmd.action(() => {
3668
+ console.error("Usage: gnosys export vault --to <dir> # Obsidian vault export");
3669
+ console.error(" gnosys export project [id] --to <bundle> # portable .json.gz bundle");
3670
+ process.exit(1);
3671
+ });
3672
+ // `gnosys export vault` — explicit alias for the default behavior
3673
+ exportCmd
3674
+ .command("vault")
3675
+ .description("Export gnosys.db to an Obsidian-compatible vault (one-way)")
3676
+ .requiredOption("--to <dir>", "Target directory for export")
3677
+ .option("--all", "Export all memories (active + archived)")
3678
+ .option("--overwrite", "Overwrite existing files")
3679
+ .option("--no-summaries", "Skip category summaries")
3680
+ .option("--no-reviews", "Skip review suggestions")
3681
+ .option("--no-graph", "Skip relationship graph")
3682
+ .option("--json", "Output raw JSON report")
3683
+ .action(runVaultExport);
3684
+ // `gnosys export project [id]` — bundle a single project for portability
3685
+ exportCmd
3686
+ .command("project [projectId]")
3687
+ .description("Export a single project to a portable .json.gz bundle (round-trips with 'gnosys import project')")
3688
+ .requiredOption("--to <file>", "Output bundle file path (e.g. ./gnosys-public.gnosys.json.gz)")
3689
+ .option("--include-archived", "Include archived and superseded memories (default: active only)")
3690
+ .option("--no-audit", "Skip the audit log")
3691
+ .option("--json", "Output the result as JSON")
3692
+ .action(async (projectIdArg, opts) => {
3693
+ const { GnosysDB: DbClass } = await import("./lib/db.js");
3694
+ const { exportProject } = await import("./lib/exportProject.js");
3695
+ const centralDb = DbClass.openCentral();
3696
+ if (!centralDb.isAvailable()) {
3697
+ console.error("Central DB unavailable.");
3698
+ process.exit(1);
3699
+ }
3700
+ let projectId = projectIdArg;
3701
+ if (!projectId) {
3702
+ // Auto-detect from cwd
3703
+ const proj = centralDb.getProjectByDirectory(process.cwd());
3704
+ if (!proj) {
3705
+ console.error("No project ID given and current directory is not a registered project.");
3706
+ console.error("Usage: gnosys export project <projectId> --to <file>");
3707
+ process.exit(1);
3708
+ }
3709
+ projectId = proj.id;
3710
+ }
3711
+ try {
3712
+ const result = exportProject(centralDb, {
3713
+ projectId,
3714
+ outputPath: path.resolve(opts.to),
3715
+ includeArchived: !!opts.includeArchived,
3716
+ includeAudit: opts.audit !== false,
3717
+ });
3718
+ if (opts.json) {
3719
+ console.log(JSON.stringify(result, null, 2));
3720
+ }
3721
+ else {
3722
+ const ratio = (result.compressedBytes / result.uncompressedBytes * 100).toFixed(1);
3723
+ console.log(`Exported project ${projectId}`);
3724
+ console.log(` Memories: ${result.memoryCount}`);
3725
+ console.log(` Relationships: ${result.relationshipCount}`);
3726
+ console.log(` Audit entries: ${result.auditEntryCount}`);
3727
+ console.log(` Bundle: ${result.outputPath}`);
3728
+ console.log(` Size: ${(result.compressedBytes / 1024).toFixed(1)} KB compressed (${ratio}% of ${(result.uncompressedBytes / 1024).toFixed(1)} KB)`);
3729
+ }
3730
+ }
3731
+ finally {
3732
+ centralDb.close();
3733
+ }
3515
3734
  });
3516
3735
  // ─── gnosys serve ────────────────────────────────────────────────────────
3517
3736
  program