gnosys-mcp 2.3.0 → 2.5.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/dist/cli.js CHANGED
@@ -8,6 +8,7 @@ import path from "path";
8
8
  import fs from "fs/promises";
9
9
  import { fileURLToPath } from "url";
10
10
  import dotenv from "dotenv";
11
+ import { readFileSync } from "fs";
11
12
  import { GnosysResolver } from "./lib/resolver.js";
12
13
  import { GnosysSearch } from "./lib/search.js";
13
14
  import { GnosysTagRegistry } from "./lib/tags.js";
@@ -25,14 +26,37 @@ import { GnosysAsk } from "./lib/ask.js";
25
26
  import { getLLMProvider, isProviderAvailable } from "./lib/llm.js";
26
27
  import { GnosysDB } from "./lib/db.js";
27
28
  import { migrate, formatMigrationReport } from "./lib/migrate.js";
28
- import { createProjectIdentity, readProjectIdentity } from "./lib/projectIdentity.js";
29
+ import { createProjectIdentity, readProjectIdentity, findProjectIdentity } from "./lib/projectIdentity.js";
29
30
  import { setPreference, getPreference, getAllPreferences, deletePreference } from "./lib/preferences.js";
30
31
  import { syncRules } from "./lib/rulesGen.js";
31
32
  // Load API keys from ~/.config/gnosys/.env (same as MCP server)
33
+ // IMPORTANT: We use dotenv.parse() instead of dotenv.config() because
34
+ // dotenv v17+ writes injection notices to stdout, which corrupts
35
+ // --json output and piped usage. parse() is a pure function with no side effects.
32
36
  const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
33
- dotenv.config({ path: path.join(home, ".config", "gnosys", ".env") });
34
- // Also load .env from current directory as fallback
35
- dotenv.config();
37
+ try {
38
+ const envFile = readFileSync(path.join(home, ".config", "gnosys", ".env"), "utf8");
39
+ const parsed = dotenv.parse(envFile);
40
+ for (const [key, val] of Object.entries(parsed)) {
41
+ if (!(key in process.env))
42
+ process.env[key] = val;
43
+ }
44
+ }
45
+ catch {
46
+ // .env file not found — that's fine, env vars may be set elsewhere
47
+ }
48
+ // Also try .env from current directory as fallback
49
+ try {
50
+ const localEnv = readFileSync(".env", "utf8");
51
+ const localParsed = dotenv.parse(localEnv);
52
+ for (const [key, val] of Object.entries(localParsed)) {
53
+ if (!(key in process.env))
54
+ process.env[key] = val;
55
+ }
56
+ }
57
+ catch {
58
+ // No local .env — fine
59
+ }
36
60
  // Read version from package.json at build time
37
61
  const __filename = fileURLToPath(import.meta.url);
38
62
  const __dirname = path.dirname(__filename);
@@ -44,6 +68,26 @@ async function getResolver() {
44
68
  await resolver.resolve();
45
69
  return resolver;
46
70
  }
71
+ /**
72
+ * v3.0: Resolve projectId from nearest .gnosys/gnosys.json.
73
+ * Used by CLI write commands to tag memories with the correct project.
74
+ */
75
+ async function resolveProjectId(dir) {
76
+ const result = await findProjectIdentity(dir || process.cwd());
77
+ return result?.identity.projectId || null;
78
+ }
79
+ /**
80
+ * Output helper: if --json flag is set, output JSON; otherwise call the
81
+ * human-readable formatter function.
82
+ */
83
+ function outputResult(json, data, humanFn) {
84
+ if (json) {
85
+ console.log(JSON.stringify(data, null, 2));
86
+ }
87
+ else {
88
+ humanFn();
89
+ }
90
+ }
47
91
  program
48
92
  .name("gnosys")
49
93
  .description("Gnosys — Agent-first persistent memory system (SQLite core + Dream Mode + Obsidian export)")
@@ -52,7 +96,8 @@ program
52
96
  program
53
97
  .command("read <memoryPath>")
54
98
  .description("Read a specific memory. Supports layer prefix (e.g., project:decisions/auth.md)")
