@astrofoundry/grimoire 3.31.1 → 3.32.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/{admin-ENGUPLQ6.js → admin-4C3QNVQE.js} +58 -39
- package/dist/admin-4C3QNVQE.js.map +7 -0
- package/dist/{chunk-R46N6C3C.js → chunk-VTTJCQRQ.js} +37 -2
- package/dist/chunk-VTTJCQRQ.js.map +7 -0
- package/dist/cli.js +121 -14
- package/dist/cli.js.map +2 -2
- package/package.json +1 -1
- package/dist/admin-ENGUPLQ6.js.map +0 -7
- package/dist/chunk-R46N6C3C.js.map +0 -7
|
@@ -30,11 +30,46 @@ var bold = (s) => isTTY ? `\x1B[1m${s}\x1B[0m` : s;
|
|
|
30
30
|
var cyan = (s) => isTTY ? `\x1B[36m${s}\x1B[0m` : s;
|
|
31
31
|
var yellow = (s) => isTTY ? `\x1B[33m${s}\x1B[0m` : s;
|
|
32
32
|
|
|
33
|
+
// src/reranker.ts
|
|
34
|
+
function getRerankerUrl() {
|
|
35
|
+
const url = process.env.RERANKER_URL;
|
|
36
|
+
if (!url) {
|
|
37
|
+
throw new Error("RERANKER_URL environment variable is not set");
|
|
38
|
+
}
|
|
39
|
+
return url;
|
|
40
|
+
}
|
|
41
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
42
|
+
var RERANK_POOL_SIZE = 25;
|
|
43
|
+
var RERANK_DOC_MAX_CHARS = 800;
|
|
44
|
+
function rerankDocText(title, headingPath, content) {
|
|
45
|
+
const context = [title, ...headingPath].filter(Boolean).join(" > ");
|
|
46
|
+
const text = context ? `${context}
|
|
47
|
+
|
|
48
|
+
${content}` : content;
|
|
49
|
+
return text.length > RERANK_DOC_MAX_CHARS ? text.slice(0, RERANK_DOC_MAX_CHARS) : text;
|
|
50
|
+
}
|
|
51
|
+
async function rerank(query, documents, topN = 5, baseUrl = getRerankerUrl(), timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
52
|
+
const response = await fetch(`${baseUrl}/v1/rerank`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "Content-Type": "application/json" },
|
|
55
|
+
body: JSON.stringify({ query, documents, top_n: topN }),
|
|
56
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
throw new Error(`Reranker request failed: ${response.status} ${response.statusText}`);
|
|
60
|
+
}
|
|
61
|
+
const data = await response.json();
|
|
62
|
+
return data.results;
|
|
63
|
+
}
|
|
64
|
+
|
|
33
65
|
export {
|
|
34
66
|
__commonJS,
|
|
35
67
|
__toESM,
|
|
36
68
|
bold,
|
|
37
69
|
cyan,
|
|
38
|
-
yellow
|
|
70
|
+
yellow,
|
|
71
|
+
RERANK_POOL_SIZE,
|
|
72
|
+
rerankDocText,
|
|
73
|
+
rerank
|
|
39
74
|
};
|
|
40
|
-
//# sourceMappingURL=chunk-
|
|
75
|
+
//# sourceMappingURL=chunk-VTTJCQRQ.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/format.ts", "../src/reranker.ts"],
|
|
4
|
+
"sourcesContent": ["const isTTY = process.stdout.isTTY ?? false;\n\nexport const bold = (s: string): string => isTTY ? `\\x1b[1m${s}\\x1b[0m` : s;\nexport const cyan = (s: string): string => isTTY ? `\\x1b[36m${s}\\x1b[0m` : s;\nexport const yellow = (s: string): string => isTTY ? `\\x1b[33m${s}\\x1b[0m` : s;\nexport const red = (s: string): string => isTTY ? `\\x1b[31m${s}\\x1b[0m` : s;\n", "export interface RerankResult {\n index: number;\n relevance_score: number;\n}\n\ninterface LlamaCppRerankResponse {\n results: { index: number; relevance_score: number }[];\n}\n\nfunction getRerankerUrl(): string {\n const url = process.env.RERANKER_URL;\n if (!url) {\n throw new Error(\"RERANKER_URL environment variable is not set\");\n }\n return url;\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n// The local 4B reranker processes ~300 tokens/s, so document size and pool\n// size bound search latency directly. The contextual head (title > path +\n// the first part of the chunk) carries the relevance signal; the tail of a\n// 500-token table chunk adds latency, not ranking quality.\nexport const RERANK_POOL_SIZE = 25;\nconst RERANK_DOC_MAX_CHARS = 800;\n\nexport function rerankDocText(\n title: string,\n headingPath: string[],\n content: string,\n): string {\n const context = [title, ...headingPath].filter(Boolean).join(\" > \");\n const text = context ? `${context}\\n\\n${content}` : content;\n return text.length > RERANK_DOC_MAX_CHARS ? text.slice(0, RERANK_DOC_MAX_CHARS) : text;\n}\n\nexport async function rerank(\n query: string,\n documents: string[],\n topN = 5,\n baseUrl = getRerankerUrl(),\n timeoutMs = DEFAULT_TIMEOUT_MS,\n): Promise<RerankResult[]> {\n const response = await fetch(`${baseUrl}/v1/rerank`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ query, documents, top_n: topN }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n\n if (!response.ok) {\n throw new Error(`Reranker request failed: ${response.status} ${response.statusText}`);\n }\n\n const data = (await response.json()) as LlamaCppRerankResponse;\n return data.results;\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAM,QAAQ,QAAQ,OAAO,SAAS;AAE/B,IAAM,OAAO,CAAC,MAAsB,QAAQ,UAAU,CAAC,YAAY;AACnE,IAAM,OAAO,CAAC,MAAsB,QAAQ,WAAW,CAAC,YAAY;AACpE,IAAM,SAAS,CAAC,MAAsB,QAAQ,WAAW,CAAC,YAAY;;;ACK7E,SAAS,iBAAyB;AAChC,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,8CAA8C;AAAA,EAChE;AACA,SAAO;AACT;AAEA,IAAM,qBAAqB;AAMpB,IAAM,mBAAmB;AAChC,IAAM,uBAAuB;AAEtB,SAAS,cACd,OACA,aACA,SACQ;AACR,QAAM,UAAU,CAAC,OAAO,GAAG,WAAW,EAAE,OAAO,OAAO,EAAE,KAAK,KAAK;AAClE,QAAM,OAAO,UAAU,GAAG,OAAO;AAAA;AAAA,EAAO,OAAO,KAAK;AACpD,SAAO,KAAK,SAAS,uBAAuB,KAAK,MAAM,GAAG,oBAAoB,IAAI;AACpF;AAEA,eAAsB,OACpB,OACA,WACA,OAAO,GACP,UAAU,eAAe,GACzB,YAAY,oBACa;AACzB,QAAM,WAAW,MAAM,MAAM,GAAG,OAAO,cAAc;AAAA,IACnD,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,WAAW,OAAO,KAAK,CAAC;AAAA,IACtD,QAAQ,YAAY,QAAQ,SAAS;AAAA,EACvC,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,4BAA4B,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,EACtF;AAEA,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,SAAO,KAAK;AACd;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
RERANK_POOL_SIZE,
|
|
3
4
|
bold,
|
|
4
5
|
cyan,
|
|
6
|
+
rerank,
|
|
7
|
+
rerankDocText,
|
|
5
8
|
yellow
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-VTTJCQRQ.js";
|
|
7
10
|
|
|
8
11
|
// src/cli.ts
|
|
9
12
|
import { parseArgs } from "node:util";
|
|
@@ -22,7 +25,11 @@ async function loadConsumerConfig() {
|
|
|
22
25
|
if (!raw) return null;
|
|
23
26
|
const data = JSON.parse(raw);
|
|
24
27
|
if (typeof data.apiUrl === "string" && typeof data.apiKey === "string") {
|
|
25
|
-
return {
|
|
28
|
+
return {
|
|
29
|
+
apiUrl: data.apiUrl,
|
|
30
|
+
apiKey: data.apiKey,
|
|
31
|
+
...typeof data.rerankerUrl === "string" && data.rerankerUrl ? { rerankerUrl: data.rerankerUrl } : {}
|
|
32
|
+
};
|
|
26
33
|
}
|
|
27
34
|
return null;
|
|
28
35
|
}
|
|
@@ -33,11 +40,18 @@ async function saveConsumerConfig(config) {
|
|
|
33
40
|
async function resolveConsumerConfig() {
|
|
34
41
|
const envUrl = process.env.GRIMOIRE_API_URL;
|
|
35
42
|
const envKey = process.env.GRIMOIRE_API_KEY;
|
|
43
|
+
const envReranker = process.env.RERANKER_URL;
|
|
36
44
|
if (envUrl && envKey) {
|
|
37
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
apiUrl: envUrl,
|
|
47
|
+
apiKey: envKey,
|
|
48
|
+
...envReranker ? { rerankerUrl: envReranker } : {}
|
|
49
|
+
};
|
|
38
50
|
}
|
|
39
51
|
const fileConfig = await loadConsumerConfig();
|
|
40
|
-
if (fileConfig)
|
|
52
|
+
if (fileConfig) {
|
|
53
|
+
return envReranker ? { ...fileConfig, rerankerUrl: envReranker } : fileConfig;
|
|
54
|
+
}
|
|
41
55
|
throw new Error("Grimoire is not configured. Run 'grimoire init' to set up.");
|
|
42
56
|
}
|
|
43
57
|
async function detectConsumerMode() {
|
|
@@ -46,15 +60,53 @@ async function detectConsumerMode() {
|
|
|
46
60
|
const config = await loadConsumerConfig();
|
|
47
61
|
return config !== null;
|
|
48
62
|
}
|
|
63
|
+
async function setConfigRerankerUrl(url) {
|
|
64
|
+
const existing = await loadConsumerConfig();
|
|
65
|
+
if (!existing) {
|
|
66
|
+
throw new Error("Grimoire is not configured. Run 'grimoire init' first.");
|
|
67
|
+
}
|
|
68
|
+
await saveConsumerConfig({
|
|
69
|
+
apiUrl: existing.apiUrl,
|
|
70
|
+
apiKey: existing.apiKey,
|
|
71
|
+
...url ? { rerankerUrl: url } : {}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
async function cmdRerank(arg) {
|
|
75
|
+
if (!arg) {
|
|
76
|
+
const envUrl = process.env.RERANKER_URL;
|
|
77
|
+
const fileConfig = await loadConsumerConfig();
|
|
78
|
+
const effective = envUrl ?? fileConfig?.rerankerUrl;
|
|
79
|
+
console.log(`Env RERANKER_URL: ${envUrl ?? "(not set)"}`);
|
|
80
|
+
console.log(`Config rerankerUrl: ${fileConfig?.rerankerUrl ?? "(not set)"}`);
|
|
81
|
+
console.log(`Effective: ${effective ?? "off"}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (arg === "off") {
|
|
85
|
+
await setConfigRerankerUrl(void 0);
|
|
86
|
+
console.log(`Reranker removed from ${CONFIG_FILE}.`);
|
|
87
|
+
if (process.env.RERANKER_URL) {
|
|
88
|
+
console.log("Note: RERANKER_URL is set in your environment and still takes precedence. Unset it too.");
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
new URL(arg);
|
|
93
|
+
await setConfigRerankerUrl(arg);
|
|
94
|
+
console.log(`Reranker set to ${arg} in ${CONFIG_FILE}.`);
|
|
95
|
+
}
|
|
49
96
|
async function cmdInit() {
|
|
50
97
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
51
98
|
const ask = (q) => new Promise((resolve2) => rl.question(q, resolve2));
|
|
52
99
|
const existing = await loadConsumerConfig();
|
|
53
100
|
const apiUrl = await ask(`API URL${existing ? ` [${existing.apiUrl}]` : ""}: `);
|
|
54
101
|
const apiKey = await ask(`API Key${existing ? " [****]" : ""}: `);
|
|
102
|
+
const rerankerUrl = await ask(
|
|
103
|
+
`Reranker URL (optional, local network)${existing?.rerankerUrl ? ` [${existing.rerankerUrl}]` : ""}: `
|
|
104
|
+
);
|
|
105
|
+
const resolvedReranker = rerankerUrl.trim() || existing?.rerankerUrl;
|
|
55
106
|
const config = {
|
|
56
107
|
apiUrl: apiUrl.trim() || existing?.apiUrl || "",
|
|
57
|
-
apiKey: apiKey.trim() || existing?.apiKey || ""
|
|
108
|
+
apiKey: apiKey.trim() || existing?.apiKey || "",
|
|
109
|
+
...resolvedReranker ? { rerankerUrl: resolvedReranker } : {}
|
|
58
110
|
};
|
|
59
111
|
rl.close();
|
|
60
112
|
if (!config.apiUrl || !config.apiKey) {
|
|
@@ -66,6 +118,31 @@ Saved to ${CONFIG_FILE}`);
|
|
|
66
118
|
}
|
|
67
119
|
|
|
68
120
|
// src/consumer.ts
|
|
121
|
+
function contextualText(result) {
|
|
122
|
+
return rerankDocText(result.title, result.heading_path, result.content);
|
|
123
|
+
}
|
|
124
|
+
var RERANK_TIMEOUT_MS = 3e4;
|
|
125
|
+
async function rerankResults(rerankerUrl, query, results, topN) {
|
|
126
|
+
try {
|
|
127
|
+
const reranked = await rerank(
|
|
128
|
+
query,
|
|
129
|
+
results.map(contextualText),
|
|
130
|
+
topN,
|
|
131
|
+
rerankerUrl,
|
|
132
|
+
RERANK_TIMEOUT_MS
|
|
133
|
+
);
|
|
134
|
+
return reranked.map((r) => ({
|
|
135
|
+
...results[r.index],
|
|
136
|
+
relevance_score: r.relevance_score
|
|
137
|
+
}));
|
|
138
|
+
} catch (err) {
|
|
139
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
140
|
+
console.error(
|
|
141
|
+
`Warning: reranker at ${rerankerUrl} unavailable (${message}); using API ranking. Use --no-rerank or 'grimoire rerank off' to silence this.`
|
|
142
|
+
);
|
|
143
|
+
return results.slice(0, topN);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
69
146
|
async function apiRequest(config, path, options) {
|
|
70
147
|
const url = `${config.apiUrl.replace(/\/$/, "")}${path}`;
|
|
71
148
|
let response;
|
|
@@ -90,12 +167,14 @@ async function apiRequest(config, path, options) {
|
|
|
90
167
|
return response.json();
|
|
91
168
|
}
|
|
92
169
|
async function cmdConsumerSearch(config, query, options) {
|
|
170
|
+
const topN = options.topN ?? 5;
|
|
171
|
+
const rerankerUrl = options.noRerank ? void 0 : config.rerankerUrl;
|
|
93
172
|
const data = await apiRequest(config, "/search", {
|
|
94
173
|
method: "POST",
|
|
95
174
|
body: JSON.stringify({
|
|
96
175
|
query,
|
|
97
176
|
source: options.source,
|
|
98
|
-
topN:
|
|
177
|
+
topN: rerankerUrl ? Math.max(topN, RERANK_POOL_SIZE) : topN,
|
|
99
178
|
candidates: options.candidates
|
|
100
179
|
})
|
|
101
180
|
});
|
|
@@ -103,6 +182,9 @@ async function cmdConsumerSearch(config, query, options) {
|
|
|
103
182
|
console.log("No results found.");
|
|
104
183
|
return;
|
|
105
184
|
}
|
|
185
|
+
if (rerankerUrl) {
|
|
186
|
+
data.results = await rerankResults(rerankerUrl, query, data.results, topN);
|
|
187
|
+
}
|
|
106
188
|
if (options.compact) {
|
|
107
189
|
for (const r of data.results) {
|
|
108
190
|
console.log(`${r.relevance_score.toFixed(4)} | ${r.source} | ${r.title} | ${r.heading_path.join(" > ")} | ${r.url}`);
|
|
@@ -188,10 +270,10 @@ async function cmdUpdate() {
|
|
|
188
270
|
function showHelp(isConsumer) {
|
|
189
271
|
if (isConsumer) {
|
|
190
272
|
console.log(`
|
|
191
|
-
grimoire
|
|
273
|
+
grimoire - Documentation RAG
|
|
192
274
|
|
|
193
275
|
USAGE
|
|
194
|
-
grimoire search "<query>" [--source <name>] [--top <n>] [--candidates <n>] [--compact]
|
|
276
|
+
grimoire search "<query>" [--source <name>] [--top <n>] [--candidates <n>] [--compact] [--no-rerank]
|
|
195
277
|
|
|
196
278
|
QUERY TIPS
|
|
197
279
|
- Use the library's own terminology ("Firestore batched writes"
|
|
@@ -218,28 +300,40 @@ FLAGS
|
|
|
218
300
|
--compact One line per result: score | source | title | heading | url
|
|
219
301
|
Default (non-compact): multi-line block with title, URL,
|
|
220
302
|
heading path, and content snippet.
|
|
303
|
+
--no-rerank Skip local reranking for this search even if a reranker
|
|
304
|
+
is configured. No effect when none is configured.
|
|
221
305
|
|
|
222
306
|
RELEVANCE SCORES
|
|
223
307
|
Range 0-1 (higher = better). >0.85 strong match, 0.6-0.85 relevant,
|
|
224
308
|
<0.6 usually too weak to cite. "No results found." + exit 0 = clean miss.
|
|
225
309
|
Results are ranked by hybrid vector + exact-identifier match; the score
|
|
226
310
|
shown is vector similarity, so an exact identifier hit can rank first
|
|
227
|
-
with a modest score.
|
|
311
|
+
with a modest score. With RERANKER_URL set, results are reranked locally
|
|
312
|
+
and the score is the reranker's relevance instead.
|
|
228
313
|
|
|
229
314
|
MANAGEMENT
|
|
230
315
|
grimoire list [--names] Show indexed sources
|
|
231
316
|
grimoire stats Index statistics
|
|
232
317
|
grimoire init Configure API connection (first-time setup)
|
|
318
|
+
grimoire rerank Show reranker status (env, config, effective)
|
|
319
|
+
grimoire rerank <url> Enable local reranking (saved to config)
|
|
320
|
+
grimoire rerank off Disable local reranking (removes from config)
|
|
233
321
|
grimoire update Update grimoire itself
|
|
234
322
|
grimoire --version Print CLI version
|
|
235
323
|
|
|
236
324
|
ENVIRONMENT
|
|
237
325
|
GRIMOIRE_API_URL API endpoint URL
|
|
238
326
|
GRIMOIRE_API_KEY API key
|
|
327
|
+
RERANKER_URL Optional. Local-network reranker endpoint; when
|
|
328
|
+
set, search fetches a 50-result pool and reranks
|
|
329
|
+
client-side. Overrides "rerankerUrl" in
|
|
330
|
+
~/.grimoire/config.json. If the reranker is
|
|
331
|
+
unreachable, search warns on stderr and falls
|
|
332
|
+
back to the API ranking after a 15s timeout.
|
|
239
333
|
`);
|
|
240
334
|
} else {
|
|
241
335
|
console.log(`
|
|
242
|
-
grimoire
|
|
336
|
+
grimoire - Documentation RAG System (admin)
|
|
243
337
|
|
|
244
338
|
Commands:
|
|
245
339
|
add <name> --url <url> Add a new documentation source
|
|
@@ -251,6 +345,8 @@ Commands:
|
|
|
251
345
|
refresh <source> --full Purge chunks + caches, re-scrape from scratch
|
|
252
346
|
refresh <source> --skip-store Dry run: scrape/convert/chunk only,
|
|
253
347
|
no embedding, no Firestore writes
|
|
348
|
+
refresh <source> --allow-shrink Override the shrink/growth guards when a
|
|
349
|
+
large chunk-count change is expected
|
|
254
350
|
refresh --all Refresh all sources
|
|
255
351
|
delete <source> Delete source and all its chunks
|
|
256
352
|
scrape-urls <source> Fetch missing pages from urls.json
|
|
@@ -287,6 +383,10 @@ async function main() {
|
|
|
287
383
|
await cmdInit();
|
|
288
384
|
return;
|
|
289
385
|
}
|
|
386
|
+
if (command === "rerank") {
|
|
387
|
+
await cmdRerank(process.argv[3]);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
290
390
|
if (ADMIN_ONLY_COMMANDS.includes(command)) {
|
|
291
391
|
console.error(`The '${command}' command is only available in admin mode.`);
|
|
292
392
|
process.exit(1);
|
|
@@ -302,18 +402,25 @@ async function main() {
|
|
|
302
402
|
source: { type: "string" },
|
|
303
403
|
top: { type: "string" },
|
|
304
404
|
candidates: { type: "string" },
|
|
305
|
-
compact: { type: "boolean", default: false }
|
|
405
|
+
compact: { type: "boolean", default: false },
|
|
406
|
+
"no-rerank": { type: "boolean", default: false }
|
|
306
407
|
},
|
|
307
408
|
allowPositionals: true
|
|
308
409
|
});
|
|
309
410
|
const query = args.positionals[0];
|
|
310
411
|
if (!query) {
|
|
311
|
-
console.error('Usage: grimoire search "<query>" [--source <name>] [--top <n>] [--candidates <n>] [--compact]');
|
|
412
|
+
console.error('Usage: grimoire search "<query>" [--source <name>] [--top <n>] [--candidates <n>] [--compact] [--no-rerank]');
|
|
312
413
|
process.exit(1);
|
|
313
414
|
}
|
|
314
415
|
const topN = args.values.top ? parseInt(args.values.top, 10) : void 0;
|
|
315
416
|
const candidates = args.values.candidates ? parseInt(args.values.candidates, 10) : void 0;
|
|
316
|
-
await cmdConsumerSearch(config, query, {
|
|
417
|
+
await cmdConsumerSearch(config, query, {
|
|
418
|
+
source: args.values.source,
|
|
419
|
+
topN,
|
|
420
|
+
candidates,
|
|
421
|
+
compact: args.values.compact,
|
|
422
|
+
noRerank: args.values["no-rerank"]
|
|
423
|
+
});
|
|
317
424
|
} else if (command === "list") {
|
|
318
425
|
const args = parseArgs({
|
|
319
426
|
args: process.argv.slice(3),
|
|
@@ -333,7 +440,7 @@ async function main() {
|
|
|
333
440
|
await cmdInit();
|
|
334
441
|
return;
|
|
335
442
|
}
|
|
336
|
-
const { ADMIN_COMMANDS } = await import("./admin-
|
|
443
|
+
const { ADMIN_COMMANDS } = await import("./admin-4C3QNVQE.js");
|
|
337
444
|
const handler = ADMIN_COMMANDS[command];
|
|
338
445
|
if (!handler) {
|
|
339
446
|
console.error(`Unknown command: ${command}. Run "grimoire --help" for usage.`);
|
package/dist/cli.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/cli.ts", "../src/consumer-config.ts", "../src/consumer.ts"],
|
|
4
|
-
"sourcesContent": ["import { parseArgs } from \"node:util\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { detectConsumerMode, resolveConsumerConfig, cmdInit } from \"./consumer-config.js\";\nimport { cmdConsumerSearch, cmdConsumerList, cmdConsumerStats } from \"./consumer.js\";\n\nconst PROJECT_ROOT = resolve(import.meta.dirname, \"..\");\n\nconst envPath = join(PROJECT_ROOT, \".env\");\nif (existsSync(envPath)) {\n for (const line of readFileSync(envPath, \"utf-8\").split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) continue;\n const key = trimmed.slice(0, eqIndex);\n const value = trimmed.slice(eqIndex + 1);\n if (!process.env[key]) {\n process.env[key] = value;\n }\n }\n}\n\nconst ADMIN_ONLY_COMMANDS = [\"add\", \"refresh\", \"delete\", \"scrape-urls\", \"export\", \"apikey\"];\n\nasync function cmdUpdate(): Promise<void> {\n const { execSync } = await import(\"node:child_process\");\n const pkg = JSON.parse(readFileSync(join(PROJECT_ROOT, \"package.json\"), \"utf-8\"));\n console.log(`Current version: ${pkg.version}`);\n console.log(\"Checking for updates...\");\n execSync(\"npm install -g @astrofoundry/grimoire@latest\", { stdio: \"inherit\" });\n const updated = JSON.parse(readFileSync(join(PROJECT_ROOT, \"package.json\"), \"utf-8\"));\n if (updated.version === pkg.version) {\n console.log(\"Already on the latest version.\");\n } else {\n console.log(`Updated to ${updated.version}.`);\n }\n}\n\nfunction showHelp(isConsumer: boolean): void {\n if (isConsumer) {\n console.log(`\ngrimoire \u2014 Documentation RAG\n\nUSAGE\n grimoire search \"<query>\" [--source <name>] [--top <n>] [--candidates <n>] [--compact]\n\nQUERY TIPS\n - Use the library's own terminology (\"Firestore batched writes\"\n not \"how do I do multi-write in firestore\")\n - Exact identifiers (max_connections, ExportContext, snake_case,\n dotted.paths, camelCase) are matched literally - include them verbatim\n - Scope with --source when you know the area\n - Rephrase if first search misses\n - Prefer --compact for scanning; omit for full snippets\n - Cite the URL in your answer when precision matters\n\nEXAMPLES\n grimoire search \"Firestore batched writes\" --source firebase-firestore --compact\n grimoire search \"react server components\" --top 3\n grimoire search \"mysql max_connections default\" --source gcp-cloud-sql\n grimoire list --names\n\nFLAGS\n --source <name> Scope to one indexed source. Run \\`grimoire list --names\\`\n for the full list (sources are added regularly).\n --top <n> Max results. Default: 5.\n --candidates <n> Retrieval pool size before final ranking. Default: 50,\n max: 200. Raise for broad/ambiguous queries.\n --compact One line per result: score | source | title | heading | url\n Default (non-compact): multi-line block with title, URL,\n heading path, and content snippet.\n\nRELEVANCE SCORES\n Range 0-1 (higher = better). >0.85 strong match, 0.6-0.85 relevant,\n <0.6 usually too weak to cite. \"No results found.\" + exit 0 = clean miss.\n Results are ranked by hybrid vector + exact-identifier match; the score\n shown is vector similarity, so an exact identifier hit can rank first\n with a modest score.\n\nMANAGEMENT\n grimoire list [--names] Show indexed sources\n grimoire stats Index statistics\n grimoire init Configure API connection (first-time setup)\n grimoire update Update grimoire itself\n grimoire --version Print CLI version\n\nENVIRONMENT\n GRIMOIRE_API_URL API endpoint URL\n GRIMOIRE_API_KEY API key\n`);\n } else {\n console.log(`\ngrimoire \u2014 Documentation RAG System (admin)\n\nCommands:\n add <name> --url <url> Add a new documentation source\n refresh <source> Scrape, convert, then sync: only chunks\n whose content changed are re-embedded and\n written; removed chunks are deleted\n refresh <source> --from-html Re-convert from cached HTML, then sync\n refresh <source> --from-markdown Re-chunk from cached markdown, then sync\n refresh <source> --full Purge chunks + caches, re-scrape from scratch\n refresh <source> --skip-store Dry run: scrape/convert/chunk only,\n no embedding, no Firestore writes\n refresh --all Refresh all sources\n delete <source> Delete source and all its chunks\n scrape-urls <source> Fetch missing pages from urls.json\n update Update grimoire to the latest version\n search \"<query>\" [--source <n>] [--top <n>] [--candidates <n>] [--compact]\n Hybrid search (vector + exact identifiers)\n list [--names] List all sources\n stats Show chunk/source statistics\n export <source> Export source as JSON\n apikey create <name> Generate an API key\n apikey list List API keys\n apikey delete <name> Delete an API key\n`);\n }\n}\n\nasync function main(): Promise<void> {\n const command = process.argv[2];\n\n if (command === \"--version\" || command === \"-v\") {\n const pkg = JSON.parse(readFileSync(join(PROJECT_ROOT, \"package.json\"), \"utf-8\"));\n console.log(pkg.version);\n process.exit(0);\n }\n\n const isConsumer = await detectConsumerMode();\n\n if (!command || command === \"--help\" || command === \"-h\") {\n showHelp(isConsumer);\n process.exit(0);\n }\n\n if (command === \"update\") {\n await cmdUpdate();\n return;\n }\n\n if (isConsumer) {\n if (command === \"init\") {\n await cmdInit();\n return;\n }\n\n if (ADMIN_ONLY_COMMANDS.includes(command)) {\n console.error(`The '${command}' command is only available in admin mode.`);\n process.exit(1);\n }\n\n const config = await resolveConsumerConfig().catch(() => {\n console.error(\"Grimoire is not configured yet. Run 'grimoire init' to set up your API connection.\");\n process.exit(1);\n });\n\n if (command === \"search\") {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n source: { type: \"string\" },\n top: { type: \"string\" },\n candidates: { type: \"string\" },\n compact: { type: \"boolean\", default: false },\n },\n allowPositionals: true,\n });\n const query = args.positionals[0];\n if (!query) {\n console.error(\"Usage: grimoire search \\\"<query>\\\" [--source <name>] [--top <n>] [--candidates <n>] [--compact]\");\n process.exit(1);\n }\n const topN = args.values.top ? parseInt(args.values.top, 10) : undefined;\n const candidates = args.values.candidates ? parseInt(args.values.candidates, 10) : undefined;\n await cmdConsumerSearch(config, query, { source: args.values.source, topN, candidates, compact: args.values.compact });\n } else if (command === \"list\") {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: { names: { type: \"boolean\", default: false } },\n allowPositionals: true,\n });\n await cmdConsumerList(config, { names: args.values.names });\n } else if (command === \"stats\") {\n await cmdConsumerStats(config);\n } else {\n console.error(`Unknown command: ${command}. Run \"grimoire --help\" for usage.`);\n process.exit(1);\n }\n return;\n }\n\n if (command === \"init\") {\n await cmdInit();\n return;\n }\n\n const { ADMIN_COMMANDS } = await import(\"./admin.js\");\n const handler = ADMIN_COMMANDS[command];\n if (!handler) {\n console.error(`Unknown command: ${command}. Run \"grimoire --help\" for usage.`);\n process.exit(1);\n }\n\n await handler();\n}\n\nconst GCP_AUTH_PATTERNS = [\n \"Unable to detect a Project Id\",\n \"Could not load the default credentials\",\n \"invalid_grant\",\n \"invalid_rapt\",\n \"UNAUTHENTICATED\",\n \"Getting metadata from plugin failed\",\n];\n\nmain().catch((err: Error) => {\n const msg = err.message ?? String(err);\n if (GCP_AUTH_PATTERNS.some((p) => msg.includes(p))) {\n console.error(\"Google Cloud authentication failed. Re-authenticate with:\\n\\n gcloud auth application-default login\\n\");\n } else {\n console.error(`Error: ${msg}`);\n }\n process.exit(1);\n});\n", "import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { createInterface } from \"node:readline\";\n\nconst CONFIG_DIR = join(homedir(), \".grimoire\");\nconst CONFIG_FILE = join(CONFIG_DIR, \"config.json\");\n\nexport interface ConsumerConfig {\n apiUrl: string;\n apiKey: string;\n}\n\nexport async function loadConsumerConfig(): Promise<ConsumerConfig | null> {\n const raw = await readFile(CONFIG_FILE, \"utf-8\").catch(() => null);\n if (!raw) return null;\n const data = JSON.parse(raw);\n if (typeof data.apiUrl === \"string\" && typeof data.apiKey === \"string\") {\n return { apiUrl: data.apiUrl, apiKey: data.apiKey };\n }\n return null;\n}\n\nexport async function saveConsumerConfig(config: ConsumerConfig): Promise<void> {\n await mkdir(CONFIG_DIR, { recursive: true });\n await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n\nexport async function resolveConsumerConfig(): Promise<ConsumerConfig> {\n const envUrl = process.env.GRIMOIRE_API_URL;\n const envKey = process.env.GRIMOIRE_API_KEY;\n\n if (envUrl && envKey) {\n return { apiUrl: envUrl, apiKey: envKey };\n }\n\n const fileConfig = await loadConsumerConfig();\n if (fileConfig) return fileConfig;\n\n throw new Error(\"Grimoire is not configured. Run 'grimoire init' to set up.\");\n}\n\nexport function isConsumerMode(): boolean {\n return !!process.env.GRIMOIRE_API_URL;\n}\n\nexport async function detectConsumerMode(): Promise<boolean> {\n if (process.env.GOOGLE_APPLICATION_CREDENTIALS) return false;\n if (process.env.GRIMOIRE_API_URL) return true;\n const config = await loadConsumerConfig();\n return config !== null;\n}\n\nexport async function cmdInit(): Promise<void> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (q: string): Promise<string> =>\n new Promise((resolve) => rl.question(q, resolve));\n\n const existing = await loadConsumerConfig();\n\n const apiUrl = await ask(`API URL${existing ? ` [${existing.apiUrl}]` : \"\"}: `);\n const apiKey = await ask(`API Key${existing ? \" [****]\" : \"\"}: `);\n\n const config: ConsumerConfig = {\n apiUrl: apiUrl.trim() || existing?.apiUrl || \"\",\n apiKey: apiKey.trim() || existing?.apiKey || \"\",\n };\n\n rl.close();\n\n if (!config.apiUrl || !config.apiKey) {\n throw new Error(\"Both API URL and API Key are required.\");\n }\n\n await saveConsumerConfig(config);\n console.log(`\\nSaved to ${CONFIG_FILE}`);\n}\n", "import type { SearchResult, SourceMeta } from \"./types.js\";\nimport type { ConsumerConfig } from \"./consumer-config.js\";\nimport { bold, cyan, yellow } from \"./format.js\";\n\nasync function apiRequest<T>(config: ConsumerConfig, path: string, options?: RequestInit): Promise<T> {\n const url = `${config.apiUrl.replace(/\\/$/, \"\")}${path}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": config.apiKey,\n ...options?.headers,\n },\n });\n } catch {\n throw new Error(`Cannot reach Grimoire API at ${config.apiUrl}. Check your GRIMOIRE_API_URL.`);\n }\n\n if (response.status === 401 || response.status === 403) {\n throw new Error(\"Invalid API key. Check your GRIMOIRE_API_KEY or run 'grimoire init'.\");\n }\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${response.statusText}`);\n }\n\n return response.json() as Promise<T>;\n}\n\nexport async function cmdConsumerSearch(\n config: ConsumerConfig,\n query: string,\n options: { source?: string; topN?: number; candidates?: number; compact?: boolean },\n): Promise<void> {\n const data = await apiRequest<{ results: SearchResult[] }>(config, \"/search\", {\n method: \"POST\",\n body: JSON.stringify({\n query,\n source: options.source,\n topN: options.topN,\n candidates: options.candidates,\n }),\n });\n\n if (data.results.length === 0) {\n console.log(\"No results found.\");\n return;\n }\n\n if (options.compact) {\n for (const r of data.results) {\n console.log(`${r.relevance_score.toFixed(4)} | ${r.source} | ${r.title} | ${r.heading_path.join(\" > \")} | ${r.url}`);\n }\n return;\n }\n\n for (let i = 0; i < data.results.length; i++) {\n const r = data.results[i];\n console.log(`\\n${bold(`[${i + 1}] ${r.title}`)} (${r.relevance_score.toFixed(4)})`);\n console.log(` ${cyan(r.url)}`);\n console.log(` ${yellow(r.heading_path.join(\" > \"))}`);\n console.log(` ${r.content.replace(/\\n/g, \" \")}`);\n }\n}\n\nexport async function cmdConsumerList(config: ConsumerConfig, options?: { names?: boolean }): Promise<void> {\n const data = await apiRequest<{ sources: SourceMeta[] }>(config, \"/list\");\n\n if (data.sources.length === 0) {\n console.log(\"No sources available.\");\n return;\n }\n\n if (options?.names) {\n for (const s of data.sources) {\n console.log(s.source);\n }\n return;\n }\n\n console.log(\"\\nSources:\\n\");\n for (const s of data.sources) {\n const ver = s.version ? ` v${s.version}` : \"\";\n console.log(` ${bold(s.source)}${ver}`);\n console.log(` ${s.chunk_count} chunks, ${s.url_count} URLs, last refreshed ${s.last_refreshed}`);\n }\n}\n\nexport async function cmdConsumerStats(config: ConsumerConfig): Promise<void> {\n const data = await apiRequest<{ sources: SourceMeta[]; totalChunks: number; totalUrls: number }>(config, \"/stats\");\n\n if (data.sources.length === 0) {\n console.log(\"No sources have been refreshed yet.\");\n return;\n }\n\n console.log(\"\\nSource Statistics:\\n\");\n for (const s of data.sources) {\n const ver = s.version ? ` v${s.version}` : \"\";\n console.log(` ${bold(s.source)}${ver}`);\n console.log(` Chunks: ${s.chunk_count}`);\n console.log(` URLs: ${s.url_count}`);\n console.log(` Last refreshed: ${s.last_refreshed}`);\n }\n\n console.log(`\\n Total: ${data.totalChunks} chunks across ${data.totalUrls} URLs from ${data.sources.length} sources`);\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { parseArgs } from \"node:util\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { detectConsumerMode, resolveConsumerConfig, cmdInit, cmdRerank } from \"./consumer-config.js\";\nimport { cmdConsumerSearch, cmdConsumerList, cmdConsumerStats } from \"./consumer.js\";\n\nconst PROJECT_ROOT = resolve(import.meta.dirname, \"..\");\n\nconst envPath = join(PROJECT_ROOT, \".env\");\nif (existsSync(envPath)) {\n for (const line of readFileSync(envPath, \"utf-8\").split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) continue;\n const key = trimmed.slice(0, eqIndex);\n const value = trimmed.slice(eqIndex + 1);\n if (!process.env[key]) {\n process.env[key] = value;\n }\n }\n}\n\nconst ADMIN_ONLY_COMMANDS = [\"add\", \"refresh\", \"delete\", \"scrape-urls\", \"export\", \"apikey\"];\n\nasync function cmdUpdate(): Promise<void> {\n const { execSync } = await import(\"node:child_process\");\n const pkg = JSON.parse(readFileSync(join(PROJECT_ROOT, \"package.json\"), \"utf-8\"));\n console.log(`Current version: ${pkg.version}`);\n console.log(\"Checking for updates...\");\n execSync(\"npm install -g @astrofoundry/grimoire@latest\", { stdio: \"inherit\" });\n const updated = JSON.parse(readFileSync(join(PROJECT_ROOT, \"package.json\"), \"utf-8\"));\n if (updated.version === pkg.version) {\n console.log(\"Already on the latest version.\");\n } else {\n console.log(`Updated to ${updated.version}.`);\n }\n}\n\nfunction showHelp(isConsumer: boolean): void {\n if (isConsumer) {\n console.log(`\ngrimoire - Documentation RAG\n\nUSAGE\n grimoire search \"<query>\" [--source <name>] [--top <n>] [--candidates <n>] [--compact] [--no-rerank]\n\nQUERY TIPS\n - Use the library's own terminology (\"Firestore batched writes\"\n not \"how do I do multi-write in firestore\")\n - Exact identifiers (max_connections, ExportContext, snake_case,\n dotted.paths, camelCase) are matched literally - include them verbatim\n - Scope with --source when you know the area\n - Rephrase if first search misses\n - Prefer --compact for scanning; omit for full snippets\n - Cite the URL in your answer when precision matters\n\nEXAMPLES\n grimoire search \"Firestore batched writes\" --source firebase-firestore --compact\n grimoire search \"react server components\" --top 3\n grimoire search \"mysql max_connections default\" --source gcp-cloud-sql\n grimoire list --names\n\nFLAGS\n --source <name> Scope to one indexed source. Run \\`grimoire list --names\\`\n for the full list (sources are added regularly).\n --top <n> Max results. Default: 5.\n --candidates <n> Retrieval pool size before final ranking. Default: 50,\n max: 200. Raise for broad/ambiguous queries.\n --compact One line per result: score | source | title | heading | url\n Default (non-compact): multi-line block with title, URL,\n heading path, and content snippet.\n --no-rerank Skip local reranking for this search even if a reranker\n is configured. No effect when none is configured.\n\nRELEVANCE SCORES\n Range 0-1 (higher = better). >0.85 strong match, 0.6-0.85 relevant,\n <0.6 usually too weak to cite. \"No results found.\" + exit 0 = clean miss.\n Results are ranked by hybrid vector + exact-identifier match; the score\n shown is vector similarity, so an exact identifier hit can rank first\n with a modest score. With RERANKER_URL set, results are reranked locally\n and the score is the reranker's relevance instead.\n\nMANAGEMENT\n grimoire list [--names] Show indexed sources\n grimoire stats Index statistics\n grimoire init Configure API connection (first-time setup)\n grimoire rerank Show reranker status (env, config, effective)\n grimoire rerank <url> Enable local reranking (saved to config)\n grimoire rerank off Disable local reranking (removes from config)\n grimoire update Update grimoire itself\n grimoire --version Print CLI version\n\nENVIRONMENT\n GRIMOIRE_API_URL API endpoint URL\n GRIMOIRE_API_KEY API key\n RERANKER_URL Optional. Local-network reranker endpoint; when\n set, search fetches a 50-result pool and reranks\n client-side. Overrides \"rerankerUrl\" in\n ~/.grimoire/config.json. If the reranker is\n unreachable, search warns on stderr and falls\n back to the API ranking after a 15s timeout.\n`);\n } else {\n console.log(`\ngrimoire - Documentation RAG System (admin)\n\nCommands:\n add <name> --url <url> Add a new documentation source\n refresh <source> Scrape, convert, then sync: only chunks\n whose content changed are re-embedded and\n written; removed chunks are deleted\n refresh <source> --from-html Re-convert from cached HTML, then sync\n refresh <source> --from-markdown Re-chunk from cached markdown, then sync\n refresh <source> --full Purge chunks + caches, re-scrape from scratch\n refresh <source> --skip-store Dry run: scrape/convert/chunk only,\n no embedding, no Firestore writes\n refresh <source> --allow-shrink Override the shrink/growth guards when a\n large chunk-count change is expected\n refresh --all Refresh all sources\n delete <source> Delete source and all its chunks\n scrape-urls <source> Fetch missing pages from urls.json\n update Update grimoire to the latest version\n search \"<query>\" [--source <n>] [--top <n>] [--candidates <n>] [--compact]\n Hybrid search (vector + exact identifiers)\n list [--names] List all sources\n stats Show chunk/source statistics\n export <source> Export source as JSON\n apikey create <name> Generate an API key\n apikey list List API keys\n apikey delete <name> Delete an API key\n`);\n }\n}\n\nasync function main(): Promise<void> {\n const command = process.argv[2];\n\n if (command === \"--version\" || command === \"-v\") {\n const pkg = JSON.parse(readFileSync(join(PROJECT_ROOT, \"package.json\"), \"utf-8\"));\n console.log(pkg.version);\n process.exit(0);\n }\n\n const isConsumer = await detectConsumerMode();\n\n if (!command || command === \"--help\" || command === \"-h\") {\n showHelp(isConsumer);\n process.exit(0);\n }\n\n if (command === \"update\") {\n await cmdUpdate();\n return;\n }\n\n if (isConsumer) {\n if (command === \"init\") {\n await cmdInit();\n return;\n }\n\n if (command === \"rerank\") {\n await cmdRerank(process.argv[3]);\n return;\n }\n\n if (ADMIN_ONLY_COMMANDS.includes(command)) {\n console.error(`The '${command}' command is only available in admin mode.`);\n process.exit(1);\n }\n\n const config = await resolveConsumerConfig().catch(() => {\n console.error(\"Grimoire is not configured yet. Run 'grimoire init' to set up your API connection.\");\n process.exit(1);\n });\n\n if (command === \"search\") {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: {\n source: { type: \"string\" },\n top: { type: \"string\" },\n candidates: { type: \"string\" },\n compact: { type: \"boolean\", default: false },\n \"no-rerank\": { type: \"boolean\", default: false },\n },\n allowPositionals: true,\n });\n const query = args.positionals[0];\n if (!query) {\n console.error(\"Usage: grimoire search \\\"<query>\\\" [--source <name>] [--top <n>] [--candidates <n>] [--compact] [--no-rerank]\");\n process.exit(1);\n }\n const topN = args.values.top ? parseInt(args.values.top, 10) : undefined;\n const candidates = args.values.candidates ? parseInt(args.values.candidates, 10) : undefined;\n await cmdConsumerSearch(config, query, {\n source: args.values.source,\n topN,\n candidates,\n compact: args.values.compact,\n noRerank: args.values[\"no-rerank\"],\n });\n } else if (command === \"list\") {\n const args = parseArgs({\n args: process.argv.slice(3),\n options: { names: { type: \"boolean\", default: false } },\n allowPositionals: true,\n });\n await cmdConsumerList(config, { names: args.values.names });\n } else if (command === \"stats\") {\n await cmdConsumerStats(config);\n } else {\n console.error(`Unknown command: ${command}. Run \"grimoire --help\" for usage.`);\n process.exit(1);\n }\n return;\n }\n\n if (command === \"init\") {\n await cmdInit();\n return;\n }\n\n const { ADMIN_COMMANDS } = await import(\"./admin.js\");\n const handler = ADMIN_COMMANDS[command];\n if (!handler) {\n console.error(`Unknown command: ${command}. Run \"grimoire --help\" for usage.`);\n process.exit(1);\n }\n\n await handler();\n}\n\nconst GCP_AUTH_PATTERNS = [\n \"Unable to detect a Project Id\",\n \"Could not load the default credentials\",\n \"invalid_grant\",\n \"invalid_rapt\",\n \"UNAUTHENTICATED\",\n \"Getting metadata from plugin failed\",\n];\n\nmain().catch((err: Error) => {\n const msg = err.message ?? String(err);\n if (GCP_AUTH_PATTERNS.some((p) => msg.includes(p))) {\n console.error(\"Google Cloud authentication failed. Re-authenticate with:\\n\\n gcloud auth application-default login\\n\");\n } else {\n console.error(`Error: ${msg}`);\n }\n process.exit(1);\n});\n", "import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { createInterface } from \"node:readline\";\n\nconst CONFIG_DIR = join(homedir(), \".grimoire\");\nconst CONFIG_FILE = join(CONFIG_DIR, \"config.json\");\n\nexport interface ConsumerConfig {\n apiUrl: string;\n apiKey: string;\n rerankerUrl?: string;\n}\n\nexport async function loadConsumerConfig(): Promise<ConsumerConfig | null> {\n const raw = await readFile(CONFIG_FILE, \"utf-8\").catch(() => null);\n if (!raw) return null;\n const data = JSON.parse(raw);\n if (typeof data.apiUrl === \"string\" && typeof data.apiKey === \"string\") {\n return {\n apiUrl: data.apiUrl,\n apiKey: data.apiKey,\n ...(typeof data.rerankerUrl === \"string\" && data.rerankerUrl\n ? { rerankerUrl: data.rerankerUrl }\n : {}),\n };\n }\n return null;\n}\n\nexport async function saveConsumerConfig(config: ConsumerConfig): Promise<void> {\n await mkdir(CONFIG_DIR, { recursive: true });\n await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// RERANKER_URL env always wins over the config file: the reranker is\n// network-specific, so the shell is the natural per-machine override.\nexport async function resolveConsumerConfig(): Promise<ConsumerConfig> {\n const envUrl = process.env.GRIMOIRE_API_URL;\n const envKey = process.env.GRIMOIRE_API_KEY;\n const envReranker = process.env.RERANKER_URL;\n\n if (envUrl && envKey) {\n return {\n apiUrl: envUrl,\n apiKey: envKey,\n ...(envReranker ? { rerankerUrl: envReranker } : {}),\n };\n }\n\n const fileConfig = await loadConsumerConfig();\n if (fileConfig) {\n return envReranker ? { ...fileConfig, rerankerUrl: envReranker } : fileConfig;\n }\n\n throw new Error(\"Grimoire is not configured. Run 'grimoire init' to set up.\");\n}\n\nexport function isConsumerMode(): boolean {\n return !!process.env.GRIMOIRE_API_URL;\n}\n\nexport async function detectConsumerMode(): Promise<boolean> {\n if (process.env.GOOGLE_APPLICATION_CREDENTIALS) return false;\n if (process.env.GRIMOIRE_API_URL) return true;\n const config = await loadConsumerConfig();\n return config !== null;\n}\n\nasync function setConfigRerankerUrl(url: string | undefined): Promise<void> {\n const existing = await loadConsumerConfig();\n if (!existing) {\n throw new Error(\"Grimoire is not configured. Run 'grimoire init' first.\");\n }\n await saveConsumerConfig({\n apiUrl: existing.apiUrl,\n apiKey: existing.apiKey,\n ...(url ? { rerankerUrl: url } : {}),\n });\n}\n\nexport async function cmdRerank(arg?: string): Promise<void> {\n if (!arg) {\n const envUrl = process.env.RERANKER_URL;\n const fileConfig = await loadConsumerConfig();\n const effective = envUrl ?? fileConfig?.rerankerUrl;\n console.log(`Env RERANKER_URL: ${envUrl ?? \"(not set)\"}`);\n console.log(`Config rerankerUrl: ${fileConfig?.rerankerUrl ?? \"(not set)\"}`);\n console.log(`Effective: ${effective ?? \"off\"}`);\n return;\n }\n\n if (arg === \"off\") {\n await setConfigRerankerUrl(undefined);\n console.log(`Reranker removed from ${CONFIG_FILE}.`);\n if (process.env.RERANKER_URL) {\n console.log(\"Note: RERANKER_URL is set in your environment and still takes precedence. Unset it too.\");\n }\n return;\n }\n\n new URL(arg);\n await setConfigRerankerUrl(arg);\n console.log(`Reranker set to ${arg} in ${CONFIG_FILE}.`);\n}\n\nexport async function cmdInit(): Promise<void> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n const ask = (q: string): Promise<string> =>\n new Promise((resolve) => rl.question(q, resolve));\n\n const existing = await loadConsumerConfig();\n\n const apiUrl = await ask(`API URL${existing ? ` [${existing.apiUrl}]` : \"\"}: `);\n const apiKey = await ask(`API Key${existing ? \" [****]\" : \"\"}: `);\n const rerankerUrl = await ask(\n `Reranker URL (optional, local network)${existing?.rerankerUrl ? ` [${existing.rerankerUrl}]` : \"\"}: `,\n );\n\n const resolvedReranker = rerankerUrl.trim() || existing?.rerankerUrl;\n const config: ConsumerConfig = {\n apiUrl: apiUrl.trim() || existing?.apiUrl || \"\",\n apiKey: apiKey.trim() || existing?.apiKey || \"\",\n ...(resolvedReranker ? { rerankerUrl: resolvedReranker } : {}),\n };\n\n rl.close();\n\n if (!config.apiUrl || !config.apiKey) {\n throw new Error(\"Both API URL and API Key are required.\");\n }\n\n await saveConsumerConfig(config);\n console.log(`\\nSaved to ${CONFIG_FILE}`);\n}\n", "import type { SearchResult, SourceMeta } from \"./types.js\";\nimport type { ConsumerConfig } from \"./consumer-config.js\";\nimport { bold, cyan, yellow } from \"./format.js\";\nimport { rerank, rerankDocText, RERANK_POOL_SIZE } from \"./reranker.js\";\n\n// The API ranks by hybrid fusion server-side; when a reranker is reachable\n// (local network), fetch a larger pool and rerank client-side with the same\n// contextual text the admin pipeline uses.\nfunction contextualText(result: SearchResult): string {\n return rerankDocText(result.title, result.heading_path, result.content);\n}\n\nconst RERANK_TIMEOUT_MS = 30_000;\n\n// Reranking is an enhancement, not a dependency: on failure or timeout the\n// API's fused ranking is already in hand, so degrade to it with a warning.\nasync function rerankResults(\n rerankerUrl: string,\n query: string,\n results: SearchResult[],\n topN: number,\n): Promise<SearchResult[]> {\n try {\n const reranked = await rerank(\n query,\n results.map(contextualText),\n topN,\n rerankerUrl,\n RERANK_TIMEOUT_MS,\n );\n return reranked.map((r) => ({\n ...results[r.index],\n relevance_score: r.relevance_score,\n }));\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(\n `Warning: reranker at ${rerankerUrl} unavailable (${message}); using API ranking. ` +\n \"Use --no-rerank or 'grimoire rerank off' to silence this.\",\n );\n return results.slice(0, topN);\n }\n}\n\nasync function apiRequest<T>(config: ConsumerConfig, path: string, options?: RequestInit): Promise<T> {\n const url = `${config.apiUrl.replace(/\\/$/, \"\")}${path}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": config.apiKey,\n ...options?.headers,\n },\n });\n } catch {\n throw new Error(`Cannot reach Grimoire API at ${config.apiUrl}. Check your GRIMOIRE_API_URL.`);\n }\n\n if (response.status === 401 || response.status === 403) {\n throw new Error(\"Invalid API key. Check your GRIMOIRE_API_KEY or run 'grimoire init'.\");\n }\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${response.statusText}`);\n }\n\n return response.json() as Promise<T>;\n}\n\nexport async function cmdConsumerSearch(\n config: ConsumerConfig,\n query: string,\n options: { source?: string; topN?: number; candidates?: number; compact?: boolean; noRerank?: boolean },\n): Promise<void> {\n const topN = options.topN ?? 5;\n const rerankerUrl = options.noRerank ? undefined : config.rerankerUrl;\n\n const data = await apiRequest<{ results: SearchResult[] }>(config, \"/search\", {\n method: \"POST\",\n body: JSON.stringify({\n query,\n source: options.source,\n topN: rerankerUrl ? Math.max(topN, RERANK_POOL_SIZE) : topN,\n candidates: options.candidates,\n }),\n });\n\n if (data.results.length === 0) {\n console.log(\"No results found.\");\n return;\n }\n\n if (rerankerUrl) {\n data.results = await rerankResults(rerankerUrl, query, data.results, topN);\n }\n\n if (options.compact) {\n for (const r of data.results) {\n console.log(`${r.relevance_score.toFixed(4)} | ${r.source} | ${r.title} | ${r.heading_path.join(\" > \")} | ${r.url}`);\n }\n return;\n }\n\n for (let i = 0; i < data.results.length; i++) {\n const r = data.results[i];\n console.log(`\\n${bold(`[${i + 1}] ${r.title}`)} (${r.relevance_score.toFixed(4)})`);\n console.log(` ${cyan(r.url)}`);\n console.log(` ${yellow(r.heading_path.join(\" > \"))}`);\n console.log(` ${r.content.replace(/\\n/g, \" \")}`);\n }\n}\n\nexport async function cmdConsumerList(config: ConsumerConfig, options?: { names?: boolean }): Promise<void> {\n const data = await apiRequest<{ sources: SourceMeta[] }>(config, \"/list\");\n\n if (data.sources.length === 0) {\n console.log(\"No sources available.\");\n return;\n }\n\n if (options?.names) {\n for (const s of data.sources) {\n console.log(s.source);\n }\n return;\n }\n\n console.log(\"\\nSources:\\n\");\n for (const s of data.sources) {\n const ver = s.version ? ` v${s.version}` : \"\";\n console.log(` ${bold(s.source)}${ver}`);\n console.log(` ${s.chunk_count} chunks, ${s.url_count} URLs, last refreshed ${s.last_refreshed}`);\n }\n}\n\nexport async function cmdConsumerStats(config: ConsumerConfig): Promise<void> {\n const data = await apiRequest<{ sources: SourceMeta[]; totalChunks: number; totalUrls: number }>(config, \"/stats\");\n\n if (data.sources.length === 0) {\n console.log(\"No sources have been refreshed yet.\");\n return;\n }\n\n console.log(\"\\nSource Statistics:\\n\");\n for (const s of data.sources) {\n const ver = s.version ? ` v${s.version}` : \"\";\n console.log(` ${bold(s.source)}${ver}`);\n console.log(` Chunks: ${s.chunk_count}`);\n console.log(` URLs: ${s.url_count}`);\n console.log(` Last refreshed: ${s.last_refreshed}`);\n }\n\n console.log(`\\n Total: ${data.totalChunks} chunks across ${data.totalUrls} URLs from ${data.sources.length} sources`);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["join", "resolve", "join"]
|
|
7
7
|
}
|