@jcode.labs/ragmir 2.0.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/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/access-log.d.ts +11 -0
- package/dist/access-log.d.ts.map +1 -0
- package/dist/access-log.js +145 -0
- package/dist/access-log.js.map +1 -0
- package/dist/chunking.d.ts +3 -0
- package/dist/chunking.d.ts.map +1 -0
- package/dist/chunking.js +58 -0
- package/dist/chunking.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +944 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +224 -0
- package/dist/config.js.map +1 -0
- package/dist/defaults.d.ts +17 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +59 -0
- package/dist/defaults.js.map +1 -0
- package/dist/destroy.d.ts +3 -0
- package/dist/destroy.d.ts.map +1 -0
- package/dist/destroy.js +16 -0
- package/dist/destroy.js.map +1 -0
- package/dist/doctor.d.ts +3 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +117 -0
- package/dist/doctor.js.map +1 -0
- package/dist/embeddings.d.ts +9 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +110 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/evaluate.d.ts +3 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +97 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/files.d.ts +16 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +388 -0
- package/dist/files.js.map +1 -0
- package/dist/gitignore.d.ts +3 -0
- package/dist/gitignore.d.ts.map +1 -0
- package/dist/gitignore.js +27 -0
- package/dist/gitignore.js.map +1 -0
- package/dist/guards.d.ts +2 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/guards.js +4 -0
- package/dist/guards.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest.d.ts +4 -0
- package/dist/ingest.d.ts.map +1 -0
- package/dist/ingest.js +268 -0
- package/dist/ingest.js.map +1 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +33 -0
- package/dist/init.js.map +1 -0
- package/dist/mcp.d.ts +8 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +189 -0
- package/dist/mcp.js.map +1 -0
- package/dist/package-manager.d.ts +11 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/package-manager.js +93 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/parsing.d.ts +12 -0
- package/dist/parsing.d.ts.map +1 -0
- package/dist/parsing.js +307 -0
- package/dist/parsing.js.map +1 -0
- package/dist/query.d.ts +5 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +180 -0
- package/dist/query.js.map +1 -0
- package/dist/redaction.d.ts +7 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +93 -0
- package/dist/redaction.js.map +1 -0
- package/dist/research.d.ts +9 -0
- package/dist/research.d.ts.map +1 -0
- package/dist/research.js +335 -0
- package/dist/research.js.map +1 -0
- package/dist/security.d.ts +3 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +106 -0
- package/dist/security.js.map +1 -0
- package/dist/semantic-config.d.ts +9 -0
- package/dist/semantic-config.d.ts.map +1 -0
- package/dist/semantic-config.js +35 -0
- package/dist/semantic-config.js.map +1 -0
- package/dist/setup.d.ts +32 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +74 -0
- package/dist/setup.js.map +1 -0
- package/dist/skill.d.ts +66 -0
- package/dist/skill.d.ts.map +1 -0
- package/dist/skill.js +652 -0
- package/dist/skill.js.map +1 -0
- package/dist/sources.d.ts +16 -0
- package/dist/sources.d.ts.map +1 -0
- package/dist/sources.js +71 -0
- package/dist/sources.js.map +1 -0
- package/dist/store.d.ts +13 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +94 -0
- package/dist/store.js.map +1 -0
- package/dist/text.d.ts +3 -0
- package/dist/text.d.ts.map +1 -0
- package/dist/text.js +10 -0
- package/dist/text.js.map +1 -0
- package/dist/types.d.ts +322 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/examples/library-api-demo/README.md +42 -0
- package/examples/library-api-demo/run.mjs +61 -0
- package/examples/sovereign-rag-demo/.ragmir/config.json +22 -0
- package/examples/sovereign-rag-demo/.ragmir/sources.txt +2 -0
- package/examples/sovereign-rag-demo/README.md +95 -0
- package/examples/sovereign-rag-demo/golden-queries.json +29 -0
- package/examples/sovereign-rag-demo/raw/dataset-inventory.csv +5 -0
- package/examples/sovereign-rag-demo/raw/facility-scan.heic +3 -0
- package/examples/sovereign-rag-demo/raw/incident-timeline.jsonl +4 -0
- package/examples/sovereign-rag-demo/raw/operations-brief.md +16 -0
- package/examples/sovereign-rag-demo/raw/review-notes.evidence +11 -0
- package/examples/sovereign-rag-demo/raw/security-policy.yaml +14 -0
- package/package.json +87 -0
- package/skills/ragmir/SKILL.md +366 -0
- package/skills/ragmir-audio-summary/SKILL.md +150 -0
- package/skills/ragmir-audio-summary/forge-voice.sh +150 -0
- package/skills/ragmir-audio-summary/split-lines.py +13 -0
- package/skills/ragmir-audio-summary/xtts-voice.py +46 -0
- package/skills/ragmir-legal-dossier/SKILL.md +143 -0
- package/skills/ragmir-markdown-report/SKILL.md +110 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isTtsLanguage, TTS_LANGUAGES } from "@jcode.labs/ragmir-tts";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
import { accessLogUsageReport } from "./access-log.js";
|
|
7
|
+
import { loadConfig } from "./config.js";
|
|
8
|
+
import { DEFAULT_SKILL_TARGET_DIR } from "./defaults.js";
|
|
9
|
+
import { destroyIndex } from "./destroy.js";
|
|
10
|
+
import { doctor } from "./doctor.js";
|
|
11
|
+
import { pullEmbeddingModel } from "./embeddings.js";
|
|
12
|
+
import { evaluateGoldenQueries } from "./evaluate.js";
|
|
13
|
+
import { countSkippedByReason } from "./files.js";
|
|
14
|
+
import { audit, ingest } from "./ingest.js";
|
|
15
|
+
import { initProject } from "./init.js";
|
|
16
|
+
import { serveMcp } from "./mcp.js";
|
|
17
|
+
import { ragmirCommand } from "./package-manager.js";
|
|
18
|
+
import { ask, search } from "./query.js";
|
|
19
|
+
import { compactResearchReport, compactSearchResults, research } from "./research.js";
|
|
20
|
+
import { securityAudit } from "./security.js";
|
|
21
|
+
import { enableSemanticEmbeddings } from "./semantic-config.js";
|
|
22
|
+
import { setupProject } from "./setup.js";
|
|
23
|
+
import { bundledSkillPath, installAgentSkills, installSkill, parseAgentTargets, SUPPORTED_AGENT_TARGETS, } from "./skill.js";
|
|
24
|
+
import { addSourceEntries, listSourceEntries } from "./sources.js";
|
|
25
|
+
import { countRows } from "./store.js";
|
|
26
|
+
import { VERSION } from "./version.js";
|
|
27
|
+
const SEARCH_TEXT_PREVIEW_LENGTH = 900;
|
|
28
|
+
const TTS_PACKAGE_NAME = "@jcode.labs/ragmir-tts";
|
|
29
|
+
const program = new Command();
|
|
30
|
+
program
|
|
31
|
+
.name("ragmir")
|
|
32
|
+
.description("Local-first RAG knowledge base for private project documents.")
|
|
33
|
+
.version(VERSION)
|
|
34
|
+
.option("--project-root <path>", "Run project-scoped commands against this local workspace.");
|
|
35
|
+
const modelsCommand = program.command("models").description("Manage local embedding models.");
|
|
36
|
+
modelsCommand
|
|
37
|
+
.command("pull")
|
|
38
|
+
.description("Download the configured Transformers.js embedding model into embeddingModelPath.")
|
|
39
|
+
.option("--enable", "Switch Ragmir config to Transformers embeddings after the model is ready.")
|
|
40
|
+
.option("--json", "Print machine-readable JSON.")
|
|
41
|
+
.action(async (options, command) => {
|
|
42
|
+
const cwd = projectRoot(command);
|
|
43
|
+
const config = await loadConfig(cwd);
|
|
44
|
+
const result = await pullEmbeddingModel(config);
|
|
45
|
+
const semanticConfig = options.enable ? await enableSemanticEmbeddings(cwd) : null;
|
|
46
|
+
if (options.json) {
|
|
47
|
+
console.log(JSON.stringify(semanticConfig ? { ...result, semanticConfig } : result, null, 2));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
console.log(pc.green("Embedding model ready."));
|
|
51
|
+
console.log(`embeddingModel=${result.embeddingModel}`);
|
|
52
|
+
console.log(`embeddingModelPath=${result.embeddingModelPath}`);
|
|
53
|
+
if (semanticConfig) {
|
|
54
|
+
console.log(`semanticConfig=${semanticConfig.configPath}`);
|
|
55
|
+
console.log(`embeddingProvider=${semanticConfig.embeddingProvider}`);
|
|
56
|
+
console.log(`transformersAllowRemoteModels=${semanticConfig.transformersAllowRemoteModels}`);
|
|
57
|
+
}
|
|
58
|
+
console.log("");
|
|
59
|
+
console.log("Next steps:");
|
|
60
|
+
if (semanticConfig) {
|
|
61
|
+
console.log(" 1. Run `ragmir ingest --rebuild` so existing vectors use the semantic model.");
|
|
62
|
+
console.log(" 2. Run `ragmir doctor` to confirm readiness.");
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log(" 1. Re-run `ragmir models pull --enable` to switch Ragmir config safely.");
|
|
66
|
+
console.log(" 2. Run `ragmir ingest --rebuild` so existing vectors use the semantic model.");
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
program
|
|
70
|
+
.command("doctor")
|
|
71
|
+
.description("Diagnose setup, index freshness, privacy posture, and next steps.")
|
|
72
|
+
.option("--fix", "Create missing scaffolding, install the agent kit, and rebuild stale indexes.")
|
|
73
|
+
.option("--json", "Print machine-readable JSON.")
|
|
74
|
+
.action(async (options, command) => {
|
|
75
|
+
const cwd = projectRoot(command);
|
|
76
|
+
if (options.fix) {
|
|
77
|
+
const result = await setupProject({ cwd });
|
|
78
|
+
if (options.json) {
|
|
79
|
+
console.log(JSON.stringify(result, null, 2));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
printSetup(result, "Ragmir repair complete.");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const report = await doctor(cwd);
|
|
86
|
+
if (options.json) {
|
|
87
|
+
console.log(JSON.stringify(report, null, 2));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
printDoctor(report);
|
|
91
|
+
});
|
|
92
|
+
program
|
|
93
|
+
.command("setup")
|
|
94
|
+
.description("Initialize Ragmir, install the agent kit, run doctor, and ingest when safe.")
|
|
95
|
+
.option("--target-dir <path>", "Directory where the skill folder should be copied.", DEFAULT_SKILL_TARGET_DIR)
|
|
96
|
+
.option("--agents <list>", `Agent MCP helpers to generate: all, ${SUPPORTED_AGENT_TARGETS.join(", ")}.`, "all")
|
|
97
|
+
.option("--mcp-name <name>", "MCP server name used in generated config.", "ragmir")
|
|
98
|
+
.option("--mcp-command <command>", "Custom MCP stdio command for generated helper files.")
|
|
99
|
+
.option("--mcp-arg <arg>", "Argument for --mcp-command. Repeat for multiple arguments.", collectOptionValue, [])
|
|
100
|
+
.option("--semantic", "Download the configured Transformers.js embedding model and enable higher-quality semantic retrieval.")
|
|
101
|
+
.option("--no-ingest", "Skip automatic indexing even when supported files are present.")
|
|
102
|
+
.option("--json", "Print machine-readable JSON.")
|
|
103
|
+
.action(async (options, command) => {
|
|
104
|
+
const cwd = projectRoot(command);
|
|
105
|
+
const setupOptions = {
|
|
106
|
+
cwd,
|
|
107
|
+
targetDir: options.targetDir,
|
|
108
|
+
agents: parseAgentTargets(options.agents),
|
|
109
|
+
mcpServerName: options.mcpName,
|
|
110
|
+
};
|
|
111
|
+
addOption(setupOptions, "semantic", options.semantic);
|
|
112
|
+
addOption(setupOptions, "ingest", options.ingest);
|
|
113
|
+
addOption(setupOptions, "mcpCommand", options.mcpCommand);
|
|
114
|
+
if (options.mcpArg.length > 0) {
|
|
115
|
+
setupOptions.mcpArgs = options.mcpArg;
|
|
116
|
+
}
|
|
117
|
+
const result = await setupProject(setupOptions);
|
|
118
|
+
if (options.json) {
|
|
119
|
+
console.log(JSON.stringify(result, null, 2));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
printSetup(result, "Ragmir setup complete.");
|
|
123
|
+
});
|
|
124
|
+
program
|
|
125
|
+
.command("init")
|
|
126
|
+
.description("Create local .ragmir config, raw-document folder, and gitignore rules.")
|
|
127
|
+
.action(async (_options, command) => {
|
|
128
|
+
const cwd = projectRoot(command);
|
|
129
|
+
const created = await initProject(cwd);
|
|
130
|
+
if (created.length === 0) {
|
|
131
|
+
console.log(pc.green("Already initialized."));
|
|
132
|
+
const doctorCommand = await ragmirCommand(cwd, ["doctor"]);
|
|
133
|
+
console.log(`Run \`${doctorCommand.display}\` to check readiness.`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
console.log(pc.green("Created:"));
|
|
137
|
+
for (const file of created) {
|
|
138
|
+
console.log(` - ${file}`);
|
|
139
|
+
}
|
|
140
|
+
const ingestCommand = await ragmirCommand(cwd, ["ingest"]);
|
|
141
|
+
const doctorCommand = await ragmirCommand(cwd, ["doctor"]);
|
|
142
|
+
const searchCommand = await ragmirCommand(cwd, ["search", "your question"]);
|
|
143
|
+
console.log("");
|
|
144
|
+
console.log(pc.cyan("Next steps:"));
|
|
145
|
+
console.log(" 1. Add supported documents under .ragmir/raw/");
|
|
146
|
+
console.log(` 2. Run \`${ingestCommand.display}\``);
|
|
147
|
+
console.log(` 3. Run \`${doctorCommand.display}\``);
|
|
148
|
+
console.log(` 4. Query with \`${searchCommand.display}\``);
|
|
149
|
+
});
|
|
150
|
+
const sourcesCommand = program
|
|
151
|
+
.command("sources")
|
|
152
|
+
.description("Manage extra source paths and glob patterns in .ragmir/sources.txt.");
|
|
153
|
+
sourcesCommand
|
|
154
|
+
.command("list")
|
|
155
|
+
.description("List extra source paths and glob patterns.")
|
|
156
|
+
.option("--json", "Print machine-readable JSON.")
|
|
157
|
+
.action(async (options, command) => {
|
|
158
|
+
const cwd = projectRoot(command);
|
|
159
|
+
const result = await listSourceEntries(cwd);
|
|
160
|
+
if (options.json) {
|
|
161
|
+
console.log(JSON.stringify(result, null, 2));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
console.log(`sourcesFile=${path.relative(cwd, result.sourcesFile) || result.sourcesFile}`);
|
|
165
|
+
if (result.entries.length === 0) {
|
|
166
|
+
console.log("No extra source entries.");
|
|
167
|
+
console.log('Add one with `ragmir sources add "../apps/*/docs/**/*.md"`.');
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
for (const entry of result.entries) {
|
|
171
|
+
console.log(` - ${entry}`);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
sourcesCommand
|
|
175
|
+
.command("add")
|
|
176
|
+
.description("Add extra source paths or glob patterns.")
|
|
177
|
+
.argument("<entries...>", "Source paths, glob patterns, or ! exclusion patterns.")
|
|
178
|
+
.option("--json", "Print machine-readable JSON.")
|
|
179
|
+
.action(async (entries, options, command) => {
|
|
180
|
+
const cwd = projectRoot(command);
|
|
181
|
+
const result = await addSourceEntries({ cwd, entries });
|
|
182
|
+
if (options.json) {
|
|
183
|
+
console.log(JSON.stringify(result, null, 2));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
console.log(`sourcesFile=${path.relative(cwd, result.sourcesFile) || result.sourcesFile}`);
|
|
187
|
+
for (const entry of result.added) {
|
|
188
|
+
console.log(pc.green(`added ${entry}`));
|
|
189
|
+
}
|
|
190
|
+
for (const entry of result.skipped) {
|
|
191
|
+
console.log(pc.dim(`skipped existing ${entry}`));
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
program
|
|
195
|
+
.command("ingest")
|
|
196
|
+
.description("Parse changed documents, redact, chunk, embed locally, and update LanceDB.")
|
|
197
|
+
.option("--rebuild", "Force a full local index rebuild instead of reusing unchanged rows.")
|
|
198
|
+
.option("--json", "Print machine-readable JSON.")
|
|
199
|
+
.action(async (options, command) => {
|
|
200
|
+
const cwd = projectRoot(command);
|
|
201
|
+
const ingestOptions = { cwd };
|
|
202
|
+
addOption(ingestOptions, "rebuild", options.rebuild);
|
|
203
|
+
const result = await ingest(ingestOptions);
|
|
204
|
+
if (options.json) {
|
|
205
|
+
console.log(JSON.stringify(result, null, 2));
|
|
206
|
+
if (result.errors.length > 0) {
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
}
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
console.log(pc.green(`Done. discoveredFiles=${result.discoveredFiles} supportedFiles=${result.supportedFiles} indexedFiles=${result.indexedFiles} rebuiltFiles=${result.rebuiltFiles} reusedFiles=${result.reusedFiles} chunks=${result.chunks} skippedFiles=${result.skippedFiles} unsupportedFiles=${result.unsupportedFiles} oversizedFiles=${result.oversizedFiles} sensitiveFiles=${result.sensitiveFiles} emptyTextFiles=${result.emptyTextFiles.length} redactions=${result.redactions} errors=${result.errors.length}`));
|
|
212
|
+
printUnsupportedSummary(result.unsupportedExtensions);
|
|
213
|
+
printEmptyTextFiles(result.emptyTextFiles);
|
|
214
|
+
if (result.unsupportedFiles > 0 || result.oversizedFiles > 0 || result.sensitiveFiles > 0) {
|
|
215
|
+
const auditCommand = await ragmirCommand(cwd, ["audit", "--unsupported"]);
|
|
216
|
+
console.log(pc.yellow(`Some files were not indexed. Run \`${auditCommand.display}\` for details.`));
|
|
217
|
+
}
|
|
218
|
+
for (const error of result.errors) {
|
|
219
|
+
console.error(pc.red(` - ${error.path}: ${error.message}`));
|
|
220
|
+
}
|
|
221
|
+
if (result.errors.length > 0) {
|
|
222
|
+
process.exitCode = 1;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
program
|
|
226
|
+
.command("search")
|
|
227
|
+
.description("Retrieve the most relevant passages without calling an LLM.")
|
|
228
|
+
.argument("<query>", "Search query.")
|
|
229
|
+
.option("-k, --top-k <number>", "Number of passages to return.", parsePositiveInt)
|
|
230
|
+
.option("--compact", "Return short snippets instead of full passages.")
|
|
231
|
+
.option("--json", "Print machine-readable JSON.")
|
|
232
|
+
.action(async (query, options, command) => {
|
|
233
|
+
const cwd = projectRoot(command);
|
|
234
|
+
const results = await search(query, withTopK(cwd, options.topK));
|
|
235
|
+
const outputResults = options.compact ? compactSearchResults(results) : results;
|
|
236
|
+
if (options.json) {
|
|
237
|
+
console.log(JSON.stringify({ query, results: outputResults }, null, 2));
|
|
238
|
+
if (results.length === 0) {
|
|
239
|
+
process.exitCode = 1;
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (results.length === 0) {
|
|
244
|
+
const repairCommand = await ragmirCommand(cwd, ["doctor", "--fix"]);
|
|
245
|
+
console.error(pc.yellow(`No results. Add documents or run \`${repairCommand.display}\`.`));
|
|
246
|
+
process.exitCode = 1;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
for (const [index, result] of outputResults.entries()) {
|
|
250
|
+
const distance = result.distance === null ? "n/a" : result.distance.toFixed(4);
|
|
251
|
+
console.log(`\n${pc.cyan(`[${index + 1}] ${result.relativePath}`)} chunk=${result.chunkIndex} distance=${distance}`);
|
|
252
|
+
console.log("snippet" in result ? result.snippet : result.text.slice(0, SEARCH_TEXT_PREVIEW_LENGTH));
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
program
|
|
256
|
+
.command("ask")
|
|
257
|
+
.description("Return cited retrieval context for a question without calling an LLM.")
|
|
258
|
+
.argument("<query>", "Question to answer.")
|
|
259
|
+
.option("-k, --top-k <number>", "Number of passages to use.", parsePositiveInt)
|
|
260
|
+
.option("--json", "Print machine-readable JSON.")
|
|
261
|
+
.action(async (query, options, command) => {
|
|
262
|
+
const cwd = projectRoot(command);
|
|
263
|
+
const result = await ask(query, withTopK(cwd, options.topK));
|
|
264
|
+
if (options.json) {
|
|
265
|
+
console.log(JSON.stringify({ query, ...result }, null, 2));
|
|
266
|
+
if (result.sources.length === 0) {
|
|
267
|
+
process.exitCode = 1;
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
console.log(`\n${result.answer}\n`);
|
|
272
|
+
if (result.sources.length > 0) {
|
|
273
|
+
console.log(pc.dim("Sources:"));
|
|
274
|
+
for (const [index, source] of result.sources.entries()) {
|
|
275
|
+
console.log(` [${index + 1}] ${source.relativePath} chunk=${source.chunkIndex}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
program
|
|
280
|
+
.command("research")
|
|
281
|
+
.description("Run an audit-backed multi-query research pass with cited evidence.")
|
|
282
|
+
.argument("<query>", "Research question or topic.")
|
|
283
|
+
.option("-k, --top-k <number>", "Maximum number of evidence passages to keep.", parsePositiveInt)
|
|
284
|
+
.option("--no-code", "Skip the lightweight repository code search.")
|
|
285
|
+
.option("--compact", "Return snippets instead of full retrieved passages.")
|
|
286
|
+
.option("--json", "Print machine-readable JSON.")
|
|
287
|
+
.action(async (query, options, command) => {
|
|
288
|
+
const cwd = projectRoot(command);
|
|
289
|
+
const researchOptions = { cwd };
|
|
290
|
+
addOption(researchOptions, "topK", options.topK);
|
|
291
|
+
addOption(researchOptions, "includeCode", options.code);
|
|
292
|
+
const report = await research(query, researchOptions);
|
|
293
|
+
const output = options.compact ? compactResearchReport(report) : report;
|
|
294
|
+
if (options.json) {
|
|
295
|
+
console.log(JSON.stringify(output, null, 2));
|
|
296
|
+
if (!report.ready) {
|
|
297
|
+
process.exitCode = 1;
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
printResearchReport(output);
|
|
302
|
+
if (!report.ready) {
|
|
303
|
+
process.exitCode = 1;
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
program
|
|
307
|
+
.command("evaluate")
|
|
308
|
+
.description("Measure retrieval recall against a JSON golden query file.")
|
|
309
|
+
.requiredOption("--golden <path>", "JSON file with queries and expected relative source paths.")
|
|
310
|
+
.option("-k, --top-k <number>", "Default number of passages to evaluate per query.", parsePositiveInt)
|
|
311
|
+
.option("--fail-under <recall>", "Exit non-zero only when recall is below this threshold from 0 to 1.", parseRecallThreshold)
|
|
312
|
+
.option("--json", "Print machine-readable JSON.")
|
|
313
|
+
.action(async (options, command) => {
|
|
314
|
+
const cwd = projectRoot(command);
|
|
315
|
+
const evaluationOptions = {
|
|
316
|
+
cwd,
|
|
317
|
+
goldenPath: options.golden,
|
|
318
|
+
};
|
|
319
|
+
addOption(evaluationOptions, "topK", options.topK);
|
|
320
|
+
const result = await evaluateGoldenQueries(evaluationOptions);
|
|
321
|
+
const minimumRecall = options.failUnder ?? 1;
|
|
322
|
+
const passed = result.recall >= minimumRecall;
|
|
323
|
+
if (options.json) {
|
|
324
|
+
const payload = options.failUnder === undefined ? result : { ...result, minimumRecall, passed };
|
|
325
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
326
|
+
if (!passed) {
|
|
327
|
+
process.exitCode = 1;
|
|
328
|
+
}
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const thresholdSummary = options.failUnder === undefined
|
|
332
|
+
? ""
|
|
333
|
+
: ` minimumRecall=${minimumRecall.toFixed(3)} passed=${passed}`;
|
|
334
|
+
console.log(`golden=${result.goldenPath} total=${result.total} hits=${result.hits} misses=${result.misses} recall=${result.recall.toFixed(3)}${thresholdSummary}`);
|
|
335
|
+
for (const testCase of result.cases) {
|
|
336
|
+
const label = testCase.id ? `${testCase.id}: ${testCase.query}` : testCase.query;
|
|
337
|
+
const status = testCase.hit ? pc.green("hit") : pc.red("miss");
|
|
338
|
+
const rank = testCase.bestRank === null ? "n/a" : String(testCase.bestRank);
|
|
339
|
+
console.log(`${status} rank=${rank} topK=${testCase.topK} ${label}`);
|
|
340
|
+
if (!testCase.hit) {
|
|
341
|
+
console.log(` expected=${testCase.expectedPaths.join(",")}`);
|
|
342
|
+
console.log(` returned=${testCase.returnedPaths.join(",")}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
if (!passed) {
|
|
346
|
+
process.exitCode = 1;
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
program
|
|
350
|
+
.command("audit")
|
|
351
|
+
.description("Compare supported files on disk with the current vector index.")
|
|
352
|
+
.option("--unsupported", "List skipped file paths and reasons.")
|
|
353
|
+
.option("--json", "Print machine-readable JSON.")
|
|
354
|
+
.action(async (options, command) => {
|
|
355
|
+
const cwd = projectRoot(command);
|
|
356
|
+
const report = await audit(cwd);
|
|
357
|
+
if (options.json) {
|
|
358
|
+
console.log(JSON.stringify(report, null, 2));
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
console.log(`supportedFiles=${report.supportedFiles.length}`);
|
|
362
|
+
console.log(`skippedFiles=${report.skippedFiles.length}`);
|
|
363
|
+
console.log(`unsupportedFiles=${countSkippedByReason(report.skippedFiles, "unsupported-extension")}`);
|
|
364
|
+
console.log(`indexedFiles=${report.indexedFiles.length}`);
|
|
365
|
+
console.log(`totalChunks=${report.totalChunks}`);
|
|
366
|
+
console.log(`emptyTextFiles=${report.emptyTextFiles.length}`);
|
|
367
|
+
console.log(`missingFromIndex=${report.missingFromIndex.length}`);
|
|
368
|
+
console.log(`staleInIndex=${report.staleInIndex.length}`);
|
|
369
|
+
console.log(`duplicateCandidates=${report.sourceDiagnostics.duplicateCandidates.length}`);
|
|
370
|
+
console.log(`archiveCandidates=${report.sourceDiagnostics.archiveCandidates.length}`);
|
|
371
|
+
console.log(`mirrorCandidates=${report.sourceDiagnostics.mirrorCandidates.length}`);
|
|
372
|
+
printUnsupportedSummary(report.unsupportedExtensions);
|
|
373
|
+
for (const file of report.missingFromIndex) {
|
|
374
|
+
console.log(pc.yellow(`missing: ${file}`));
|
|
375
|
+
}
|
|
376
|
+
for (const file of report.staleInIndex) {
|
|
377
|
+
console.log(pc.red(`stale: ${file}`));
|
|
378
|
+
}
|
|
379
|
+
if (options.unsupported) {
|
|
380
|
+
for (const file of report.skippedFiles) {
|
|
381
|
+
console.log(pc.yellow(`skipped: ${file.relativePath} reason=${file.reason} recommendation=${file.recommendation}`));
|
|
382
|
+
}
|
|
383
|
+
for (const candidate of report.sourceDiagnostics.duplicateCandidates) {
|
|
384
|
+
console.log(pc.yellow(`duplicate-candidate: ${candidate.key} files=${candidate.files.join(",")}`));
|
|
385
|
+
}
|
|
386
|
+
for (const candidate of report.sourceDiagnostics.archiveCandidates) {
|
|
387
|
+
console.log(pc.yellow(`archive-candidate: ${candidate.relativePath} reason=${candidate.reason}`));
|
|
388
|
+
}
|
|
389
|
+
for (const candidate of report.sourceDiagnostics.mirrorCandidates) {
|
|
390
|
+
console.log(pc.yellow(`mirror-candidate: ${candidate.relativePath} reason=${candidate.reason}`));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else if (report.skippedFiles.length > 0) {
|
|
394
|
+
console.log(pc.yellow("Run `ragmir audit --unsupported` to list skipped file paths."));
|
|
395
|
+
}
|
|
396
|
+
if (report.missingFromIndex.length > 0 || report.staleInIndex.length > 0) {
|
|
397
|
+
process.exitCode = 1;
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
program
|
|
401
|
+
.command("usage-report")
|
|
402
|
+
.description("Summarize the metadata-only local access log.")
|
|
403
|
+
.option("--days <number>", "Number of recent days to include.", parsePositiveInt, 7)
|
|
404
|
+
.option("--json", "Print machine-readable JSON.")
|
|
405
|
+
.action(async (options, command) => {
|
|
406
|
+
const cwd = projectRoot(command);
|
|
407
|
+
const report = await accessLogUsageReport({ cwd, days: options.days });
|
|
408
|
+
if (options.json) {
|
|
409
|
+
console.log(JSON.stringify(report, null, 2));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
console.log(`accessLogEnabled=${report.accessLogEnabled}`);
|
|
413
|
+
console.log(`since=${report.since}`);
|
|
414
|
+
console.log(`until=${report.until}`);
|
|
415
|
+
console.log(`totalEvents=${report.totalEvents}`);
|
|
416
|
+
console.log(`invalidLines=${report.invalidLines}`);
|
|
417
|
+
console.log(`uniqueQueryHashes=${report.uniqueQueryHashes}`);
|
|
418
|
+
console.log(`averageResultCount=${report.averageResultCount ?? "n/a"}`);
|
|
419
|
+
console.log(`lastEventAt=${report.lastEventAt ?? "n/a"}`);
|
|
420
|
+
for (const [action, count] of Object.entries(report.eventsByAction)) {
|
|
421
|
+
console.log(`events.${action}=${count}`);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
program
|
|
425
|
+
.command("status")
|
|
426
|
+
.description("Show active configuration and index row count.")
|
|
427
|
+
.option("--json", "Print machine-readable JSON.")
|
|
428
|
+
.action(async (options, command) => {
|
|
429
|
+
const cwd = projectRoot(command);
|
|
430
|
+
const config = await loadConfig(cwd);
|
|
431
|
+
const rows = await countRows(config);
|
|
432
|
+
const status = {
|
|
433
|
+
projectRoot: config.projectRoot,
|
|
434
|
+
rawDir: config.rawDir,
|
|
435
|
+
storageDir: config.storageDir,
|
|
436
|
+
sourcesFile: config.sourcesFile,
|
|
437
|
+
accessLogPath: config.accessLogPath,
|
|
438
|
+
embeddingModelPath: config.embeddingModelPath,
|
|
439
|
+
embeddingProvider: config.embeddingProvider,
|
|
440
|
+
embeddingModel: config.embeddingModel,
|
|
441
|
+
transformersAllowRemoteModels: config.transformersAllowRemoteModels,
|
|
442
|
+
redactionEnabled: config.redaction.enabled,
|
|
443
|
+
accessLog: config.accessLog,
|
|
444
|
+
mcpMaxTopK: config.mcpMaxTopK,
|
|
445
|
+
topK: config.topK,
|
|
446
|
+
chunkSize: config.chunkSize,
|
|
447
|
+
chunkOverlap: config.chunkOverlap,
|
|
448
|
+
maxFileBytes: config.maxFileBytes,
|
|
449
|
+
ingestConcurrency: config.ingestConcurrency,
|
|
450
|
+
embeddingBatchSize: config.embeddingBatchSize,
|
|
451
|
+
includeExtensions: config.includeExtensions,
|
|
452
|
+
pdfOcrCommand: config.pdfOcrCommand,
|
|
453
|
+
pdfOcrTimeoutMs: config.pdfOcrTimeoutMs,
|
|
454
|
+
imageOcrCommand: config.imageOcrCommand,
|
|
455
|
+
imageOcrTimeoutMs: config.imageOcrTimeoutMs,
|
|
456
|
+
legacyWordCommand: config.legacyWordCommand,
|
|
457
|
+
legacyWordTimeoutMs: config.legacyWordTimeoutMs,
|
|
458
|
+
chunksIndexed: rows,
|
|
459
|
+
};
|
|
460
|
+
if (options.json) {
|
|
461
|
+
console.log(JSON.stringify(status, null, 2));
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
console.log(`projectRoot=${config.projectRoot}`);
|
|
465
|
+
console.log(`rawDir=${config.rawDir}`);
|
|
466
|
+
console.log(`storageDir=${config.storageDir}`);
|
|
467
|
+
console.log(`sourcesFile=${config.sourcesFile}`);
|
|
468
|
+
console.log(`accessLogPath=${config.accessLogPath}`);
|
|
469
|
+
console.log(`embeddingModelPath=${config.embeddingModelPath}`);
|
|
470
|
+
console.log(`embeddingProvider=${config.embeddingProvider}`);
|
|
471
|
+
console.log(`embeddingModel=${config.embeddingModel}`);
|
|
472
|
+
console.log(`transformersAllowRemoteModels=${config.transformersAllowRemoteModels}`);
|
|
473
|
+
console.log(`redactionEnabled=${config.redaction.enabled}`);
|
|
474
|
+
console.log(`accessLog=${config.accessLog}`);
|
|
475
|
+
console.log(`mcpMaxTopK=${config.mcpMaxTopK}`);
|
|
476
|
+
console.log(`topK=${config.topK}`);
|
|
477
|
+
console.log(`chunkSize=${config.chunkSize}`);
|
|
478
|
+
console.log(`chunkOverlap=${config.chunkOverlap}`);
|
|
479
|
+
console.log(`maxFileBytes=${config.maxFileBytes}`);
|
|
480
|
+
console.log(`ingestConcurrency=${config.ingestConcurrency}`);
|
|
481
|
+
console.log(`embeddingBatchSize=${config.embeddingBatchSize}`);
|
|
482
|
+
console.log(`includeExtensions=${config.includeExtensions.join(",")}`);
|
|
483
|
+
console.log(`pdfOcrCommand=${config.pdfOcrCommand.join(" ")}`);
|
|
484
|
+
console.log(`pdfOcrTimeoutMs=${config.pdfOcrTimeoutMs}`);
|
|
485
|
+
console.log(`imageOcrCommand=${config.imageOcrCommand.join(" ")}`);
|
|
486
|
+
console.log(`imageOcrTimeoutMs=${config.imageOcrTimeoutMs}`);
|
|
487
|
+
console.log(`legacyWordCommand=${config.legacyWordCommand.join(" ")}`);
|
|
488
|
+
console.log(`legacyWordTimeoutMs=${config.legacyWordTimeoutMs}`);
|
|
489
|
+
console.log(`chunksIndexed=${rows}`);
|
|
490
|
+
});
|
|
491
|
+
program
|
|
492
|
+
.command("security-audit")
|
|
493
|
+
.description("Show local privacy, provider, redaction, MCP, and gitignore posture.")
|
|
494
|
+
.option("--json", "Print machine-readable JSON.")
|
|
495
|
+
.option("--strict", "Exit with code 1 when warnings are present.")
|
|
496
|
+
.action(async (options, command) => {
|
|
497
|
+
const cwd = projectRoot(command);
|
|
498
|
+
const report = await securityAudit(cwd);
|
|
499
|
+
if (options.json) {
|
|
500
|
+
console.log(JSON.stringify(report, null, 2));
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
console.log(`zeroTelemetry=${report.zeroTelemetry}`);
|
|
504
|
+
console.log(`embeddingProvider=${report.providers.embedding}`);
|
|
505
|
+
console.log(`embeddingModel=${report.providers.embeddingModel}`);
|
|
506
|
+
console.log(`embeddingModelPath=${report.providers.embeddingModelPath}`);
|
|
507
|
+
console.log(`transformersAllowRemoteModels=${report.providers.transformersAllowRemoteModels}`);
|
|
508
|
+
console.log(`llmGeneration=${report.providers.llmGeneration}`);
|
|
509
|
+
console.log(`redactionEnabled=${report.redaction.enabled}`);
|
|
510
|
+
console.log(`redactionBuiltIn=${report.redaction.builtIn}`);
|
|
511
|
+
console.log(`accessLog=${report.accessLog.enabled}`);
|
|
512
|
+
console.log(`accessLogStoresRawQueries=${report.accessLog.storesRawQueries}`);
|
|
513
|
+
console.log(`storageGitIgnored=${report.storage.gitIgnored}`);
|
|
514
|
+
console.log(`mcpMaxTopK=${report.mcp.maxTopK}`);
|
|
515
|
+
console.log(`mcpDestructiveToolsExposed=${report.mcp.destructiveToolsExposed}`);
|
|
516
|
+
for (const warning of report.warnings) {
|
|
517
|
+
console.log(pc.yellow(`warning: ${warning}`));
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (options.strict && report.warnings.length > 0) {
|
|
521
|
+
process.exitCode = 1;
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
program
|
|
525
|
+
.command("destroy-index")
|
|
526
|
+
.description("Remove the generated local vector index from Ragmir storage.")
|
|
527
|
+
.option("--yes", "Confirm deletion without an interactive prompt.")
|
|
528
|
+
.action(async (options, command) => {
|
|
529
|
+
const cwd = projectRoot(command);
|
|
530
|
+
if (!options.yes) {
|
|
531
|
+
console.error(pc.red("Refusing to delete the index without --yes."));
|
|
532
|
+
process.exitCode = 1;
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const result = await destroyIndex(cwd);
|
|
536
|
+
console.log(`storageDir=${result.storageDir}`);
|
|
537
|
+
console.log(`removed=${result.removed}`);
|
|
538
|
+
console.log(result.note);
|
|
539
|
+
});
|
|
540
|
+
program
|
|
541
|
+
.command("audio")
|
|
542
|
+
.description("Render a narration text file to local speech audio with Ragmir TTS.")
|
|
543
|
+
.argument("[text-file]", "Narration text file to render.")
|
|
544
|
+
.option("-o, --out <path>", "Output MP3 or WAV path.")
|
|
545
|
+
.option("--engine <engine>", "TTS engine: auto, edge, or transformers.")
|
|
546
|
+
.option("--lang <language>", "TTS language: en, es, or fr. Selects the model and Edge voice.")
|
|
547
|
+
.option("--model <id>", "Transformers.js TTS model ID.")
|
|
548
|
+
.option("--model-path <path>", "Local model/cache path.")
|
|
549
|
+
.option("--offline", "Force the Transformers.js local/offline WAV path.")
|
|
550
|
+
.option("--allow-remote-models", "Explicitly allow remote model downloads.")
|
|
551
|
+
.option("--voice <voice>", "Edge voice. Defaults to fr-FR-DeniseNeural.")
|
|
552
|
+
.option("--rate <rate>", "Edge rate. Defaults to +0%.")
|
|
553
|
+
.option("--speaker-embeddings <path>", "Optional model-specific speaker embedding path or URL.")
|
|
554
|
+
.option("--speed <number>", "Optional model-specific speech speed.", parseNumber)
|
|
555
|
+
.option("--doctor", "Show TTS runtime readiness instead of rendering.")
|
|
556
|
+
.option("--json", "Print machine-readable JSON.")
|
|
557
|
+
.action(async (textFile, options, command) => {
|
|
558
|
+
const cwd = projectRoot(command);
|
|
559
|
+
const tts = await loadTts();
|
|
560
|
+
if (options.doctor) {
|
|
561
|
+
const report = await tts.doctor();
|
|
562
|
+
printMaybeJson(report, options.json);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (!textFile) {
|
|
566
|
+
console.error(pc.red("Missing text file. Use `ragmir audio <text-file>`."));
|
|
567
|
+
process.exitCode = 1;
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const renderOptions = {
|
|
571
|
+
cwd,
|
|
572
|
+
textFile,
|
|
573
|
+
engine: audioEngine(options),
|
|
574
|
+
};
|
|
575
|
+
addOption(renderOptions, "language", audioLanguage(options));
|
|
576
|
+
addOption(renderOptions, "outputPath", options.out);
|
|
577
|
+
addOption(renderOptions, "model", options.model);
|
|
578
|
+
addOption(renderOptions, "modelPath", options.modelPath);
|
|
579
|
+
addOption(renderOptions, "allowRemoteModels", audioAllowRemoteModels(options));
|
|
580
|
+
addOption(renderOptions, "voice", options.voice);
|
|
581
|
+
addOption(renderOptions, "rate", options.rate);
|
|
582
|
+
addOption(renderOptions, "speakerEmbeddings", options.speakerEmbeddings);
|
|
583
|
+
addOption(renderOptions, "speed", options.speed);
|
|
584
|
+
const result = await tts.renderSpeech(renderOptions);
|
|
585
|
+
printMaybeJson(result, options.json);
|
|
586
|
+
});
|
|
587
|
+
program
|
|
588
|
+
.command("serve-mcp")
|
|
589
|
+
.description("Start the MCP server over stdio for Claude, Codex, and other MCP-compatible agents.")
|
|
590
|
+
.action(async (_options, command) => {
|
|
591
|
+
const explicitRoot = explicitProjectRoot(command);
|
|
592
|
+
await serveMcp(explicitRoot);
|
|
593
|
+
});
|
|
594
|
+
program
|
|
595
|
+
.command("skill-path")
|
|
596
|
+
.description("Print the bundled Ragmir skill path for agents that can load SKILL.md folders.")
|
|
597
|
+
.action(() => {
|
|
598
|
+
console.log(bundledSkillPath());
|
|
599
|
+
});
|
|
600
|
+
program
|
|
601
|
+
.command("install-skill")
|
|
602
|
+
.description("Copy the bundled agent skill and MCP config snippet into the current repository.")
|
|
603
|
+
.option("--target-dir <path>", "Directory where the skill folder should be copied.", DEFAULT_SKILL_TARGET_DIR)
|
|
604
|
+
.option("--agents <list>", `Agent MCP helpers to generate: all, ${SUPPORTED_AGENT_TARGETS.join(", ")}.`, "all")
|
|
605
|
+
.option("--mcp-name <name>", "MCP server name used in generated config.", "ragmir")
|
|
606
|
+
.option("--mcp-command <command>", "Custom MCP stdio command for generated helper files.")
|
|
607
|
+
.option("--mcp-arg <arg>", "Argument for --mcp-command. Repeat for multiple arguments.", collectOptionValue, [])
|
|
608
|
+
.action(async (options, command) => {
|
|
609
|
+
const cwd = projectRoot(command);
|
|
610
|
+
const installOptions = {
|
|
611
|
+
cwd,
|
|
612
|
+
targetDir: options.targetDir,
|
|
613
|
+
agents: parseAgentTargets(options.agents),
|
|
614
|
+
mcpServerName: options.mcpName,
|
|
615
|
+
};
|
|
616
|
+
addOption(installOptions, "mcpCommand", options.mcpCommand);
|
|
617
|
+
if (options.mcpArg.length > 0) {
|
|
618
|
+
installOptions.mcpArgs = options.mcpArg;
|
|
619
|
+
}
|
|
620
|
+
const result = await installSkill(installOptions);
|
|
621
|
+
const doctorCommand = await ragmirCommand(cwd, ["doctor"]);
|
|
622
|
+
console.log("Installed Ragmir agent kit:");
|
|
623
|
+
for (const file of result.written) {
|
|
624
|
+
console.log(` - ${file}`);
|
|
625
|
+
}
|
|
626
|
+
console.log(`Skill path: ${result.skillPath}`);
|
|
627
|
+
console.log(`Optional audio skill path: ${result.audioSkillPath}`);
|
|
628
|
+
console.log(`Optional Markdown report skill path: ${result.reportSkillPath}`);
|
|
629
|
+
console.log(`MCP config example: ${result.mcpConfigPath}`);
|
|
630
|
+
for (const helper of result.agentHelpers) {
|
|
631
|
+
console.log(`${helper.label} MCP helper: ${helper.path}`);
|
|
632
|
+
}
|
|
633
|
+
console.log(`Agent setup guide: ${result.agentSetupPath}`);
|
|
634
|
+
console.log("");
|
|
635
|
+
console.log("Next steps:");
|
|
636
|
+
console.log(" 1. Run `ragmir install-agent --agents claude` or another targeted agent list.");
|
|
637
|
+
console.log(" 2. Add the MCP config from .ragmir/ to the same agent when MCP tools are needed.");
|
|
638
|
+
console.log(` 3. Run \`${doctorCommand.display}\` before relying on retrieved context.`);
|
|
639
|
+
});
|
|
640
|
+
program
|
|
641
|
+
.command("install-agent")
|
|
642
|
+
.description("Install Ragmir skills into native Claude, Codex, Kimi, OpenCode, or Cline folders.")
|
|
643
|
+
.option("--agents <list>", `Comma-separated agents: all, ${SUPPORTED_AGENT_TARGETS.join(", ")}.`, "all")
|
|
644
|
+
.option("--scope <scope>", "Install scope: project or user.", "project")
|
|
645
|
+
.option("--mode <mode>", "Expose skills as links or physical copies: link or copy.", "link")
|
|
646
|
+
.option("--json", "Print machine-readable JSON.")
|
|
647
|
+
.action(async (options, command) => {
|
|
648
|
+
const cwd = projectRoot(command);
|
|
649
|
+
const scope = parseAgentInstallScope(options.scope);
|
|
650
|
+
const mode = parseAgentInstallMode(options.mode);
|
|
651
|
+
const agents = parseAgentTargets(options.agents);
|
|
652
|
+
const result = await installAgentSkills({ cwd, agents, scope, mode });
|
|
653
|
+
if (options.json) {
|
|
654
|
+
console.log(JSON.stringify(result, null, 2));
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
console.log(`Installed Ragmir skills for ${scope}-scope agent discovery:`);
|
|
658
|
+
for (const installation of result.installations) {
|
|
659
|
+
console.log(` - ${installation.label}: ${installation.targetDir} (${installation.mode})`);
|
|
660
|
+
}
|
|
661
|
+
console.log("");
|
|
662
|
+
console.log("MCP helper files:");
|
|
663
|
+
console.log(` - generic: ${result.projectKit.mcpConfigPath}`);
|
|
664
|
+
for (const helper of result.projectKit.agentHelpers) {
|
|
665
|
+
console.log(` - ${helper.label}: ${helper.path}`);
|
|
666
|
+
}
|
|
667
|
+
console.log("");
|
|
668
|
+
console.log("Next steps:");
|
|
669
|
+
console.log(" 1. Keep editing the canonical skills under .ragmir/skills/.");
|
|
670
|
+
console.log(" 2. Restart or reload the selected agent so it discovers the exposed SKILL.md files.");
|
|
671
|
+
console.log(" 3. Wire the matching MCP helper if the agent should call Ragmir tools directly.");
|
|
672
|
+
console.log(` 4. Run \`${(await ragmirCommand(cwd, ["doctor"])).display}\`.`);
|
|
673
|
+
});
|
|
674
|
+
try {
|
|
675
|
+
await program.parseAsync(process.argv);
|
|
676
|
+
}
|
|
677
|
+
catch (error) {
|
|
678
|
+
console.error(pc.red(error instanceof Error ? error.message : String(error)));
|
|
679
|
+
process.exitCode = 1;
|
|
680
|
+
}
|
|
681
|
+
function parsePositiveInt(value) {
|
|
682
|
+
const parsed = Number.parseInt(value, 10);
|
|
683
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
684
|
+
throw new Error("Expected a positive integer.");
|
|
685
|
+
}
|
|
686
|
+
return parsed;
|
|
687
|
+
}
|
|
688
|
+
function parseRecallThreshold(value) {
|
|
689
|
+
const trimmed = value.trim();
|
|
690
|
+
const parsed = Number(trimmed);
|
|
691
|
+
if (trimmed.length === 0 || !Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
|
|
692
|
+
throw new Error("Expected a recall threshold between 0 and 1.");
|
|
693
|
+
}
|
|
694
|
+
return parsed;
|
|
695
|
+
}
|
|
696
|
+
function parseNumber(value) {
|
|
697
|
+
const parsed = Number.parseFloat(value);
|
|
698
|
+
if (!Number.isFinite(parsed)) {
|
|
699
|
+
throw new Error("Expected a number.");
|
|
700
|
+
}
|
|
701
|
+
return parsed;
|
|
702
|
+
}
|
|
703
|
+
function collectOptionValue(value, previous) {
|
|
704
|
+
return [...previous, value];
|
|
705
|
+
}
|
|
706
|
+
function projectRoot(command) {
|
|
707
|
+
return explicitProjectRoot(command) ?? process.cwd();
|
|
708
|
+
}
|
|
709
|
+
function explicitProjectRoot(command) {
|
|
710
|
+
const options = command.optsWithGlobals();
|
|
711
|
+
return options.projectRoot ? path.resolve(options.projectRoot) : undefined;
|
|
712
|
+
}
|
|
713
|
+
function withTopK(cwd, topK) {
|
|
714
|
+
return topK === undefined ? { cwd } : { cwd, topK };
|
|
715
|
+
}
|
|
716
|
+
async function loadTts() {
|
|
717
|
+
const module = await import(TTS_PACKAGE_NAME);
|
|
718
|
+
if (!isTtsModule(module)) {
|
|
719
|
+
throw new Error(`${TTS_PACKAGE_NAME} did not expose the expected TTS API.`);
|
|
720
|
+
}
|
|
721
|
+
return module;
|
|
722
|
+
}
|
|
723
|
+
function isTtsModule(value) {
|
|
724
|
+
return (typeof value === "object" &&
|
|
725
|
+
value !== null &&
|
|
726
|
+
"doctor" in value &&
|
|
727
|
+
typeof value.doctor === "function" &&
|
|
728
|
+
"renderSpeech" in value &&
|
|
729
|
+
typeof value.renderSpeech === "function");
|
|
730
|
+
}
|
|
731
|
+
function audioAllowRemoteModels(options) {
|
|
732
|
+
if (options.offline) {
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
if (options.allowRemoteModels) {
|
|
736
|
+
return true;
|
|
737
|
+
}
|
|
738
|
+
return undefined;
|
|
739
|
+
}
|
|
740
|
+
function audioLanguage(options) {
|
|
741
|
+
if (options.lang === undefined) {
|
|
742
|
+
return undefined;
|
|
743
|
+
}
|
|
744
|
+
if (isTtsLanguage(options.lang)) {
|
|
745
|
+
return options.lang;
|
|
746
|
+
}
|
|
747
|
+
throw new Error(`Expected --lang to be one of: ${TTS_LANGUAGES.join(", ")}.`);
|
|
748
|
+
}
|
|
749
|
+
function audioEngine(options) {
|
|
750
|
+
if (options.offline) {
|
|
751
|
+
return "transformers";
|
|
752
|
+
}
|
|
753
|
+
if (options.engine === undefined) {
|
|
754
|
+
if (options.out?.toLowerCase().endsWith(".mp3")) {
|
|
755
|
+
throw new Error("MP3 output uses online Edge TTS. Re-run with `--engine edge` only when sending narration text to Edge TTS is acceptable.");
|
|
756
|
+
}
|
|
757
|
+
return "transformers";
|
|
758
|
+
}
|
|
759
|
+
if (options.engine === "auto" || options.engine === "edge" || options.engine === "transformers") {
|
|
760
|
+
return options.engine;
|
|
761
|
+
}
|
|
762
|
+
throw new Error("Expected --engine to be auto, edge, or transformers.");
|
|
763
|
+
}
|
|
764
|
+
function parseAgentInstallScope(value) {
|
|
765
|
+
if (value === "project" || value === "user") {
|
|
766
|
+
return value;
|
|
767
|
+
}
|
|
768
|
+
throw new Error("Expected --scope to be project or user.");
|
|
769
|
+
}
|
|
770
|
+
function parseAgentInstallMode(value) {
|
|
771
|
+
if (value === "link" || value === "copy") {
|
|
772
|
+
return value;
|
|
773
|
+
}
|
|
774
|
+
throw new Error("Expected --mode to be link or copy.");
|
|
775
|
+
}
|
|
776
|
+
function printDoctor(report) {
|
|
777
|
+
console.log(`projectRoot=${report.projectRoot}`);
|
|
778
|
+
console.log(`initialized=${report.initialized}`);
|
|
779
|
+
console.log(`ready=${report.ready}`);
|
|
780
|
+
console.log(`packageManager=${report.packageManager}`);
|
|
781
|
+
console.log(`runCommand=${report.runCommand}`);
|
|
782
|
+
console.log(`agentKitInstalled=${report.agentKitInstalled}`);
|
|
783
|
+
console.log(`embeddingProvider=${report.embeddingProvider}`);
|
|
784
|
+
console.log(`transformersAllowRemoteModels=${report.transformersAllowRemoteModels}`);
|
|
785
|
+
console.log(`redactionEnabled=${report.redactionEnabled}`);
|
|
786
|
+
console.log(`accessLog=${report.accessLog}`);
|
|
787
|
+
console.log(`supportedFiles=${report.supportedFiles}`);
|
|
788
|
+
console.log(`skippedFiles=${report.skippedFiles}`);
|
|
789
|
+
console.log(`unsupportedFiles=${report.unsupportedFiles}`);
|
|
790
|
+
console.log(`indexedFiles=${report.indexedFiles}`);
|
|
791
|
+
console.log(`chunksIndexed=${report.chunksIndexed}`);
|
|
792
|
+
console.log(`missingFromIndex=${report.missingFromIndex}`);
|
|
793
|
+
console.log(`staleInIndex=${report.staleInIndex}`);
|
|
794
|
+
console.log(`securityWarnings=${report.securityWarnings.length}`);
|
|
795
|
+
if (report.securityWarnings.length > 0) {
|
|
796
|
+
for (const warning of report.securityWarnings) {
|
|
797
|
+
console.log(pc.yellow(`warning: ${warning}`));
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
console.log("nextSteps:");
|
|
801
|
+
for (const step of report.nextSteps) {
|
|
802
|
+
console.log(` - ${step}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
function printResearchReport(report) {
|
|
806
|
+
console.log(`query=${report.query}`);
|
|
807
|
+
console.log(`ready=${report.ready}`);
|
|
808
|
+
console.log(`generatedQueries=${report.generatedQueries.length}`);
|
|
809
|
+
console.log(`audit.supportedFiles=${report.audit.supportedFiles} audit.indexedFiles=${report.audit.indexedFiles} audit.totalChunks=${report.audit.totalChunks} audit.skippedFiles=${report.audit.skippedFiles} audit.missingFromIndex=${report.audit.missingFromIndex} audit.staleInIndex=${report.audit.staleInIndex}`);
|
|
810
|
+
console.log(`securityWarnings=${report.securityWarnings.length}`);
|
|
811
|
+
console.log(`sourceDiagnostics.duplicates=${report.sourceDiagnostics.duplicateCandidates.length} sourceDiagnostics.archives=${report.sourceDiagnostics.archiveCandidates.length} sourceDiagnostics.mirrors=${report.sourceDiagnostics.mirrorCandidates.length}`);
|
|
812
|
+
if (report.sourceDiagnostics.duplicateCandidates.length > 0) {
|
|
813
|
+
console.log("");
|
|
814
|
+
console.log(pc.cyan("Duplicate Candidates:"));
|
|
815
|
+
for (const candidate of report.sourceDiagnostics.duplicateCandidates.slice(0, 5)) {
|
|
816
|
+
console.log(` - ${candidate.key}: ${candidate.files.join(", ")}`);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (report.evidence.length > 0) {
|
|
820
|
+
console.log("");
|
|
821
|
+
console.log(pc.cyan("Evidence:"));
|
|
822
|
+
for (const [index, evidence] of report.evidence.entries()) {
|
|
823
|
+
const distance = evidence.distance === null ? "n/a" : evidence.distance.toFixed(4);
|
|
824
|
+
console.log(` [${index + 1}] ${evidence.relativePath} chunk=${evidence.chunkIndex} distance=${distance}`);
|
|
825
|
+
console.log(` ${researchEvidencePreview(evidence)}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
if (report.codeEvidence.length > 0) {
|
|
829
|
+
console.log("");
|
|
830
|
+
console.log(pc.cyan("Code Evidence:"));
|
|
831
|
+
for (const evidence of report.codeEvidence.slice(0, 10)) {
|
|
832
|
+
console.log(` - ${evidence.relativePath}:${evidence.lineNumber} terms=${evidence.matchedTerms.join(",")}`);
|
|
833
|
+
console.log(` ${evidence.snippet}`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
if (report.gaps.length > 0) {
|
|
837
|
+
console.log("");
|
|
838
|
+
console.log(pc.yellow("Gaps:"));
|
|
839
|
+
for (const gap of report.gaps) {
|
|
840
|
+
console.log(pc.yellow(` - ${gap}`));
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
console.log("");
|
|
844
|
+
console.log(pc.cyan("Next Steps:"));
|
|
845
|
+
for (const step of report.nextSteps) {
|
|
846
|
+
console.log(` - ${step}`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
function researchEvidencePreview(evidence) {
|
|
850
|
+
if ("snippet" in evidence) {
|
|
851
|
+
return evidence.snippet;
|
|
852
|
+
}
|
|
853
|
+
return evidence.text.replace(/\s+/gu, " ").trim().slice(0, SEARCH_TEXT_PREVIEW_LENGTH);
|
|
854
|
+
}
|
|
855
|
+
function printSetup(result, title) {
|
|
856
|
+
console.log(pc.green(title));
|
|
857
|
+
console.log(`projectRoot=${result.projectRoot}`);
|
|
858
|
+
console.log(`packageManager=${result.packageManager}`);
|
|
859
|
+
console.log(`runCommand=${result.runCommand}`);
|
|
860
|
+
console.log("");
|
|
861
|
+
console.log(pc.cyan("Scaffolding:"));
|
|
862
|
+
if (result.created.length === 0) {
|
|
863
|
+
console.log(" - already initialized");
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
for (const file of result.created) {
|
|
867
|
+
console.log(` - ${file}`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
console.log("");
|
|
871
|
+
console.log(pc.cyan("Agent integration:"));
|
|
872
|
+
console.log(` - skill: ${result.agentKit.skillPath}`);
|
|
873
|
+
console.log(` - audio skill: ${result.agentKit.audioSkillPath}`);
|
|
874
|
+
console.log(` - report skill: ${result.agentKit.reportSkillPath}`);
|
|
875
|
+
console.log(` - MCP config: ${result.agentKit.mcpConfigPath}`);
|
|
876
|
+
for (const helper of result.agentKit.agentHelpers) {
|
|
877
|
+
console.log(` - ${helper.label} MCP helper: ${helper.path}`);
|
|
878
|
+
}
|
|
879
|
+
console.log(` - agent setup guide: ${result.agentKit.agentSetupPath}`);
|
|
880
|
+
console.log("");
|
|
881
|
+
if (result.semantic) {
|
|
882
|
+
console.log(pc.cyan("Semantic retrieval:"));
|
|
883
|
+
console.log(" - enabled for higher-quality natural-language retrieval");
|
|
884
|
+
console.log(` - embedding model: ${result.semantic.model.embeddingModel}`);
|
|
885
|
+
console.log(` - model path: ${result.semantic.model.embeddingModelPath}`);
|
|
886
|
+
console.log(" - remote model loading after setup: false");
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
console.log(pc.cyan("Semantic retrieval:"));
|
|
890
|
+
console.log(" - skipped; default local-hash retrieval is fully local but not semantic. Run `ragmir setup --semantic` or `ragmir models pull --enable` when a one-time model download is acceptable.");
|
|
891
|
+
}
|
|
892
|
+
console.log("");
|
|
893
|
+
console.log(pc.cyan("Index:"));
|
|
894
|
+
if (result.ingested) {
|
|
895
|
+
console.log(` - ingested indexedFiles=${result.ingested.indexedFiles} rebuiltFiles=${result.ingested.rebuiltFiles} reusedFiles=${result.ingested.reusedFiles} chunks=${result.ingested.chunks} skippedFiles=${result.ingested.skippedFiles} emptyTextFiles=${result.ingested.emptyTextFiles.length} errors=${result.ingested.errors.length}`);
|
|
896
|
+
printUnsupportedSummary(result.ingested.unsupportedExtensions);
|
|
897
|
+
printEmptyTextFiles(result.ingested.emptyTextFiles);
|
|
898
|
+
}
|
|
899
|
+
else if (result.doctor.ready) {
|
|
900
|
+
console.log(` - already ready chunks=${result.doctor.chunksIndexed}`);
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
console.log(" - skipped; add supported files or run doctor --fix when ready");
|
|
904
|
+
}
|
|
905
|
+
console.log("");
|
|
906
|
+
printDoctor(result.doctor);
|
|
907
|
+
}
|
|
908
|
+
function printUnsupportedSummary(extensions) {
|
|
909
|
+
if (extensions.length === 0) {
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
console.log(pc.yellow(`unsupportedExtensions=${extensions
|
|
913
|
+
.map((entry) => `${entry.extension}:${entry.count}`)
|
|
914
|
+
.join(",")}`));
|
|
915
|
+
}
|
|
916
|
+
function printEmptyTextFiles(files) {
|
|
917
|
+
if (files.length === 0) {
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
console.log(pc.yellow(`emptyTextFiles=${files.length}`));
|
|
921
|
+
for (const file of files) {
|
|
922
|
+
console.log(pc.yellow(`empty-text: ${file}`));
|
|
923
|
+
}
|
|
924
|
+
console.log(pc.yellow("These supported files produced no indexable text. For scanned/image-only sources, configure pdfOcrCommand or imageOcrCommand, or store local OCR text beside the source file."));
|
|
925
|
+
}
|
|
926
|
+
function printMaybeJson(value, json) {
|
|
927
|
+
if (json) {
|
|
928
|
+
console.log(JSON.stringify(value, null, 2));
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
if (typeof value === "object" && value !== null) {
|
|
932
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
933
|
+
console.log(`${key}=${String(entry)}`);
|
|
934
|
+
}
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
console.log(String(value));
|
|
938
|
+
}
|
|
939
|
+
function addOption(target, key, value) {
|
|
940
|
+
if (value !== undefined) {
|
|
941
|
+
target[key] = value;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
//# sourceMappingURL=cli.js.map
|