55
- .action(async (memoryPath) => {
99
+ .option("--json", "Output as JSON")
100
+ .action(async (memoryPath, opts) => {
56
101
  const resolver = await getResolver();
57
102
  const memory = await resolver.readMemory(memoryPath);
58
103
  if (!memory) {
@@ -60,14 +105,17 @@ program
60
105
  process.exit(1);
61
106
  }
62
107
  const raw = await fs.readFile(memory.filePath, "utf-8");
63
- console.log(`[Source: ${memory.sourceLabel}]\n`);
64
- console.log(raw);
108
+ outputResult(!!opts.json, { path: memoryPath, source: memory.sourceLabel, content: raw }, () => {
109
+ console.log(`[Source: ${memory.sourceLabel}]\n`);
110
+ console.log(raw);
111
+ });
65
112
  });
66
113
  // ─── gnosys discover <query> ─────────────────────────────────────────────
67
114
  program
68
115
  .command("discover <query>")
69
116
  .description("Discover relevant memories by keyword. Searches relevance clouds, titles, and tags — returns metadata only, no content.")
70
117
  .option("-n, --limit <number>", "Max results", "20")
118
+ .option("--json", "Output as JSON")
71
119
  .action(async (query, opts) => {
72
120
  const resolver = await getResolver();
73
121
  const stores = resolver.getStores();
@@ -82,18 +130,22 @@ program
82
130
  }
83
131
  const results = search.discover(query, parseInt(opts.limit));
84
132
  if (results.length === 0) {
85
- console.log(`No memories found for "${query}". Try gnosys search for full-text.`);
133
+ outputResult(!!opts.json, { query, results: [] }, () => {
134
+ console.log(`No memories found for "${query}". Try gnosys search for full-text.`);
135
+ });
86
136
  search.close();
87
137
  return;
88
138
  }
89
- console.log(`Found ${results.length} relevant memories for "${query}":\n`);
90
- for (const r of results) {
91
- console.log(` ${r.title}`);
92
- console.log(` ${r.relative_path}`);
93
- if (r.relevance)
94
- console.log(` Relevance: ${r.relevance}`);
95
- console.log();
96
- }
139
+ outputResult(!!opts.json, { query, count: results.length, results }, () => {
140
+ console.log(`Found ${results.length} relevant memories for "${query}":\n`);
141
+ for (const r of results) {
142
+ console.log(` ${r.title}`);
143
+ console.log(` ${r.relative_path}`);
144
+ if (r.relevance)
145
+ console.log(` Relevance: ${r.relevance}`);
146
+ console.log();
147
+ }
148
+ });
97
149
  search.close();
98
150
  });
99
151
  // ─── gnosys search <query> ───────────────────────────────────────────────
@@ -101,6 +153,7 @@ program
101
153
  .command("search <query>")
102
154
  .description("Search memories by keyword across all stores")
103
155
  .option("-n, --limit <number>", "Max results", "20")
156
+ .option("--json", "Output as JSON")
104
157
  .action(async (query, opts) => {
105
158
  const resolver = await getResolver();
106
159
  const stores = resolver.getStores();
@@ -115,17 +168,21 @@ program
115
168
  }
116
169
  const results = search.search(query, parseInt(opts.limit));
117
170
  if (results.length === 0) {
118
- console.log(`No results for "${query}".`);
171
+ outputResult(!!opts.json, { query, results: [] }, () => {
172
+ console.log(`No results for "${query}".`);
173
+ });
119
174
  search.close();
120
175
  return;
121
176
  }
122
- console.log(`Found ${results.length} results for "${query}":\n`);
123
- for (const r of results) {
124
- console.log(` ${r.title}`);
125
- console.log(` ${r.relative_path}`);
126
- console.log(` ${r.snippet.replace(/>>>/g, "").replace(/<<</g, "")}`);
127
- console.log();
128
- }
177
+ outputResult(!!opts.json, { query, count: results.length, results }, () => {
178
+ console.log(`Found ${results.length} results for "${query}":\n`);
179
+ for (const r of results) {
180
+ console.log(` ${r.title}`);
181
+ console.log(` ${r.relative_path}`);
182
+ console.log(` ${r.snippet.replace(/>>>/g, "").replace(/<<</g, "")}`);
183
+ console.log();
184
+ }
185
+ });
129
186
  search.close();
130
187
  });
131
188
  // ─── gnosys list ─────────────────────────────────────────────────────────
@@ -135,6 +192,7 @@ program
135
192
  .option("-c, --category <category>", "Filter by category")
136
193
  .option("-t, --tag <tag>", "Filter by tag")
137
194
  .option("-s, --store <store>", "Filter by store layer")
195
+ .option("--json", "Output as JSON")
138
196
  .action(async (opts) => {
139
197
  const resolver = await getResolver();
140
198
  let memories = await resolver.getAllMemories();
@@ -152,12 +210,24 @@ program
152
210
  return tags.includes(opts.tag);
153
211
  });
