@astrofoundry/grimoire 3.31.1 → 3.32.1

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
@@ -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-R46N6C3C.js";
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 { apiUrl: data.apiUrl, apiKey: data.apiKey };
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 { apiUrl: envUrl, apiKey: envKey };
45
+ return {
46
+ apiUrl: envUrl,
47
+ apiKey: envKey,
48
+ ...envReranker ? { rerankerUrl: envReranker } : {}
49
+ };
38
50
  }
39
51
  const fileConfig = await loadConsumerConfig();
40
- if (fileConfig) return 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: options.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 \u2014 Documentation RAG
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,41 @@ 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
- Range 0-1 (higher = better). >0.85 strong match, 0.6-0.85 relevant,
224
- <0.6 usually too weak to cite. "No results found." + exit 0 = clean miss.
225
- Results are ranked by hybrid vector + exact-identifier match; the score
226
- shown is vector similarity, so an exact identifier hit can rank first
227
- with a modest score.
307
+ Range 0-1 (higher = better), always descending with rank.
308
+ "No results found." + exit 0 = clean miss.
309
+ Without a reranker the score is hybrid-fusion strength relative to the
310
+ top result (top result is always 1.0); judge by rank and content, not by
311
+ absolute score. With RERANKER_URL set the score is the local reranker's
312
+ relevance: >0.85 strong match, 0.6-0.85 relevant, <0.6 usually too weak
313
+ to cite.
228
314
 
229
315
  MANAGEMENT
230
316
  grimoire list [--names] Show indexed sources
231
317
  grimoire stats Index statistics
232
318
  grimoire init Configure API connection (first-time setup)
319
+ grimoire rerank Show reranker status (env, config, effective)
320
+ grimoire rerank <url> Enable local reranking (saved to config)
321
+ grimoire rerank off Disable local reranking (removes from config)
233
322
  grimoire update Update grimoire itself
234
323
  grimoire --version Print CLI version
235
324
 
236
325
  ENVIRONMENT
237
326
  GRIMOIRE_API_URL API endpoint URL
238
327
  GRIMOIRE_API_KEY API key
328
+ RERANKER_URL Optional. Local-network reranker endpoint; when
329
+ set, search fetches a 50-result pool and reranks
330
+ client-side. Overrides "rerankerUrl" in
331
+ ~/.grimoire/config.json. If the reranker is
332
+ unreachable, search warns on stderr and falls
333
+ back to the API ranking after a 15s timeout.
239
334
  `);
240
335
  } else {
241
336
  console.log(`
242
- grimoire \u2014 Documentation RAG System (admin)
337
+ grimoire - Documentation RAG System (admin)
243
338
 
244
339
  Commands:
245
340
  add <name> --url <url> Add a new documentation source
@@ -251,6 +346,8 @@ Commands:
251
346
  refresh <source> --full Purge chunks + caches, re-scrape from scratch
252
347
  refresh <source> --skip-store Dry run: scrape/convert/chunk only,
253
348
  no embedding, no Firestore writes
349
+ refresh <source> --allow-shrink Override the shrink/growth guards when a
350
+ large chunk-count change is expected
254
351
  refresh --all Refresh all sources
255
352
  delete <source> Delete source and all its chunks
256
353
  scrape-urls <source> Fetch missing pages from urls.json
@@ -287,6 +384,10 @@ async function main() {
287
384
  await cmdInit();
288
385
  return;
289
386
  }
387
+ if (command === "rerank") {
388
+ await cmdRerank(process.argv[3]);
389
+ return;
390
+ }
290
391
  if (ADMIN_ONLY_COMMANDS.includes(command)) {
291
392
  console.error(`The '${command}' command is only available in admin mode.`);
292
393
  process.exit(1);
@@ -302,18 +403,25 @@ async function main() {
302
403
  source: { type: "string" },
303
404
  top: { type: "string" },
304
405
  candidates: { type: "string" },
305
- compact: { type: "boolean", default: false }
406
+ compact: { type: "boolean", default: false },
407
+ "no-rerank": { type: "boolean", default: false }
306
408
  },
307
409
  allowPositionals: true
308
410
  });
309
411
  const query = args.positionals[0];
310
412
  if (!query) {
311
- console.error('Usage: grimoire search "<query>" [--source <name>] [--top <n>] [--candidates <n>] [--compact]');
413
+ console.error('Usage: grimoire search "<query>" [--source <name>] [--top <n>] [--candidates <n>] [--compact] [--no-rerank]');
312
414
  process.exit(1);
313
415
  }
314
416
  const topN = args.values.top ? parseInt(args.values.top, 10) : void 0;
315
417
  const candidates = args.values.candidates ? parseInt(args.values.candidates, 10) : void 0;
316
- await cmdConsumerSearch(config, query, { source: args.values.source, topN, candidates, compact: args.values.compact });
418
+ await cmdConsumerSearch(config, query, {
419
+ source: args.values.source,
420
+ topN,
421
+ candidates,
422
+ compact: args.values.compact,
423
+ noRerank: args.values["no-rerank"]
424
+ });
317
425
  } else if (command === "list") {
318
426
  const args = parseArgs({
319
427
  args: process.argv.slice(3),
@@ -333,7 +441,7 @@ async function main() {
333
441
  await cmdInit();
334
442
  return;
335
443
  }
336
- const { ADMIN_COMMANDS } = await import("./admin-ENGUPLQ6.js");
444
+ const { ADMIN_COMMANDS } = await import("./admin-MA5SI5CH.js");
337
445
  const handler = ADMIN_COMMANDS[command];
338
446
  if (!handler) {
339
447
  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": "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",
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), always descending with rank.\n \"No results found.\" + exit 0 = clean miss.\n Without a reranker the score is hybrid-fusion strength relative to the\n top result (top result is always 1.0); judge by rank and content, not by\n absolute score. With RERANKER_URL set the score is the local reranker's\n relevance: >0.85 strong match, 0.6-0.85 relevant, <0.6 usually too weak\n to cite.\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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrofoundry/grimoire",
3
- "version": "3.31.1",
3
+ "version": "3.32.1",
4
4
  "description": "Documentation RAG System",
5
5
  "keywords": [],
6
6
  "author": "",