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 +367 -72
- package/dist/cli.js.map +1 -1
- package/dist/index.js +119 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/federated.d.ts +111 -0
- package/dist/lib/federated.d.ts.map +1 -0
- package/dist/lib/federated.js +340 -0
- package/dist/lib/federated.js.map +1 -0
- package/package.json +1 -1
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
|
-
|
|
34
|
-
|
|
35
|
-
dotenv.
|
|
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
|
-
.
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
171
|
+
outputResult(!!opts.json, { query, results: [] }, () => {
|
|
172
|
+
console.log(`No results for "${query}".`);
|
|
173
|
+
});
|
|
119
174
|
search.close();
|
|
120
175
|
return;
|
|
121
176
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
console.log(
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
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
|