154
212
  }
155
- console.log(`${memories.length} memories:\n`);
156
- for (const m of memories) {
157
- console.log(` [${m.sourceLabel}] [${m.frontmatter.status}] ${m.frontmatter.title}`);
158
- console.log(` ${m.sourceLabel}:${m.relativePath}`);
159
- console.log();
160
- }
213
+ outputResult(!!opts.json, {
214
+ count: memories.length,
215
+ memories: memories.map((m) => ({
216
+ id: m.frontmatter.id,
217
+ title: m.frontmatter.title,
218
+ category: m.frontmatter.category,
219
+ status: m.frontmatter.status,
220
+ source: m.sourceLabel,
221
+ path: `${m.sourceLabel}:${m.relativePath}`,
222
+ })),
223
+ }, () => {
224
+ console.log(`${memories.length} memories:\n`);
225
+ for (const m of memories) {
226
+ console.log(` [${m.sourceLabel}] [${m.frontmatter.status}] ${m.frontmatter.title}`);
227
+ console.log(` ${m.sourceLabel}:${m.relativePath}`);
228
+ console.log();
229
+ }
230
+ });
161
231
  });
162
232
  // ─── gnosys add <input> ──────────────────────────────────────────────────
163
233
  program
@@ -830,31 +900,36 @@ program
830
900
  program
831
901
  .command("stats")
832
902
  .description("Show summary statistics for the memory store")
833
- .action(async () => {
903
+ .option("--json", "Output as JSON")
904
+ .action(async (opts) => {
834
905
  const resolver = await getResolver();
835
906
  const allMemories = await resolver.getAllMemories();
836
907
  if (allMemories.length === 0) {
837
- console.log("No memories found.");
908
+ outputResult(!!opts.json, { totalCount: 0 }, () => {
909
+ console.log("No memories found.");
910
+ });
838
911
  return;
839
912
  }
840
913
  const stats = computeStats(allMemories);
841
- console.log(`Gnosys Store Statistics:\n`);
842
- console.log(` Total memories: ${stats.totalCount}`);
843
- console.log(` Average confidence: ${stats.averageConfidence}`);
844
- console.log(` Date range: ${stats.oldestCreated} → ${stats.newestCreated}`);
845
- console.log(` Last modified: ${stats.lastModified}`);
846
- console.log(`\n By category:`);
847
- for (const [cat, count] of Object.entries(stats.byCategory).sort((a, b) => b[1] - a[1])) {
848
- console.log(` ${cat}: ${count}`);
849
- }
850
- console.log(`\n By status:`);
851
- for (const [st, count] of Object.entries(stats.byStatus)) {
852
- console.log(` ${st}: ${count}`);
853
- }
854
- console.log(`\n By author:`);
855
- for (const [author, count] of Object.entries(stats.byAuthor)) {
856
- console.log(` ${author}: ${count}`);
857
- }
914
+ outputResult(!!opts.json, stats, () => {
915
+ console.log(`Gnosys Store Statistics:\n`);
916
+ console.log(` Total memories: ${stats.totalCount}`);
917
+ console.log(` Average confidence: ${stats.averageConfidence}`);
918
+ console.log(` Date range: ${stats.oldestCreated} → ${stats.newestCreated}`);
919
+ console.log(` Last modified: ${stats.lastModified}`);
920
+ console.log(`\n By category:`);
921
+ for (const [cat, count] of Object.entries(stats.byCategory).sort((a, b) => b[1] - a[1])) {
922
+ console.log(` ${cat}: ${count}`);
923
+ }
924
+ console.log(`\n By status:`);
925
+ for (const [st, count] of Object.entries(stats.byStatus)) {
926
+ console.log(` ${st}: ${count}`);
927
+ }
928
+ console.log(`\n By author:`);
929
+ for (const [author, count] of Object.entries(stats.byAuthor)) {
930
+ console.log(` ${author}: ${count}`);
931
+ }
932
+ });
858
933
  });
859
934
  // ─── gnosys links <path> ─────────────────────────────────────────────────
860
935
  program
@@ -2128,7 +2203,8 @@ program
2128
2203
  program
2129
2204
  .command("projects")
2130
2205
  .description("List all registered projects in the central DB")
2131
- .action(async () => {
2206
+ .option("--json", "Output as JSON")
2207
+ .action(async (opts) => {
2132
2208
  let centralDb = null;
2133
2209
  try {
2134
2210
  centralDb = GnosysDB.openCentral();
@@ -2142,16 +2218,21 @@ program
2142
2218
  centralDb.close();
2143
2219
  return;
2144
2220
  }
2145
- console.log(`${projects.length} registered project(s):\n`);
2146
- for (const p of projects) {
2147
- const memCount = centralDb.getMemoriesByProject(p.id).length;
2148
- console.log(` ${p.name}`);
2149
- console.log(` ID: ${p.id}`);
2150
- console.log(` Directory: ${p.working_directory}`);
2151
- console.log(` Memories: ${memCount}`);
2152
- console.log(` Created: ${p.created}`);
2153
- console.log();
2154
- }
2221
+ const projectData = projects.map((p) => ({
2222
+ ...p,
2223
+ memoryCount: centralDb.getMemoriesByProject(p.id).length,
2224
+ }));
2225
+ outputResult(!!opts.json, { count: projects.length, projects: projectData }, () => {
2226
+ console.log(`${projects.length} registered project(s):\n`);
2227
+ for (const p of projectData) {
2228
+ console.log(` ${p.name}`);
2229
+ console.log(` ID: ${p.id}`);
2230
+ console.log(` Directory: ${p.working_directory}`);
2231
+ console.log(` Memories: ${p.memoryCount}`);
2232
+ console.log(` Created: ${p.created}`);
2233
+ console.log();
2234
+ }
2235
+ });
2155
2236
  }
2156
2237
  catch (err) {
2157
2238
  console.error(`Error: ${err instanceof Error ? err.message : err}`);
@@ -2196,7 +2277,8 @@ prefCmd
2196
2277
  prefCmd
2197
2278
  .command("get [key]")
2198
2279
  .description("Get a preference by key, or list all preferences if no key given.")
2199
- .action(async (key) => {
2280
+ .option("--json", "Output as JSON")
2281
+ .action(async (key, opts) => {
2200
2282
  let centralDb = null;
2201
2283
  try {
2202
2284
  centralDb = GnosysDB.openCentral();
@@ -2210,23 +2292,29 @@ prefCmd
2210
2292
  console.log(`No preference found for key "${key}".`);
2211
2293
  return;
2212
2294
  }
2213
- console.log(`${pref.title} (${pref.key})\n`);
2214
- console.log(pref.value);
2215
- console.log(`\nConfidence: ${pref.confidence}`);
2216
- console.log(`Modified: ${pref.modified}`);
2295
+ outputResult(!!opts.json, pref, () => {
2296
+ console.log(`${pref.title} (${pref.key})\n`);
2297
+ console.log(pref.value);
2298
+ console.log(`\nConfidence: ${pref.confidence}`);
2299
+ console.log(`Modified: ${pref.modified}`);
2300
+ });
2217
2301
  }
2218
2302
  else {
2219
2303
  const prefs = getAllPreferences(centralDb);
2220
2304
  if (prefs.length === 0) {
2221
- console.log("No preferences set. Use 'gnosys pref set <key> <value>' to add some.");
2305
+ outputResult(!!opts.json, { preferences: [] }, () => {
2306
+ console.log("No preferences set. Use 'gnosys pref set <key> <value>' to add some.");
2307
+ });
2222
2308
  return;
2223
2309
  }
2224
- console.log(`${prefs.length} user preference(s):\n`);
2225
- for (const p of prefs) {
2226
- console.log(` ${p.title} (${p.key})`);
2227
- console.log(` ${p.value.split("\n")[0]}`);
2228
- console.log();
2229
- }
2310
+ outputResult(!!opts.json, { count: prefs.length, preferences: prefs }, () => {
2311
+ console.log(`${prefs.length} user preference(s):\n`);
2312
+ for (const p of prefs) {
2313
+ console.log(` ${p.title} (${p.key})`);
2314
+ console.log(` ${p.value.split("\n")[0]}`);
2315
+ console.log();
2316
+ }
2317
+ });
2230
2318
  }
2231
2319
  }
2232
2320
  catch (err) {
@@ -2309,5 +2397,212 @@ program
2309
2397
  centralDb?.close();
2310
2398
  }
2311
2399
  });
2400
+ // ─── gnosys fsearch (federated search) ───────────────────────────────────
2401
+ program
2402
+ .command("fsearch <query>")
2403
+ .description("Federated search across all scopes with tier boosting (project > user > global)")
2404
+ .option("-l, --limit <n>", "Max results", "20")
2405
+ .option("-d, --directory <dir>", "Project directory for context")
2406
+ .option("--no-global", "Exclude global-scope memories")
2407
+ .option("--json", "Output as JSON")
2408
+ .action(async (query, opts) => {
2409
+ let centralDb = null;
2410
+ try {
2411
+ centralDb = GnosysDB.openCentral();
2412
+ if (!centralDb.isAvailable()) {
2413
+ console.error("Central DB not available.");
2414
+ process.exit(1);
2415
+ }
2416
+ const { federatedSearch, detectCurrentProject } = await import("./lib/federated.js");
2417
+ const projectId = await detectCurrentProject(centralDb, opts.directory || undefined);
2418
+ const results = federatedSearch(centralDb, query, {
2419
+ limit: parseInt(opts.limit, 10),
2420
+ projectId,
2421
+ includeGlobal: opts.global,
2422
+ });
2423
+ if (opts.json) {
2424
+ console.log(JSON.stringify({ query, projectId, count: results.length, results }, null, 2));
2425
+ }
2426
+ else {
2427
+ if (results.length === 0) {
2428
+ console.log(`No results for "${query}".`);
2429
+ return;
2430
+ }
2431
+ const ctx = projectId ? `Context: project ${projectId}` : "No project detected";
2432
+ console.log(ctx);
2433
+ for (const [i, r] of results.entries()) {
2434
+ const proj = r.projectName ? ` [${r.projectName}]` : "";
2435
+ console.log(`\n${i + 1}. ${r.title} (${r.category})${proj}`);
2436
+ console.log(` scope: ${r.scope} | score: ${r.score.toFixed(4)} | boosts: ${r.boosts.join(", ")}`);
2437
+ if (r.snippet)
2438
+ console.log(` ${r.snippet.substring(0, 120)}`);
2439
+ }
2440
+ }
2441
+ }
2442
+ catch (err) {
2443
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
2444
+ process.exit(1);
2445
+ }
2446
+ finally {
2447
+ centralDb?.close();
2448
+ }
2449
+ });
2450
+ // ─── gnosys ambiguity ────────────────────────────────────────────────────
2451
+ program
2452
+ .command("ambiguity <query>")
2453
+ .description("Check if a query matches memories in multiple projects")
2454
+ .option("--json", "Output as JSON")
2455
+ .action(async (query, opts) => {
2456
+ let centralDb = null;
2457
+ try {
2458
+ centralDb = GnosysDB.openCentral();
2459
+ if (!centralDb.isAvailable()) {
2460
+ console.error("Central DB not available.");
2461
+ process.exit(1);
2462
+ }
2463
+ const { detectAmbiguity } = await import("./lib/federated.js");
2464
+ const ambiguity = detectAmbiguity(centralDb, query);
2465
+ if (opts.json) {
2466
+ console.log(JSON.stringify({ query, ambiguous: !!ambiguity, ...(ambiguity || {}) }, null, 2));
2467
+ }
2468
+ else if (!ambiguity) {
2469
+ console.log(`No ambiguity for "${query}" — matches at most one project.`);
2470
+ }
2471
+ else {
2472
+ console.log(ambiguity.message);
2473
+ for (const c of ambiguity.candidates) {
2474
+ console.log(`\n ${c.projectName} (${c.projectId})`);
2475
+ console.log(` Dir: ${c.workingDirectory}`);
2476
+ console.log(` Matching memories: ${c.memoryCount}`);
2477
+ }
2478
+ }
2479
+ }
2480
+ catch (err) {
2481
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
2482
+ process.exit(1);
2483
+ }
2484
+ finally {
2485
+ centralDb?.close();
2486
+ }
2487
+ });
2488
+ // ─── gnosys briefing ─────────────────────────────────────────────────────
2489
+ program
2490
+ .command("briefing")
2491
+ .description("Generate project briefing — memory state summary, categories, recent activity, top tags")
2492
+ .option("-p, --project <id>", "Project ID (auto-detects if omitted)")
2493
+ .option("-a, --all", "Generate briefings for all projects")
2494
+ .option("-d, --directory <dir>", "Project directory for auto-detection")
2495
+ .option("--json", "Output as JSON")
2496
+ .action(async (opts) => {
2497
+ let centralDb = null;
2498
+ try {
2499
+ centralDb = GnosysDB.openCentral();
2500
+ if (!centralDb.isAvailable()) {
2501
+ console.error("Central DB not available.");
2502
+ process.exit(1);
2503
+ }
2504
+ const { generateBriefing, generateAllBriefings, detectCurrentProject } = await import("./lib/federated.js");
2505
+ if (opts.all) {
2506
+ const briefings = generateAllBriefings(centralDb);
2507
+ if (opts.json) {
2508
+ console.log(JSON.stringify({ count: briefings.length, briefings }, null, 2));
2509
+ }
2510
+ else {
2511
+ if (briefings.length === 0) {
2512
+ console.log("No projects registered.");
2513
+ return;
2514
+ }
2515
+ for (const b of briefings) {
2516
+ console.log(`\n## ${b.projectName}`);
2517
+ console.log(b.summary);
2518
+ }
2519
+ }
2520
+ return;
2521
+ }
2522
+ let pid = opts.project || null;
2523
+ if (!pid)
2524
+ pid = await detectCurrentProject(centralDb, opts.directory || undefined);
2525
+ if (!pid) {
2526
+ console.error("No project specified and none detected.");
2527
+ process.exit(1);
2528
+ }
2529
+ const briefing = generateBriefing(centralDb, pid);
2530
+ if (!briefing) {
2531
+ console.error(`Project not found: ${pid}`);
2532
+ process.exit(1);
2533
+ }
2534
+ if (opts.json) {
2535
+ console.log(JSON.stringify(briefing, null, 2));
2536
+ }
2537
+ else {
2538
+ console.log(`# Briefing: ${briefing.projectName}`);
2539
+ console.log(`Directory: ${briefing.workingDirectory}`);
2540
+ console.log(`Active memories: ${briefing.activeMemories} / ${briefing.totalMemories}`);
2541
+ console.log(`\nCategories:`);
2542
+ for (const [cat, count] of Object.entries(briefing.categories).sort((a, b) => b[1] - a[1])) {
2543
+ console.log(` ${cat}: ${count}`);
2544
+ }
2545
+ console.log(`\nRecent activity (7d):`);
2546
+ if (briefing.recentActivity.length === 0) {
2547
+ console.log(" None");
2548
+ }
2549
+ for (const r of briefing.recentActivity) {
2550
+ console.log(` - ${r.title} (${r.modified})`);
2551
+ }
2552
+ console.log(`\nTop tags: ${briefing.topTags.slice(0, 10).map((t) => `${t.tag}(${t.count})`).join(", ") || "None"}`);
2553
+ console.log(`\n${briefing.summary}`);
2554
+ }
2555
+ }
2556
+ catch (err) {
2557
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
2558
+ process.exit(1);
2559
+ }
2560
+ finally {
2561
+ centralDb?.close();
2562
+ }
2563
+ });
2564
+ // ─── gnosys working-set ──────────────────────────────────────────────────
2565
+ program
2566
+ .command("working-set")
2567
+ .description("Show the implicit working set — recently modified memories for the current project")
2568
+ .option("-d, --directory <dir>", "Project directory")
2569
+ .option("-w, --window <hours>", "Lookback window in hours", "24")
2570
+ .option("--json", "Output as JSON")
2571
+ .action(async (opts) => {
2572
+ let centralDb = null;
2573
+ try {
2574
+ centralDb = GnosysDB.openCentral();
2575
+ if (!centralDb.isAvailable()) {
2576
+ console.error("Central DB not available.");
2577
+ process.exit(1);
2578
+ }
2579
+ const { getWorkingSet, formatWorkingSet, detectCurrentProject } = await import("./lib/federated.js");
2580
+ const pid = await detectCurrentProject(centralDb, opts.directory || undefined);
2581
+ if (!pid) {
2582
+ console.error("No project detected.");
2583
+ process.exit(1);
2584
+ }
2585
+ const windowHours = parseInt(opts.window, 10);
2586
+ const workingSet = getWorkingSet(centralDb, pid, { windowHours });
2587
+ if (opts.json) {
2588
+ console.log(JSON.stringify({
2589
+ projectId: pid,
2590
+ windowHours,
2591
+ count: workingSet.length,
2592
+ memories: workingSet.map((m) => ({ id: m.id, title: m.title, category: m.category, modified: m.modified })),
2593
+ }, null, 2));
2594
+ }
2595
+ else {
2596
+ console.log(formatWorkingSet(workingSet));
2597
+ }
2598
+ }
2599
+ catch (err) {
2600
+ console.error(`Error: ${err instanceof Error ? err.message : err}`);
2601
+ process.exit(1);
2602
+ }
2603
+ finally {
2604
+ centralDb?.close();
2605
+ }
2606
+ });
2312
2607
  program.parse();
2313
2608
  //# sourceMappingURL=cli.js.map