@jcode.labs/ragmir 4.2.2 → 5.0.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/README.md +71 -160
- package/dist/chunking.d.ts +1 -1
- package/dist/chunking.d.ts.map +1 -1
- package/dist/chunking.js +17 -1
- package/dist/chunking.js.map +1 -1
- package/dist/cli-entry.js +1 -1
- package/dist/cli-entry.js.map +1 -1
- package/dist/cli-fast.d.ts +1 -3
- package/dist/cli-fast.d.ts.map +1 -1
- package/dist/cli-fast.js +2 -104
- package/dist/cli-fast.js.map +1 -1
- package/dist/cli-options.d.ts +0 -37
- package/dist/cli-options.d.ts.map +1 -1
- package/dist/cli-options.js +0 -53
- package/dist/cli-options.js.map +1 -1
- package/dist/cli.js +9 -701
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +1 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +7 -36
- package/dist/client.js.map +1 -1
- package/dist/config-migration.d.ts +6 -0
- package/dist/config-migration.d.ts.map +1 -0
- package/dist/config-migration.js +31 -0
- package/dist/config-migration.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -65
- package/dist/config.js.map +1 -1
- package/dist/context-resources.d.ts.map +1 -1
- package/dist/context-resources.js +2 -14
- package/dist/context-resources.js.map +1 -1
- package/dist/defaults.d.ts +0 -3
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +0 -13
- package/dist/defaults.js.map +1 -1
- package/dist/destroy.d.ts.map +1 -1
- package/dist/destroy.js +0 -2
- package/dist/destroy.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +1 -9
- package/dist/doctor.js.map +1 -1
- package/dist/document-structure.js.map +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +9 -1
- package/dist/embeddings.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/evaluate.d.ts.map +1 -1
- package/dist/evaluate.js +0 -7
- package/dist/evaluate.js.map +1 -1
- package/dist/files.js.map +1 -1
- package/dist/generation-retention.js.map +1 -1
- package/dist/gitignore.js.map +1 -1
- package/dist/index-diagnostics.js +1 -1
- package/dist/index-diagnostics.js.map +1 -1
- package/dist/index-policy.d.ts.map +1 -1
- package/dist/index-policy.js +3 -4
- package/dist/index-policy.js.map +1 -1
- package/dist/index-write-lock.js.map +1 -1
- package/dist/index.d.ts +4 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -8
- package/dist/index.js.map +1 -1
- package/dist/ingest.d.ts.map +1 -1
- package/dist/ingest.js +3 -18
- package/dist/ingest.js.map +1 -1
- package/dist/ingestion-metrics.d.ts +1 -1
- package/dist/ingestion-metrics.d.ts.map +1 -1
- package/dist/ingestion-metrics.js +0 -1
- package/dist/ingestion-metrics.js.map +1 -1
- package/dist/ingestion-state.d.ts +1 -2
- package/dist/ingestion-state.d.ts.map +1 -1
- package/dist/ingestion-state.js +0 -2
- package/dist/ingestion-state.js.map +1 -1
- package/dist/lexical-scoring.d.ts +13 -0
- package/dist/lexical-scoring.d.ts.map +1 -0
- package/dist/lexical-scoring.js +35 -0
- package/dist/lexical-scoring.js.map +1 -0
- package/dist/mcp-output.d.ts +4 -31
- package/dist/mcp-output.d.ts.map +1 -1
- package/dist/mcp-output.js +4 -123
- package/dist/mcp-output.js.map +1 -1
- package/dist/mcp-summaries.d.ts +1 -12
- package/dist/mcp-summaries.d.ts.map +1 -1
- package/dist/mcp-summaries.js +0 -112
- package/dist/mcp-summaries.js.map +1 -1
- package/dist/mcp.d.ts +1 -5
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +22 -329
- package/dist/mcp.js.map +1 -1
- package/dist/ocr.d.ts +0 -1
- package/dist/ocr.d.ts.map +1 -1
- package/dist/ocr.js +0 -4
- package/dist/ocr.js.map +1 -1
- package/dist/parsing.js +13 -0
- package/dist/parsing.js.map +1 -1
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +2 -5
- package/dist/preview.js.map +1 -1
- package/dist/query-sanitizer.d.ts +2 -1
- package/dist/query-sanitizer.d.ts.map +1 -1
- package/dist/query-sanitizer.js +9 -127
- package/dist/query-sanitizer.js.map +1 -1
- package/dist/query.d.ts +1 -3
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +95 -71
- package/dist/query.js.map +1 -1
- package/dist/ranking.d.ts +1 -1
- package/dist/ranking.d.ts.map +1 -1
- package/dist/ranking.js +52 -41
- package/dist/ranking.js.map +1 -1
- package/dist/runtime-info.js.map +1 -1
- package/dist/search-output.d.ts +3 -0
- package/dist/search-output.d.ts.map +1 -0
- package/dist/search-output.js +26 -0
- package/dist/search-output.js.map +1 -0
- package/dist/security.d.ts.map +1 -1
- package/dist/security.js +5 -37
- package/dist/security.js.map +1 -1
- package/dist/setup-prompt.d.ts +1 -1
- package/dist/setup-prompt.d.ts.map +1 -1
- package/dist/setup-prompt.js +22 -25
- package/dist/setup-prompt.js.map +1 -1
- package/dist/setup.js +1 -1
- package/dist/setup.js.map +1 -1
- package/dist/skill.d.ts +1 -4
- package/dist/skill.d.ts.map +1 -1
- package/dist/skill.js +3 -71
- package/dist/skill.js.map +1 -1
- package/dist/storage-maintenance.js.map +1 -1
- package/dist/store.js +9 -1
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +19 -156
- package/dist/types.d.ts.map +1 -1
- package/dist/upgrade.d.ts +3 -2
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +20 -2
- package/dist/upgrade.js.map +1 -1
- package/dist/vector-index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workload.js.map +1 -1
- package/examples/document-evidence-benchmark/.ragmir/config.json +0 -9
- package/examples/document-evidence-benchmark/README.md +5 -3
- package/examples/document-evidence-benchmark/raw/rfp/security-questionnaire.md +1 -1
- package/examples/library-api-demo/README.md +17 -8
- package/examples/library-api-demo/run.mjs +6 -6
- package/examples/sovereign-rag-demo/.ragmir/config.json +3 -10
- package/examples/sovereign-rag-demo/README.md +21 -12
- package/package.json +21 -30
- package/skills/ragmir/SKILL.md +67 -199
- package/dist/access-log.d.ts +0 -30
- package/dist/access-log.d.ts.map +0 -1
- package/dist/access-log.js +0 -415
- package/dist/access-log.js.map +0 -1
- package/dist/cli-stdin.d.ts +0 -3
- package/dist/cli-stdin.d.ts.map +0 -1
- package/dist/cli-stdin.js +0 -15
- package/dist/cli-stdin.js.map +0 -1
- package/dist/portable-entry.d.ts +0 -5
- package/dist/portable-entry.d.ts.map +0 -1
- package/dist/portable-entry.js +0 -337
- package/dist/portable-entry.js.map +0 -1
- package/dist/portable.d.ts +0 -89
- package/dist/portable.d.ts.map +0 -1
- package/dist/portable.js +0 -1290
- package/dist/portable.js.map +0 -1
- package/dist/prompt-routing.d.ts +0 -12
- package/dist/prompt-routing.d.ts.map +0 -1
- package/dist/prompt-routing.js +0 -150
- package/dist/prompt-routing.js.map +0 -1
- package/dist/redaction.d.ts +0 -12
- package/dist/redaction.d.ts.map +0 -1
- package/dist/redaction.js +0 -253
- package/dist/redaction.js.map +0 -1
- package/dist/research.d.ts +0 -21
- package/dist/research.d.ts.map +0 -1
- package/dist/research.js +0 -667
- package/dist/research.js.map +0 -1
- package/dist/team-diagnostics.d.ts +0 -119
- package/dist/team-diagnostics.d.ts.map +0 -1
- package/dist/team-diagnostics.js +0 -515
- package/dist/team-diagnostics.js.map +0 -1
- package/dist/team-sync.d.ts +0 -49
- package/dist/team-sync.d.ts.map +0 -1
- package/dist/team-sync.js +0 -436
- package/dist/team-sync.js.map +0 -1
- package/skills/ragmir-audio-summary/SKILL.md +0 -80
- package/skills/ragmir-audio-summary/forge-voice.sh +0 -150
- package/skills/ragmir-audio-summary/listening-style.md +0 -92
- package/skills/ragmir-audio-summary/narration-templates.md +0 -162
- package/skills/ragmir-audio-summary/split-lines.py +0 -14
- package/skills/ragmir-audio-summary/xtts-voice.py +0 -46
- package/skills/ragmir-legal-dossier/SKILL.md +0 -143
- package/skills/ragmir-markdown-report/SKILL.md +0 -110
package/dist/portable.js
DELETED
|
@@ -1,1290 +0,0 @@
|
|
|
1
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { createReadStream, existsSync } from "node:fs";
|
|
3
|
-
import { chmod, copyFile, lstat, readdir, readFile, realpath, rename, rm, stat, writeFile, } from "node:fs/promises";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { z } from "zod";
|
|
7
|
-
import { externalExtractorsRequested, loadConfig } from "./config.js";
|
|
8
|
-
import { INDEX_MANIFEST_FILENAME, RAGMIR_DIR, RAGMIR_PORTABLE_READ_ONLY_ENV, RAGMIR_PROJECT_ROOT_ENV, } from "./defaults.js";
|
|
9
|
-
import { embeddingModelArtifactDigest, embeddingModelArtifactRoot } from "./embeddings.js";
|
|
10
|
-
import { indexFreshnessWarning } from "./index-diagnostics.js";
|
|
11
|
-
import { withIndexWriteLock } from "./index-write-lock.js";
|
|
12
|
-
import { operationSignal, throwIfAborted } from "./operation.js";
|
|
13
|
-
import { ensurePrivateDirectory, hardenPrivateFile } from "./permissions.js";
|
|
14
|
-
import { closeRowsTable, closeStoreConnection, connectStore, openRowsTableByName, readIndexManifestHeader, } from "./store.js";
|
|
15
|
-
import { VERSION } from "./version.js";
|
|
16
|
-
const PORTABLE_SCHEMA_VERSION = 1;
|
|
17
|
-
const PORTABLE_KIND = "ragmir-portable-knowledge-base";
|
|
18
|
-
const PORTABLE_MANIFEST_FILENAME = "manifest.json";
|
|
19
|
-
const PORTABLE_CONFIG_PATH = path.join(RAGMIR_DIR, "config.json");
|
|
20
|
-
const PORTABLE_STORAGE_PATH = path.join(RAGMIR_DIR, "storage");
|
|
21
|
-
const PORTABLE_MODEL_PATH = path.join(RAGMIR_DIR, "models");
|
|
22
|
-
const PORTABLE_SOURCES_PATH = path.join(RAGMIR_DIR, "sources.txt");
|
|
23
|
-
const PORTABLE_RUNNER_PATH = path.join("bin", "rgr.cjs");
|
|
24
|
-
const PORTABLE_CONFIGURATOR_PATH = path.join("bin", "configure.cjs");
|
|
25
|
-
const PORTABLE_RUNTIME_PATH = "runtime";
|
|
26
|
-
const PORTABLE_RUNTIME_DIST_PATH = path.join(PORTABLE_RUNTIME_PATH, "dist");
|
|
27
|
-
const PORTABLE_SKILL_NAMES = ["ragmir-portable", "ragmir-decision-evidence"];
|
|
28
|
-
const PORTABLE_ADAPTER_FILENAMES = [
|
|
29
|
-
"README.md",
|
|
30
|
-
"generic-mcp.json",
|
|
31
|
-
"openclaw-mcp-server.json",
|
|
32
|
-
"claude-mcp-server.json",
|
|
33
|
-
"codex-mcp.toml",
|
|
34
|
-
"kimi-mcp.json",
|
|
35
|
-
"opencode.jsonc",
|
|
36
|
-
"cline-mcp.json",
|
|
37
|
-
"stdio-command.txt",
|
|
38
|
-
];
|
|
39
|
-
const MAX_PORTABLE_MANIFEST_BYTES = 64 * 1_024 * 1_024;
|
|
40
|
-
const MAX_PORTABLE_PACKAGE_BYTES = 1_024 * 1_024;
|
|
41
|
-
const PORTABLE_ROOT_PLACEHOLDER = "<PORTABLE_ROOT>";
|
|
42
|
-
const ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/u;
|
|
43
|
-
const SHA256_PATTERN = /^[0-9a-f]{64}$/u;
|
|
44
|
-
const SAFE_TABLE_NAME_PATTERN = /^[A-Za-z0-9_-]+$/u;
|
|
45
|
-
const INDEX_FILES_SNAPSHOT_PATTERN = /^index-manifest\.files\.[A-Za-z0-9-]+\.jsonl$/u;
|
|
46
|
-
const PORTABLE_RUNTIME_ALWAYS_DEPENDENCIES = [
|
|
47
|
-
"@lancedb/lancedb",
|
|
48
|
-
"@modelcontextprotocol/sdk",
|
|
49
|
-
"apache-arrow",
|
|
50
|
-
"safe-regex2",
|
|
51
|
-
"zod",
|
|
52
|
-
];
|
|
53
|
-
const PORTABLE_RUNTIME_TRANSFORMERS_DEPENDENCY = "@huggingface/transformers";
|
|
54
|
-
const PORTABLE_RUNTIME_OMITTED_DEPENDENCIES = new Set([
|
|
55
|
-
"@hono/node-server",
|
|
56
|
-
"content-type",
|
|
57
|
-
"cors",
|
|
58
|
-
"cross-spawn",
|
|
59
|
-
"eventsource",
|
|
60
|
-
"eventsource-parser",
|
|
61
|
-
"express",
|
|
62
|
-
"express-rate-limit",
|
|
63
|
-
"hono",
|
|
64
|
-
"jose",
|
|
65
|
-
"onnxruntime-web",
|
|
66
|
-
"pkce-challenge",
|
|
67
|
-
"raw-body",
|
|
68
|
-
"sharp",
|
|
69
|
-
]);
|
|
70
|
-
const ONNX_RUNTIME_PLATFORM_ROOT = path.join("bin", "napi-v6", process.platform, process.arch);
|
|
71
|
-
const portableRelativePathSchema = z
|
|
72
|
-
.string()
|
|
73
|
-
.min(1)
|
|
74
|
-
.max(4_096)
|
|
75
|
-
.refine((value) => portableRelativePath(value) !== null, "Path must stay inside the bundle.");
|
|
76
|
-
const portableFileSchema = z
|
|
77
|
-
.object({
|
|
78
|
-
path: portableRelativePathSchema,
|
|
79
|
-
bytes: z.number().int().nonnegative(),
|
|
80
|
-
sha256: z.string().regex(SHA256_PATTERN),
|
|
81
|
-
})
|
|
82
|
-
.strict();
|
|
83
|
-
export const portableKnowledgeBaseManifestSchema = z
|
|
84
|
-
.object({
|
|
85
|
-
schemaVersion: z.literal(PORTABLE_SCHEMA_VERSION),
|
|
86
|
-
kind: z.literal(PORTABLE_KIND),
|
|
87
|
-
createdAt: z.string().regex(ISO_DATE_PATTERN),
|
|
88
|
-
name: z.string().min(1).max(120),
|
|
89
|
-
ragmirVersion: z.string().min(1).max(100),
|
|
90
|
-
runtime: z
|
|
91
|
-
.object({
|
|
92
|
-
node: z.literal(">=22"),
|
|
93
|
-
package: z.literal("@jcode.labs/ragmir"),
|
|
94
|
-
packageVersion: z.string().min(1).max(100),
|
|
95
|
-
resolution: z.enum(["embedded-platform-runtime", "local-install-or-pinned-npx"]),
|
|
96
|
-
exportedOn: z
|
|
97
|
-
.object({
|
|
98
|
-
platform: z.string().min(1).max(100),
|
|
99
|
-
arch: z.string().min(1).max(100),
|
|
100
|
-
})
|
|
101
|
-
.strict(),
|
|
102
|
-
})
|
|
103
|
-
.strict(),
|
|
104
|
-
knowledgeBase: z
|
|
105
|
-
.object({
|
|
106
|
-
corpusFingerprint: z.string().regex(SHA256_PATTERN).nullable(),
|
|
107
|
-
indexedFiles: z.number().int().positive(),
|
|
108
|
-
indexedChunks: z.number().int().positive(),
|
|
109
|
-
embeddingProvider: z.enum(["local-hash", "transformers"]),
|
|
110
|
-
embeddingModel: z.string().min(1),
|
|
111
|
-
embeddingModelRevision: z.string().min(1),
|
|
112
|
-
embeddingModelDigest: z.string().nullable(),
|
|
113
|
-
indexSchemaVersion: z.number().int().positive(),
|
|
114
|
-
tableName: z.string().regex(SAFE_TABLE_NAME_PATTERN),
|
|
115
|
-
})
|
|
116
|
-
.strict(),
|
|
117
|
-
contents: z
|
|
118
|
-
.object({
|
|
119
|
-
rawSourcesIncluded: z.literal(false),
|
|
120
|
-
indexedTextIncluded: z.literal(true),
|
|
121
|
-
accessLogsIncluded: z.literal(false),
|
|
122
|
-
embeddingModelIncluded: z.boolean(),
|
|
123
|
-
skills: z.array(z.enum(PORTABLE_SKILL_NAMES)).length(PORTABLE_SKILL_NAMES.length),
|
|
124
|
-
adapters: z
|
|
125
|
-
.array(z.enum(PORTABLE_ADAPTER_FILENAMES))
|
|
126
|
-
.length(PORTABLE_ADAPTER_FILENAMES.length),
|
|
127
|
-
})
|
|
128
|
-
.strict(),
|
|
129
|
-
files: z.array(portableFileSchema).min(1).max(200_000),
|
|
130
|
-
})
|
|
131
|
-
.strict();
|
|
132
|
-
export async function exportPortableKnowledgeBase(options = {}) {
|
|
133
|
-
const signal = operationSignal(options);
|
|
134
|
-
throwIfAborted(signal);
|
|
135
|
-
const config = await loadConfig(options.cwd ?? process.cwd());
|
|
136
|
-
const name = portableDisplayName(options.name ?? path.basename(config.projectRoot));
|
|
137
|
-
const outputDir = path.resolve(config.projectRoot, options.outputDir ?? defaultPortableOutputPath(name));
|
|
138
|
-
assertPortableDestination(config, outputDir);
|
|
139
|
-
const destinationExists = await pathExists(outputDir);
|
|
140
|
-
if (destinationExists) {
|
|
141
|
-
if (!options.replaceExisting) {
|
|
142
|
-
throw new Error(`Portable export destination already exists: ${outputDir}. Choose a new directory or pass replaceExisting to preserve and replace its portable bundle.`);
|
|
143
|
-
}
|
|
144
|
-
await assertReplaceablePortableDestination(outputDir);
|
|
145
|
-
}
|
|
146
|
-
if (externalExtractorsRequested(config)) {
|
|
147
|
-
throw new Error("Portable export does not copy external extractor commands. Disable PDF OCR, image OCR, and legacy Word commands, rebuild the index if needed, then export again.");
|
|
148
|
-
}
|
|
149
|
-
if (config.embeddingProvider === "transformers" && config.embeddingModelDigest === null) {
|
|
150
|
-
throw new Error("Portable semantic export requires a verified embeddingModelDigest. Pull and verify the configured model before exporting.");
|
|
151
|
-
}
|
|
152
|
-
const stagingDir = path.join(path.dirname(outputDir), `.${path.basename(outputDir)}.ragmir-staging-${randomUUID()}`);
|
|
153
|
-
await ensurePrivateDirectory(path.dirname(outputDir));
|
|
154
|
-
try {
|
|
155
|
-
const activeManifest = await withIndexWriteLock(config.storageDir, signal, async () => {
|
|
156
|
-
throwIfAborted(signal);
|
|
157
|
-
const currentManifest = await readIndexManifestHeader(config);
|
|
158
|
-
assertExportableManifest(config, currentManifest);
|
|
159
|
-
await ensurePrivateDirectory(stagingDir);
|
|
160
|
-
await copyPortableStorage(stagingDir, config, currentManifest, signal);
|
|
161
|
-
return currentManifest;
|
|
162
|
-
});
|
|
163
|
-
await buildPortableDirectory(stagingDir, config, name, signal);
|
|
164
|
-
const manifest = await createPortableManifest(stagingDir, config, activeManifest, name, signal);
|
|
165
|
-
await writePrivateJson(path.join(stagingDir, PORTABLE_MANIFEST_FILENAME), manifest);
|
|
166
|
-
const verification = await verifyPortableKnowledgeBase(stagingDir);
|
|
167
|
-
if (!verification.valid) {
|
|
168
|
-
throw new Error(`Portable export verification failed: ${verification.errors.join(" ")}`);
|
|
169
|
-
}
|
|
170
|
-
throwIfAborted(signal);
|
|
171
|
-
const previousOutputDir = await activatePortableDirectory(stagingDir, outputDir, options.replaceExisting === true);
|
|
172
|
-
const totalBytes = manifest.files.reduce((sum, file) => sum + file.bytes, 0);
|
|
173
|
-
return {
|
|
174
|
-
outputDir,
|
|
175
|
-
previousOutputDir,
|
|
176
|
-
manifestPath: path.join(outputDir, PORTABLE_MANIFEST_FILENAME),
|
|
177
|
-
fileCount: manifest.files.length,
|
|
178
|
-
totalBytes,
|
|
179
|
-
embeddingModelIncluded: manifest.contents.embeddingModelIncluded,
|
|
180
|
-
verification: { ...verification, root: outputDir },
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
catch (error) {
|
|
184
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
185
|
-
throw error;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
export async function verifyPortableKnowledgeBase(root) {
|
|
189
|
-
const resolvedRoot = path.resolve(root);
|
|
190
|
-
const errors = [];
|
|
191
|
-
const warnings = [];
|
|
192
|
-
let checkedFiles = 0;
|
|
193
|
-
let manifest = null;
|
|
194
|
-
try {
|
|
195
|
-
const manifestPath = path.join(resolvedRoot, PORTABLE_MANIFEST_FILENAME);
|
|
196
|
-
const manifestStat = await lstat(manifestPath);
|
|
197
|
-
if (!manifestStat.isFile() || manifestStat.isSymbolicLink()) {
|
|
198
|
-
throw new Error("manifest.json must be a regular file.");
|
|
199
|
-
}
|
|
200
|
-
if (manifestStat.size > MAX_PORTABLE_MANIFEST_BYTES) {
|
|
201
|
-
throw new Error(`manifest.json exceeds ${MAX_PORTABLE_MANIFEST_BYTES} bytes.`);
|
|
202
|
-
}
|
|
203
|
-
manifest = portableKnowledgeBaseManifestSchema.parse(JSON.parse(await readFile(manifestPath, "utf8")));
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
errors.push(`Manifest validation failed: ${errorMessage(error)}`);
|
|
207
|
-
return { root: resolvedRoot, valid: false, checkedFiles, errors, warnings, manifest };
|
|
208
|
-
}
|
|
209
|
-
const seenPaths = new Set();
|
|
210
|
-
for (const expected of manifest.files) {
|
|
211
|
-
if (seenPaths.has(expected.path)) {
|
|
212
|
-
errors.push(`Duplicate manifest path: ${expected.path}.`);
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
seenPaths.add(expected.path);
|
|
216
|
-
const relativePath = portableRelativePath(expected.path);
|
|
217
|
-
if (relativePath === null) {
|
|
218
|
-
errors.push(`Unsafe manifest path: ${expected.path}.`);
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
const filePath = path.join(resolvedRoot, ...relativePath.split("/"));
|
|
222
|
-
try {
|
|
223
|
-
const details = await lstat(filePath);
|
|
224
|
-
if (!details.isFile() || details.isSymbolicLink()) {
|
|
225
|
-
errors.push(`Managed path is not a regular file: ${expected.path}.`);
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
if (details.size !== expected.bytes) {
|
|
229
|
-
errors.push(`Size mismatch for ${expected.path}.`);
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
const sha256 = await hashFile(filePath);
|
|
233
|
-
if (sha256 !== expected.sha256) {
|
|
234
|
-
errors.push(`SHA-256 mismatch for ${expected.path}.`);
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
checkedFiles += 1;
|
|
238
|
-
}
|
|
239
|
-
catch (error) {
|
|
240
|
-
errors.push(`Cannot verify ${expected.path}: ${errorMessage(error)}`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (errors.length === 0) {
|
|
244
|
-
try {
|
|
245
|
-
const config = await loadConfig(resolvedRoot);
|
|
246
|
-
assertPortableRuntimeConfig(config, resolvedRoot);
|
|
247
|
-
const indexManifest = await readIndexManifestHeader(config);
|
|
248
|
-
if (!indexManifest) {
|
|
249
|
-
throw new Error("The active Ragmir index manifest is missing or invalid.");
|
|
250
|
-
}
|
|
251
|
-
const freshnessWarning = indexFreshnessWarning(config, indexManifest);
|
|
252
|
-
if (freshnessWarning) {
|
|
253
|
-
throw new Error(freshnessWarning);
|
|
254
|
-
}
|
|
255
|
-
if (indexManifest.corpusFingerprint !== manifest.knowledgeBase.corpusFingerprint) {
|
|
256
|
-
throw new Error("The portable manifest and active index have different corpus fingerprints.");
|
|
257
|
-
}
|
|
258
|
-
if (indexManifest.chunkCount !== manifest.knowledgeBase.indexedChunks) {
|
|
259
|
-
throw new Error("The portable manifest and active index have different chunk counts.");
|
|
260
|
-
}
|
|
261
|
-
if (config.embeddingProvider === "transformers") {
|
|
262
|
-
const digest = await embeddingModelArtifactDigest(config);
|
|
263
|
-
if (digest !== config.embeddingModelDigest) {
|
|
264
|
-
throw new Error(`Portable embedding model digest mismatch: expected ${config.embeddingModelDigest}, received ${digest}.`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
await verifyPortableTable(config, manifest.knowledgeBase.tableName, indexManifest.chunkCount);
|
|
268
|
-
}
|
|
269
|
-
catch (error) {
|
|
270
|
-
errors.push(`Runtime validation failed: ${errorMessage(error)}`);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
if (manifest.runtime.exportedOn.platform !== process.platform ||
|
|
274
|
-
manifest.runtime.exportedOn.arch !== process.arch) {
|
|
275
|
-
const message = `This bundle was exported for ${manifest.runtime.exportedOn.platform}/${manifest.runtime.exportedOn.arch}, but this host is ${process.platform}/${process.arch}.`;
|
|
276
|
-
if (manifest.runtime.resolution === "embedded-platform-runtime") {
|
|
277
|
-
errors.push(`${message} Re-export it on a matching platform before use.`);
|
|
278
|
-
}
|
|
279
|
-
else {
|
|
280
|
-
warnings.push(`${message} Verify retrieval before relying on it.`);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return {
|
|
284
|
-
root: resolvedRoot,
|
|
285
|
-
valid: errors.length === 0,
|
|
286
|
-
checkedFiles,
|
|
287
|
-
errors,
|
|
288
|
-
warnings,
|
|
289
|
-
manifest,
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
function defaultPortableOutputPath(name) {
|
|
293
|
-
return path.join(RAGMIR_DIR, "exports", `${portableSlug(name)}-${portableTimestamp()}`);
|
|
294
|
-
}
|
|
295
|
-
function portableTimestamp() {
|
|
296
|
-
return new Date()
|
|
297
|
-
.toISOString()
|
|
298
|
-
.replace(/[-:]/gu, "")
|
|
299
|
-
.replace(/\.\d{3}Z$/u, "Z");
|
|
300
|
-
}
|
|
301
|
-
async function activatePortableDirectory(stagingDir, outputDir, replaceExisting) {
|
|
302
|
-
if (!(await pathExists(outputDir))) {
|
|
303
|
-
await rename(stagingDir, outputDir);
|
|
304
|
-
return null;
|
|
305
|
-
}
|
|
306
|
-
if (!replaceExisting) {
|
|
307
|
-
throw new Error(`Portable export destination appeared before activation: ${outputDir}. Run the export again with a new destination.`);
|
|
308
|
-
}
|
|
309
|
-
await assertReplaceablePortableDestination(outputDir);
|
|
310
|
-
const previousOutputDir = `${outputDir}.previous-${portableTimestamp()}-${randomUUID().slice(0, 8)}`;
|
|
311
|
-
await rename(outputDir, previousOutputDir);
|
|
312
|
-
try {
|
|
313
|
-
await rename(stagingDir, outputDir);
|
|
314
|
-
return previousOutputDir;
|
|
315
|
-
}
|
|
316
|
-
catch (activationError) {
|
|
317
|
-
try {
|
|
318
|
-
await rename(previousOutputDir, outputDir);
|
|
319
|
-
}
|
|
320
|
-
catch (rollbackError) {
|
|
321
|
-
throw new AggregateError([activationError, rollbackError], `Portable activation and rollback both failed. The previous bundle remains at ${previousOutputDir}.`);
|
|
322
|
-
}
|
|
323
|
-
throw activationError;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
async function assertReplaceablePortableDestination(outputDir) {
|
|
327
|
-
try {
|
|
328
|
-
const rootDetails = await lstat(outputDir);
|
|
329
|
-
if (!rootDetails.isDirectory() || rootDetails.isSymbolicLink()) {
|
|
330
|
-
throw new Error("the destination is not a regular directory");
|
|
331
|
-
}
|
|
332
|
-
const manifestPath = path.join(outputDir, PORTABLE_MANIFEST_FILENAME);
|
|
333
|
-
const manifestDetails = await lstat(manifestPath);
|
|
334
|
-
if (!manifestDetails.isFile() || manifestDetails.isSymbolicLink()) {
|
|
335
|
-
throw new Error("manifest.json is not a regular file");
|
|
336
|
-
}
|
|
337
|
-
if (manifestDetails.size > MAX_PORTABLE_MANIFEST_BYTES) {
|
|
338
|
-
throw new Error(`manifest.json exceeds ${MAX_PORTABLE_MANIFEST_BYTES} bytes`);
|
|
339
|
-
}
|
|
340
|
-
const manifestValue = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
341
|
-
if (typeof manifestValue !== "object" ||
|
|
342
|
-
manifestValue === null ||
|
|
343
|
-
Array.isArray(manifestValue) ||
|
|
344
|
-
Reflect.get(manifestValue, "kind") !== PORTABLE_KIND) {
|
|
345
|
-
throw new Error(`manifest.json does not declare ${PORTABLE_KIND}`);
|
|
346
|
-
}
|
|
347
|
-
const packagePath = path.join(outputDir, "package.json");
|
|
348
|
-
const packageDetails = await lstat(packagePath);
|
|
349
|
-
if (!packageDetails.isFile() || packageDetails.isSymbolicLink()) {
|
|
350
|
-
throw new Error("package.json is not a regular file");
|
|
351
|
-
}
|
|
352
|
-
if (packageDetails.size > MAX_PORTABLE_PACKAGE_BYTES) {
|
|
353
|
-
throw new Error(`package.json exceeds ${MAX_PORTABLE_PACKAGE_BYTES} bytes`);
|
|
354
|
-
}
|
|
355
|
-
const packageValue = JSON.parse(await readFile(packagePath, "utf8"));
|
|
356
|
-
if (typeof packageValue !== "object" ||
|
|
357
|
-
packageValue === null ||
|
|
358
|
-
Array.isArray(packageValue) ||
|
|
359
|
-
Reflect.get(packageValue, "name") !== "ragmir-portable-knowledge-base") {
|
|
360
|
-
throw new Error("package.json does not declare a Ragmir portable knowledge base");
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
catch (error) {
|
|
364
|
-
throw new Error(`Portable replacement refused for ${outputDir}: ${errorMessage(error)}. Move the existing directory aside manually before retrying.`);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
function portableDisplayName(value) {
|
|
368
|
-
const normalized = value.trim().replace(/\s+/gu, " ");
|
|
369
|
-
if (normalized.length === 0 ||
|
|
370
|
-
normalized.length > 120 ||
|
|
371
|
-
[...normalized].some((character) => {
|
|
372
|
-
const code = character.charCodeAt(0);
|
|
373
|
-
return code <= 31 || code === 127;
|
|
374
|
-
})) {
|
|
375
|
-
throw new Error("Portable knowledge-base name must contain 1 to 120 printable characters.");
|
|
376
|
-
}
|
|
377
|
-
return normalized;
|
|
378
|
-
}
|
|
379
|
-
function portableSlug(value) {
|
|
380
|
-
const slug = value
|
|
381
|
-
.normalize("NFKD")
|
|
382
|
-
.toLowerCase()
|
|
383
|
-
.replace(/[^a-z0-9]+/gu, "-")
|
|
384
|
-
.replace(/^-+|-+$/gu, "");
|
|
385
|
-
return slug.length > 0 ? slug.slice(0, 80) : "knowledge-base";
|
|
386
|
-
}
|
|
387
|
-
function assertPortableDestination(config, outputDir) {
|
|
388
|
-
const protectedPaths = [config.rawDir, config.storageDir, config.embeddingModelPath];
|
|
389
|
-
for (const protectedPath of protectedPaths) {
|
|
390
|
-
if (pathContains(protectedPath, outputDir) || pathContains(outputDir, protectedPath)) {
|
|
391
|
-
throw new Error(`Portable export destination must not overlap Ragmir source, storage, or model directories: ${outputDir}.`);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
const defaultExportRoot = path.join(config.projectRoot, RAGMIR_DIR, "exports");
|
|
395
|
-
if (pathContains(config.projectRoot, outputDir) && !pathContains(defaultExportRoot, outputDir)) {
|
|
396
|
-
throw new Error(`Portable exports inside the project must stay under ${defaultExportRoot} so indexed passages cannot be committed or ingested accidentally. Choose a destination outside the project otherwise.`);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
function assertExportableManifest(config, manifest) {
|
|
400
|
-
if (!manifest) {
|
|
401
|
-
throw new Error("Ragmir has no valid index to export. Run `rgr ingest` first.");
|
|
402
|
-
}
|
|
403
|
-
if (manifest.fileCount <= 0 || manifest.chunkCount <= 0) {
|
|
404
|
-
throw new Error("Ragmir index is empty. Ingest at least one supported source before exporting.");
|
|
405
|
-
}
|
|
406
|
-
if (!manifest.health) {
|
|
407
|
-
throw new Error("Ragmir index has no health snapshot. Run `rgr ingest --rebuild` before exporting.");
|
|
408
|
-
}
|
|
409
|
-
if (manifest.health.missingFromIndex > 0 ||
|
|
410
|
-
manifest.health.staleInIndex > 0 ||
|
|
411
|
-
manifest.health.emptyTextFiles > 0 ||
|
|
412
|
-
manifest.health.oversizedFiles > 0) {
|
|
413
|
-
throw new Error("Ragmir index coverage is incomplete. Resolve missing, stale, empty, or oversized sources before exporting.");
|
|
414
|
-
}
|
|
415
|
-
if (manifest.health.securityWarnings.length > 0) {
|
|
416
|
-
throw new Error("Ragmir index has unresolved security warnings. Resolve them before exporting.");
|
|
417
|
-
}
|
|
418
|
-
const freshnessWarning = indexFreshnessWarning(config, manifest);
|
|
419
|
-
if (freshnessWarning) {
|
|
420
|
-
throw new Error(freshnessWarning);
|
|
421
|
-
}
|
|
422
|
-
const tableName = manifest.tableName ?? config.tableName;
|
|
423
|
-
if (!SAFE_TABLE_NAME_PATTERN.test(tableName)) {
|
|
424
|
-
throw new Error("Ragmir active table name is not portable.");
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
async function buildPortableDirectory(root, config, name, signal) {
|
|
428
|
-
await ensurePrivateDirectory(root);
|
|
429
|
-
await ensurePrivateDirectory(path.join(root, RAGMIR_DIR));
|
|
430
|
-
await writePrivateJson(path.join(root, PORTABLE_CONFIG_PATH), portableConfig(config));
|
|
431
|
-
await writePrivateText(path.join(root, PORTABLE_SOURCES_PATH), "");
|
|
432
|
-
const embeddingModelIncluded = config.embeddingProvider === "transformers";
|
|
433
|
-
if (embeddingModelIncluded) {
|
|
434
|
-
const sourceModelRoot = embeddingModelArtifactRoot(config);
|
|
435
|
-
if (!(await directoryExists(sourceModelRoot))) {
|
|
436
|
-
throw new Error(`Configured embedding model directory is missing: ${sourceModelRoot}. Pull the model before exporting.`);
|
|
437
|
-
}
|
|
438
|
-
await copyPrivateTree(sourceModelRoot, path.join(root, PORTABLE_MODEL_PATH, config.embeddingModel), signal);
|
|
439
|
-
}
|
|
440
|
-
await copyPortableRuntime(root, embeddingModelIncluded, signal);
|
|
441
|
-
await writePrivateJson(path.join(root, "package.json"), portablePackageManifest());
|
|
442
|
-
await writeExecutable(path.join(root, PORTABLE_RUNNER_PATH), portableRunnerSource());
|
|
443
|
-
await writeExecutable(path.join(root, PORTABLE_CONFIGURATOR_PATH), portableConfiguratorSource());
|
|
444
|
-
await writePortableSkills(root);
|
|
445
|
-
await writePortableAdapters(root);
|
|
446
|
-
await writePrivateText(path.join(root, "README.md"), portableReadme(name, embeddingModelIncluded));
|
|
447
|
-
}
|
|
448
|
-
function portableConfig(config) {
|
|
449
|
-
return {
|
|
450
|
-
privacyProfile: config.privacyProfile,
|
|
451
|
-
retrievalProfile: config.retrievalProfile,
|
|
452
|
-
acceptedRisks: [],
|
|
453
|
-
rawDir: ".ragmir/source-unavailable",
|
|
454
|
-
storageDir: ".ragmir/storage",
|
|
455
|
-
sourcesFile: ".ragmir/sources.txt",
|
|
456
|
-
sources: [],
|
|
457
|
-
accessLogPath: ".ragmir/access.log",
|
|
458
|
-
embeddingModelPath: ".ragmir/models",
|
|
459
|
-
tableName: config.tableName,
|
|
460
|
-
embeddingProvider: config.embeddingProvider,
|
|
461
|
-
embeddingModel: config.embeddingModel,
|
|
462
|
-
embeddingModelRevision: config.embeddingModelRevision,
|
|
463
|
-
embeddingModelDigest: config.embeddingModelDigest,
|
|
464
|
-
transformersAllowRemoteModels: false,
|
|
465
|
-
redaction: config.redaction,
|
|
466
|
-
accessLog: false,
|
|
467
|
-
mcpMaxTopK: config.mcpMaxTopK,
|
|
468
|
-
mcpMaxOutputBytes: config.mcpMaxOutputBytes,
|
|
469
|
-
topK: config.topK,
|
|
470
|
-
maxChunksPerDocument: config.maxChunksPerDocument,
|
|
471
|
-
chunkSize: config.chunkSize,
|
|
472
|
-
chunkOverlap: config.chunkOverlap,
|
|
473
|
-
maxFileBytes: config.maxFileBytes,
|
|
474
|
-
ingestConcurrency: config.ingestConcurrency,
|
|
475
|
-
embeddingBatchSize: config.embeddingBatchSize,
|
|
476
|
-
sourceFingerprintMode: config.sourceFingerprintMode,
|
|
477
|
-
incrementalFailurePolicy: config.incrementalFailurePolicy,
|
|
478
|
-
hybridTextScanLimit: config.hybridTextScanLimit,
|
|
479
|
-
workloadLimits: config.workloadLimits,
|
|
480
|
-
includeExtensions: config.includeExtensions,
|
|
481
|
-
pdfOcrCommand: [],
|
|
482
|
-
pdfOcrTimeoutMs: config.pdfOcrTimeoutMs,
|
|
483
|
-
imageOcrCommand: [],
|
|
484
|
-
imageOcrTimeoutMs: config.imageOcrTimeoutMs,
|
|
485
|
-
legacyWordCommand: [],
|
|
486
|
-
legacyWordTimeoutMs: config.legacyWordTimeoutMs,
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
async function copyPortableRuntime(root, embeddingModelIncluded, signal) {
|
|
490
|
-
const sourcePackageRoot = portableSourcePackageRoot();
|
|
491
|
-
const sourceDist = path.join(sourcePackageRoot, "dist");
|
|
492
|
-
const portableEntry = path.join(sourceDist, "portable-entry.js");
|
|
493
|
-
if (!(await pathExists(portableEntry))) {
|
|
494
|
-
throw new Error("Portable runtime is not built. Build @jcode.labs/ragmir before exporting a standalone knowledge base.");
|
|
495
|
-
}
|
|
496
|
-
const runtimeRoot = path.join(root, PORTABLE_RUNTIME_PATH);
|
|
497
|
-
await copyPortableRuntimeTree(sourceDist, path.join(root, PORTABLE_RUNTIME_DIST_PATH), signal);
|
|
498
|
-
await writePrivateJson(path.join(runtimeRoot, "package.json"), {
|
|
499
|
-
name: "ragmir-portable-runtime",
|
|
500
|
-
private: true,
|
|
501
|
-
type: "module",
|
|
502
|
-
engines: { node: ">=22" },
|
|
503
|
-
});
|
|
504
|
-
const dependencies = [
|
|
505
|
-
...PORTABLE_RUNTIME_ALWAYS_DEPENDENCIES,
|
|
506
|
-
...(embeddingModelIncluded ? [PORTABLE_RUNTIME_TRANSFORMERS_DEPENDENCY] : []),
|
|
507
|
-
];
|
|
508
|
-
const copiedPackages = new Map();
|
|
509
|
-
for (const dependency of dependencies) {
|
|
510
|
-
await copyPortableRuntimePackage({
|
|
511
|
-
packageName: dependency,
|
|
512
|
-
sourceParentRoot: sourcePackageRoot,
|
|
513
|
-
targetParentRoot: path.join(root, PORTABLE_RUNTIME_PATH),
|
|
514
|
-
copiedPackages,
|
|
515
|
-
signal,
|
|
516
|
-
optional: false,
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
if (embeddingModelIncluded) {
|
|
520
|
-
await copyPortableTransformersCompatibilityRuntime(sourcePackageRoot, runtimeRoot, copiedPackages, signal);
|
|
521
|
-
await writePortableSharpStub(runtimeRoot);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
async function copyPortableTransformersCompatibilityRuntime(sourcePackageRoot, runtimeRoot, copiedPackages, signal) {
|
|
525
|
-
const transformersRoot = await installedPackageRoot(sourcePackageRoot, PORTABLE_RUNTIME_TRANSFORMERS_DEPENDENCY, false);
|
|
526
|
-
if (!transformersRoot) {
|
|
527
|
-
throw new Error("Portable Transformers runtime is unavailable.");
|
|
528
|
-
}
|
|
529
|
-
const onnxRuntimeRoot = await installedPackageRoot(transformersRoot, "onnxruntime-node", false);
|
|
530
|
-
if (!onnxRuntimeRoot) {
|
|
531
|
-
throw new Error("Portable ONNX runtime is unavailable.");
|
|
532
|
-
}
|
|
533
|
-
await copyPortableRuntimePackage({
|
|
534
|
-
packageName: "onnxruntime-common",
|
|
535
|
-
sourceParentRoot: onnxRuntimeRoot,
|
|
536
|
-
targetParentRoot: runtimeRoot,
|
|
537
|
-
copiedPackages,
|
|
538
|
-
signal,
|
|
539
|
-
optional: false,
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
async function writePortableSharpStub(runtimeRoot) {
|
|
543
|
-
const packageRoot = path.join(runtimeRoot, "node_modules", "sharp");
|
|
544
|
-
await writePrivateJson(path.join(packageRoot, "package.json"), {
|
|
545
|
-
name: "sharp",
|
|
546
|
-
private: true,
|
|
547
|
-
type: "module",
|
|
548
|
-
exports: "./index.js",
|
|
549
|
-
});
|
|
550
|
-
await writePrivateText(path.join(packageRoot, "index.js"), 'const sharp = () => { throw new Error("Image processing is unavailable in this text-retrieval bundle.") }\nexport default sharp\n');
|
|
551
|
-
}
|
|
552
|
-
function portableSourcePackageRoot() {
|
|
553
|
-
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
554
|
-
}
|
|
555
|
-
async function copyPortableRuntimePackage(options) {
|
|
556
|
-
throwIfAborted(options.signal);
|
|
557
|
-
if (PORTABLE_RUNTIME_OMITTED_DEPENDENCIES.has(options.packageName)) {
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
const sourceRoot = await installedPackageRoot(options.sourceParentRoot, options.packageName, options.optional);
|
|
561
|
-
if (sourceRoot === null) {
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
const targetRoot = path.join(options.targetParentRoot, "node_modules", ...options.packageName.split("/"));
|
|
565
|
-
const existingSource = options.copiedPackages.get(targetRoot);
|
|
566
|
-
if (existingSource) {
|
|
567
|
-
if (existingSource !== sourceRoot) {
|
|
568
|
-
throw new Error(`Portable runtime cannot embed conflicting installed versions of ${options.packageName} at ${targetRoot}.`);
|
|
569
|
-
}
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
options.copiedPackages.set(targetRoot, sourceRoot);
|
|
573
|
-
const packageManifestPath = path.join(sourceRoot, "package.json");
|
|
574
|
-
const packageManifest = JSON.parse(await readFile(packageManifestPath, "utf8"));
|
|
575
|
-
const packageDetails = runtimePackageDetails(packageManifest, options.packageName);
|
|
576
|
-
await copyPortableRuntimeTree(sourceRoot, targetRoot, options.signal, options.packageName);
|
|
577
|
-
const dependencies = [
|
|
578
|
-
...Object.keys(packageDetails.dependencies),
|
|
579
|
-
...Object.keys(packageDetails.optionalDependencies),
|
|
580
|
-
].sort((left, right) => left.localeCompare(right));
|
|
581
|
-
for (const dependency of dependencies) {
|
|
582
|
-
await copyPortableRuntimePackage({
|
|
583
|
-
...options,
|
|
584
|
-
packageName: dependency,
|
|
585
|
-
sourceParentRoot: sourceRoot,
|
|
586
|
-
targetParentRoot: targetRoot,
|
|
587
|
-
optional: dependency in packageDetails.optionalDependencies,
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
async function installedPackageRoot(sourceParentRoot, packageName, optional) {
|
|
592
|
-
let current = path.resolve(sourceParentRoot);
|
|
593
|
-
while (true) {
|
|
594
|
-
const candidate = path.join(current, "node_modules", ...packageName.split("/"));
|
|
595
|
-
try {
|
|
596
|
-
return await realpath(candidate);
|
|
597
|
-
}
|
|
598
|
-
catch (error) {
|
|
599
|
-
if (!isMissingPathError(error)) {
|
|
600
|
-
throw new Error(`Portable runtime dependency ${packageName} is unavailable from ${sourceParentRoot}: ${errorMessage(error)}`);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
const parent = path.dirname(current);
|
|
604
|
-
if (parent === current) {
|
|
605
|
-
break;
|
|
606
|
-
}
|
|
607
|
-
current = parent;
|
|
608
|
-
}
|
|
609
|
-
if (optional) {
|
|
610
|
-
return null;
|
|
611
|
-
}
|
|
612
|
-
throw new Error(`Portable runtime dependency ${packageName} is unavailable from ${sourceParentRoot}.`);
|
|
613
|
-
}
|
|
614
|
-
function runtimePackageDetails(value, expectedName) {
|
|
615
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
616
|
-
throw new Error(`Portable runtime package metadata is invalid for ${expectedName}.`);
|
|
617
|
-
}
|
|
618
|
-
if (Reflect.get(value, "name") !== expectedName) {
|
|
619
|
-
throw new Error(`Portable runtime package metadata has an unexpected name for ${expectedName}.`);
|
|
620
|
-
}
|
|
621
|
-
return {
|
|
622
|
-
dependencies: runtimeDependencyRecord(Reflect.get(value, "dependencies"), expectedName),
|
|
623
|
-
optionalDependencies: runtimeDependencyRecord(Reflect.get(value, "optionalDependencies"), expectedName),
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
function runtimeDependencyRecord(value, packageName) {
|
|
627
|
-
if (value === undefined) {
|
|
628
|
-
return {};
|
|
629
|
-
}
|
|
630
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
631
|
-
throw new Error(`Portable runtime dependencies are invalid for ${packageName}.`);
|
|
632
|
-
}
|
|
633
|
-
const dependencies = {};
|
|
634
|
-
for (const [name, version] of Object.entries(value)) {
|
|
635
|
-
if (typeof version !== "string") {
|
|
636
|
-
throw new Error(`Portable runtime dependency ${name} is invalid for ${packageName}.`);
|
|
637
|
-
}
|
|
638
|
-
dependencies[name] = version;
|
|
639
|
-
}
|
|
640
|
-
return dependencies;
|
|
641
|
-
}
|
|
642
|
-
async function copyPortableRuntimeTree(source, destination, signal, packageName = null, relativePath = "") {
|
|
643
|
-
throwIfAborted(signal);
|
|
644
|
-
const details = await lstat(source);
|
|
645
|
-
if (details.isSymbolicLink()) {
|
|
646
|
-
throw new Error(`Portable runtime does not follow symbolic links: ${source}.`);
|
|
647
|
-
}
|
|
648
|
-
if (details.isDirectory()) {
|
|
649
|
-
if (relativePath === "node_modules") {
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
|
-
await ensurePrivateDirectory(destination);
|
|
653
|
-
const entries = await readdir(source, { withFileTypes: true });
|
|
654
|
-
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
655
|
-
const nextRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name;
|
|
656
|
-
if (shouldSkipPortableRuntimePath(packageName, nextRelativePath)) {
|
|
657
|
-
continue;
|
|
658
|
-
}
|
|
659
|
-
await copyPortableRuntimeTree(path.join(source, entry.name), path.join(destination, entry.name), signal, packageName, nextRelativePath);
|
|
660
|
-
}
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
if (!details.isFile()) {
|
|
664
|
-
throw new Error(`Portable runtime supports regular files only: ${source}.`);
|
|
665
|
-
}
|
|
666
|
-
await ensurePrivateDirectory(path.dirname(destination));
|
|
667
|
-
await copyFile(source, destination);
|
|
668
|
-
await hardenPrivateFile(destination);
|
|
669
|
-
}
|
|
670
|
-
function shouldSkipPortableRuntimePath(packageName, relativePath) {
|
|
671
|
-
if (relativePath === "node_modules" || relativePath.startsWith(`node_modules${path.sep}`)) {
|
|
672
|
-
return true;
|
|
673
|
-
}
|
|
674
|
-
if (packageName !== "onnxruntime-node") {
|
|
675
|
-
return false;
|
|
676
|
-
}
|
|
677
|
-
const normalized = relativePath.split(path.sep).join("/");
|
|
678
|
-
const platformRoot = ONNX_RUNTIME_PLATFORM_ROOT.split(path.sep).join("/");
|
|
679
|
-
return (normalized.startsWith("bin/napi-v6/") &&
|
|
680
|
-
!platformRoot.startsWith(normalized) &&
|
|
681
|
-
!normalized.startsWith(`${platformRoot}/`));
|
|
682
|
-
}
|
|
683
|
-
function isMissingPathError(error) {
|
|
684
|
-
return typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT";
|
|
685
|
-
}
|
|
686
|
-
async function copyPortableStorage(root, config, manifest, signal) {
|
|
687
|
-
const targetStorage = path.join(root, PORTABLE_STORAGE_PATH);
|
|
688
|
-
await ensurePrivateDirectory(targetStorage);
|
|
689
|
-
const tableName = manifest.tableName ?? config.tableName;
|
|
690
|
-
const tableDirectoryName = `${tableName}.lance`;
|
|
691
|
-
await copyPrivateTree(path.join(config.storageDir, tableDirectoryName), path.join(targetStorage, tableDirectoryName), signal);
|
|
692
|
-
const databaseManifestPath = path.join(config.storageDir, "__manifest");
|
|
693
|
-
if (await pathExists(databaseManifestPath)) {
|
|
694
|
-
await copyPrivateTree(databaseManifestPath, path.join(targetStorage, "__manifest"), signal);
|
|
695
|
-
}
|
|
696
|
-
const sourceManifestPath = path.join(config.storageDir, INDEX_MANIFEST_FILENAME);
|
|
697
|
-
await copyPrivateTree(sourceManifestPath, path.join(targetStorage, INDEX_MANIFEST_FILENAME), signal);
|
|
698
|
-
const pointer = persistedManifestPointer(await readFile(sourceManifestPath, "utf8"));
|
|
699
|
-
if (pointer.indexedFilesSnapshot) {
|
|
700
|
-
await copyPrivateTree(path.join(config.storageDir, pointer.indexedFilesSnapshot), path.join(targetStorage, pointer.indexedFilesSnapshot), signal);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
function persistedManifestPointer(raw) {
|
|
704
|
-
const value = JSON.parse(raw);
|
|
705
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
706
|
-
throw new Error("Ragmir persisted index manifest is invalid.");
|
|
707
|
-
}
|
|
708
|
-
const snapshot = Reflect.get(value, "indexedFilesSnapshot");
|
|
709
|
-
if (snapshot === undefined) {
|
|
710
|
-
return {};
|
|
711
|
-
}
|
|
712
|
-
if (typeof snapshot !== "string" || !INDEX_FILES_SNAPSHOT_PATTERN.test(snapshot)) {
|
|
713
|
-
throw new Error("Ragmir index file snapshot path is not portable.");
|
|
714
|
-
}
|
|
715
|
-
return { indexedFilesSnapshot: snapshot };
|
|
716
|
-
}
|
|
717
|
-
async function createPortableManifest(root, config, indexManifest, name, signal) {
|
|
718
|
-
const files = await portableFileInventory(root, signal);
|
|
719
|
-
const tableName = indexManifest.tableName ?? config.tableName;
|
|
720
|
-
return portableKnowledgeBaseManifestSchema.parse({
|
|
721
|
-
schemaVersion: PORTABLE_SCHEMA_VERSION,
|
|
722
|
-
kind: PORTABLE_KIND,
|
|
723
|
-
createdAt: new Date().toISOString(),
|
|
724
|
-
name,
|
|
725
|
-
ragmirVersion: VERSION,
|
|
726
|
-
runtime: {
|
|
727
|
-
node: ">=22",
|
|
728
|
-
package: "@jcode.labs/ragmir",
|
|
729
|
-
packageVersion: VERSION,
|
|
730
|
-
resolution: "embedded-platform-runtime",
|
|
731
|
-
exportedOn: { platform: process.platform, arch: process.arch },
|
|
732
|
-
},
|
|
733
|
-
knowledgeBase: {
|
|
734
|
-
corpusFingerprint: indexManifest.corpusFingerprint ?? null,
|
|
735
|
-
indexedFiles: indexManifest.fileCount,
|
|
736
|
-
indexedChunks: indexManifest.chunkCount,
|
|
737
|
-
embeddingProvider: config.embeddingProvider,
|
|
738
|
-
embeddingModel: config.embeddingModel,
|
|
739
|
-
embeddingModelRevision: config.embeddingModelRevision,
|
|
740
|
-
embeddingModelDigest: config.embeddingModelDigest,
|
|
741
|
-
indexSchemaVersion: indexManifest.schemaVersion,
|
|
742
|
-
tableName,
|
|
743
|
-
},
|
|
744
|
-
contents: {
|
|
745
|
-
rawSourcesIncluded: false,
|
|
746
|
-
indexedTextIncluded: true,
|
|
747
|
-
accessLogsIncluded: false,
|
|
748
|
-
embeddingModelIncluded: config.embeddingProvider === "transformers",
|
|
749
|
-
skills: [...PORTABLE_SKILL_NAMES],
|
|
750
|
-
adapters: [...PORTABLE_ADAPTER_FILENAMES],
|
|
751
|
-
},
|
|
752
|
-
files,
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
async function portableFileInventory(root, signal) {
|
|
756
|
-
const relativeFiles = await walkRegularFiles(root, root, signal);
|
|
757
|
-
const inventory = [];
|
|
758
|
-
for (const relativePath of relativeFiles) {
|
|
759
|
-
throwIfAborted(signal);
|
|
760
|
-
if (relativePath === PORTABLE_MANIFEST_FILENAME) {
|
|
761
|
-
continue;
|
|
762
|
-
}
|
|
763
|
-
const filePath = path.join(root, ...relativePath.split("/"));
|
|
764
|
-
const details = await stat(filePath);
|
|
765
|
-
inventory.push({ path: relativePath, bytes: details.size, sha256: await hashFile(filePath) });
|
|
766
|
-
}
|
|
767
|
-
return inventory.sort((left, right) => left.path.localeCompare(right.path));
|
|
768
|
-
}
|
|
769
|
-
async function walkRegularFiles(root, directory, signal) {
|
|
770
|
-
throwIfAborted(signal);
|
|
771
|
-
const entries = await readdir(directory, { withFileTypes: true });
|
|
772
|
-
const files = [];
|
|
773
|
-
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
774
|
-
throwIfAborted(signal);
|
|
775
|
-
const entryPath = path.join(directory, entry.name);
|
|
776
|
-
if (entry.isSymbolicLink()) {
|
|
777
|
-
throw new Error(`Portable exports do not follow symbolic links: ${entryPath}.`);
|
|
778
|
-
}
|
|
779
|
-
if (entry.isDirectory()) {
|
|
780
|
-
files.push(...(await walkRegularFiles(root, entryPath, signal)));
|
|
781
|
-
continue;
|
|
782
|
-
}
|
|
783
|
-
if (!entry.isFile()) {
|
|
784
|
-
throw new Error(`Portable exports support regular files only: ${entryPath}.`);
|
|
785
|
-
}
|
|
786
|
-
files.push(path.relative(root, entryPath).split(path.sep).join("/"));
|
|
787
|
-
}
|
|
788
|
-
return files;
|
|
789
|
-
}
|
|
790
|
-
async function copyPrivateTree(source, destination, signal) {
|
|
791
|
-
throwIfAborted(signal);
|
|
792
|
-
const details = await lstat(source);
|
|
793
|
-
if (details.isSymbolicLink()) {
|
|
794
|
-
throw new Error(`Portable exports do not follow symbolic links: ${source}.`);
|
|
795
|
-
}
|
|
796
|
-
if (details.isDirectory()) {
|
|
797
|
-
await ensurePrivateDirectory(destination);
|
|
798
|
-
const entries = await readdir(source, { withFileTypes: true });
|
|
799
|
-
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
800
|
-
await copyPrivateTree(path.join(source, entry.name), path.join(destination, entry.name), signal);
|
|
801
|
-
}
|
|
802
|
-
return;
|
|
803
|
-
}
|
|
804
|
-
if (!details.isFile()) {
|
|
805
|
-
throw new Error(`Portable exports support regular files only: ${source}.`);
|
|
806
|
-
}
|
|
807
|
-
await ensurePrivateDirectory(path.dirname(destination));
|
|
808
|
-
await copyFile(source, destination);
|
|
809
|
-
await hardenPrivateFile(destination);
|
|
810
|
-
}
|
|
811
|
-
async function writePortableSkills(root) {
|
|
812
|
-
const skillRoot = path.join(root, "skills");
|
|
813
|
-
await writePrivateText(path.join(skillRoot, "ragmir-portable", "SKILL.md"), portableRetrievalSkill());
|
|
814
|
-
await writePrivateText(path.join(skillRoot, "ragmir-decision-evidence", "SKILL.md"), portableDecisionSkill());
|
|
815
|
-
}
|
|
816
|
-
async function writePortableAdapters(root) {
|
|
817
|
-
const adapterRoot = path.join(root, "adapters");
|
|
818
|
-
for (const [filename, contents] of Object.entries(portableAdapterFiles())) {
|
|
819
|
-
await writePrivateText(path.join(adapterRoot, filename), contents);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
function portableAdapterFiles() {
|
|
823
|
-
const runner = `${PORTABLE_ROOT_PLACEHOLDER}/bin/rgr.cjs`;
|
|
824
|
-
const env = {
|
|
825
|
-
[RAGMIR_PROJECT_ROOT_ENV]: PORTABLE_ROOT_PLACEHOLDER,
|
|
826
|
-
[RAGMIR_PORTABLE_READ_ONLY_ENV]: "1",
|
|
827
|
-
};
|
|
828
|
-
const generic = {
|
|
829
|
-
mcpServers: {
|
|
830
|
-
ragmir: { command: "node", args: [runner, "serve-mcp"], cwd: PORTABLE_ROOT_PLACEHOLDER, env },
|
|
831
|
-
},
|
|
832
|
-
};
|
|
833
|
-
const openclaw = {
|
|
834
|
-
command: "node",
|
|
835
|
-
args: [runner, "serve-mcp"],
|
|
836
|
-
cwd: PORTABLE_ROOT_PLACEHOLDER,
|
|
837
|
-
env,
|
|
838
|
-
toolFilter: {
|
|
839
|
-
include: [
|
|
840
|
-
"ragmir_status",
|
|
841
|
-
"ragmir_route_prompt",
|
|
842
|
-
"ragmir_search",
|
|
843
|
-
"ragmir_ask",
|
|
844
|
-
"ragmir_expand",
|
|
845
|
-
],
|
|
846
|
-
},
|
|
847
|
-
};
|
|
848
|
-
const claude = { type: "stdio", command: "node", args: [runner, "serve-mcp"], env };
|
|
849
|
-
const opencode = {
|
|
850
|
-
$schema: "https://opencode.ai/config.json",
|
|
851
|
-
mcp: {
|
|
852
|
-
ragmir: {
|
|
853
|
-
type: "local",
|
|
854
|
-
command: ["node", runner, "serve-mcp"],
|
|
855
|
-
enabled: true,
|
|
856
|
-
environment: env,
|
|
857
|
-
},
|
|
858
|
-
},
|
|
859
|
-
};
|
|
860
|
-
const codex = `[mcp_servers.ragmir]
|
|
861
|
-
command = "node"
|
|
862
|
-
args = [${JSON.stringify(runner)}, "serve-mcp"]
|
|
863
|
-
cwd = ${JSON.stringify(PORTABLE_ROOT_PLACEHOLDER)}
|
|
864
|
-
|
|
865
|
-
[[skills.config]]
|
|
866
|
-
path = ${JSON.stringify(`${PORTABLE_ROOT_PLACEHOLDER}/skills/ragmir-portable`)}
|
|
867
|
-
enabled = true
|
|
868
|
-
|
|
869
|
-
[[skills.config]]
|
|
870
|
-
path = ${JSON.stringify(`${PORTABLE_ROOT_PLACEHOLDER}/skills/ragmir-decision-evidence`)}
|
|
871
|
-
enabled = true
|
|
872
|
-
`;
|
|
873
|
-
return {
|
|
874
|
-
"README.md": portableAdapterReadme(),
|
|
875
|
-
"generic-mcp.json": `${JSON.stringify(generic, null, 2)}\n`,
|
|
876
|
-
"openclaw-mcp-server.json": `${JSON.stringify(openclaw, null, 2)}\n`,
|
|
877
|
-
"claude-mcp-server.json": `${JSON.stringify(claude, null, 2)}\n`,
|
|
878
|
-
"codex-mcp.toml": codex,
|
|
879
|
-
"kimi-mcp.json": `${JSON.stringify(generic, null, 2)}\n`,
|
|
880
|
-
"opencode.jsonc": `${JSON.stringify(opencode, null, 2)}\n`,
|
|
881
|
-
"cline-mcp.json": `${JSON.stringify(generic, null, 2)}\n`,
|
|
882
|
-
"stdio-command.txt": `node ${runner} serve-mcp\n`,
|
|
883
|
-
};
|
|
884
|
-
}
|
|
885
|
-
function portablePackageManifest() {
|
|
886
|
-
return {
|
|
887
|
-
name: "ragmir-portable-knowledge-base",
|
|
888
|
-
private: true,
|
|
889
|
-
type: "module",
|
|
890
|
-
engines: { node: ">=22" },
|
|
891
|
-
ragmirRuntime: "embedded-platform-runtime",
|
|
892
|
-
};
|
|
893
|
-
}
|
|
894
|
-
function portableRunnerSource() {
|
|
895
|
-
return `#!/usr/bin/env node
|
|
896
|
-
const { existsSync, readFileSync } = require("node:fs")
|
|
897
|
-
const path = require("node:path")
|
|
898
|
-
const { pathToFileURL } = require("node:url")
|
|
899
|
-
|
|
900
|
-
const ROOT = path.resolve(__dirname, "..")
|
|
901
|
-
const ALLOWED_COMMANDS = new Set([
|
|
902
|
-
"ask",
|
|
903
|
-
"doctor",
|
|
904
|
-
"route-prompt",
|
|
905
|
-
"search",
|
|
906
|
-
"serve-mcp",
|
|
907
|
-
"status",
|
|
908
|
-
])
|
|
909
|
-
const args = process.argv.slice(2)
|
|
910
|
-
|
|
911
|
-
function reject(message) {
|
|
912
|
-
console.error(message)
|
|
913
|
-
process.exit(1)
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
function assertEmbeddedRuntimePlatform() {
|
|
917
|
-
let runtime
|
|
918
|
-
try {
|
|
919
|
-
runtime = JSON.parse(readFileSync(path.join(ROOT, "manifest.json"), "utf8")).runtime
|
|
920
|
-
} catch {
|
|
921
|
-
reject("Portable manifest is missing or invalid. Run portable verify before using this bundle.")
|
|
922
|
-
}
|
|
923
|
-
if (!runtime || runtime.resolution !== "embedded-platform-runtime") return
|
|
924
|
-
if (runtime.exportedOn?.platform === process.platform && runtime.exportedOn?.arch === process.arch) return
|
|
925
|
-
reject(
|
|
926
|
-
"This bundle targets " + runtime.exportedOn?.platform + "/" + runtime.exportedOn?.arch +
|
|
927
|
-
", but this host is " + process.platform + "/" + process.arch +
|
|
928
|
-
". Re-export it on the destination platform before querying it.",
|
|
929
|
-
)
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
if (args.length === 0) {
|
|
933
|
-
reject("Usage: node bin/rgr.cjs <search|ask|status|doctor|route-prompt|serve-mcp|portable verify> [...args]")
|
|
934
|
-
}
|
|
935
|
-
if (args[0] === "portable") {
|
|
936
|
-
if (args[1] !== "verify") reject("Portable bundles allow only the portable verify subcommand.")
|
|
937
|
-
} else if (args[0] !== "--help" && args[0] !== "--version" && !ALLOWED_COMMANDS.has(args[0])) {
|
|
938
|
-
reject("This frozen bundle blocks index and source mutation commands.")
|
|
939
|
-
}
|
|
940
|
-
if (args[0] === "doctor" && args.includes("--fix")) {
|
|
941
|
-
reject("This frozen bundle blocks doctor --fix.")
|
|
942
|
-
}
|
|
943
|
-
const cliPath = path.join(ROOT, "runtime", "dist", "portable-entry.js")
|
|
944
|
-
if (!existsSync(cliPath)) {
|
|
945
|
-
reject("Portable runtime is missing. Re-export this knowledge base from its authoritative source.")
|
|
946
|
-
}
|
|
947
|
-
if (args[0] !== "portable" && args[0] !== "--help" && args[0] !== "--version") {
|
|
948
|
-
assertEmbeddedRuntimePlatform()
|
|
949
|
-
}
|
|
950
|
-
process.chdir(ROOT)
|
|
951
|
-
process.env.${RAGMIR_PROJECT_ROOT_ENV} = ROOT
|
|
952
|
-
process.env.${RAGMIR_PORTABLE_READ_ONLY_ENV} = "1"
|
|
953
|
-
|
|
954
|
-
process.argv = [process.execPath, cliPath, ...args]
|
|
955
|
-
import(pathToFileURL(cliPath).href).catch((error) => {
|
|
956
|
-
console.error(error instanceof Error ? error.message : String(error))
|
|
957
|
-
process.exitCode = 1
|
|
958
|
-
})
|
|
959
|
-
`;
|
|
960
|
-
}
|
|
961
|
-
function portableConfiguratorSource() {
|
|
962
|
-
return `#!/usr/bin/env node
|
|
963
|
-
const path = require("node:path")
|
|
964
|
-
|
|
965
|
-
const root = path.resolve(__dirname, "..")
|
|
966
|
-
const runner = path.join(root, "bin", "rgr.cjs")
|
|
967
|
-
const target = (process.argv[2] || "generic").toLowerCase()
|
|
968
|
-
const env = {
|
|
969
|
-
${RAGMIR_PROJECT_ROOT_ENV}: root,
|
|
970
|
-
${RAGMIR_PORTABLE_READ_ONLY_ENV}: "1",
|
|
971
|
-
}
|
|
972
|
-
const server = { command: process.execPath, args: [runner, "serve-mcp"], cwd: root, env }
|
|
973
|
-
const openclawServer = {
|
|
974
|
-
...server,
|
|
975
|
-
toolFilter: {
|
|
976
|
-
include: [
|
|
977
|
-
"ragmir_status",
|
|
978
|
-
"ragmir_route_prompt",
|
|
979
|
-
"ragmir_search",
|
|
980
|
-
"ragmir_ask",
|
|
981
|
-
"ragmir_expand",
|
|
982
|
-
],
|
|
983
|
-
},
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
function json(value) {
|
|
987
|
-
process.stdout.write(JSON.stringify(value, null, 2) + "\\n")
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
if (target === "--list" || target === "list") {
|
|
991
|
-
process.stdout.write("generic\\nopenclaw\\nclaude\\ncodex\\nkimi\\nopencode\\ncline\\n")
|
|
992
|
-
} else if (target === "openclaw") {
|
|
993
|
-
json(openclawServer)
|
|
994
|
-
} else if (target === "claude") {
|
|
995
|
-
json({ type: "stdio", command: server.command, args: server.args, env: server.env })
|
|
996
|
-
} else if (target === "codex") {
|
|
997
|
-
const quote = (value) => JSON.stringify(value)
|
|
998
|
-
process.stdout.write(
|
|
999
|
-
"[mcp_servers.ragmir]\\n" +
|
|
1000
|
-
"command = " + quote(server.command) + "\\n" +
|
|
1001
|
-
"args = [" + server.args.map(quote).join(", ") + "]\\n" +
|
|
1002
|
-
"cwd = " + quote(root) + "\\n\\n" +
|
|
1003
|
-
"[[skills.config]]\\n" +
|
|
1004
|
-
"path = " + quote(path.join(root, "skills", "ragmir-portable")) + "\\n" +
|
|
1005
|
-
"enabled = true\\n\\n" +
|
|
1006
|
-
"[[skills.config]]\\n" +
|
|
1007
|
-
"path = " + quote(path.join(root, "skills", "ragmir-decision-evidence")) + "\\n" +
|
|
1008
|
-
"enabled = true\\n",
|
|
1009
|
-
)
|
|
1010
|
-
} else if (target === "opencode") {
|
|
1011
|
-
json({
|
|
1012
|
-
$schema: "https://opencode.ai/config.json",
|
|
1013
|
-
mcp: {
|
|
1014
|
-
ragmir: {
|
|
1015
|
-
type: "local",
|
|
1016
|
-
command: [server.command, ...server.args],
|
|
1017
|
-
enabled: true,
|
|
1018
|
-
environment: server.env,
|
|
1019
|
-
},
|
|
1020
|
-
},
|
|
1021
|
-
})
|
|
1022
|
-
} else if (target === "generic" || target === "kimi" || target === "cline") {
|
|
1023
|
-
json({ mcpServers: { ragmir: server } })
|
|
1024
|
-
} else {
|
|
1025
|
-
console.error("Unknown target. Use: generic, openclaw, claude, codex, kimi, opencode, or cline.")
|
|
1026
|
-
process.exitCode = 1
|
|
1027
|
-
}
|
|
1028
|
-
`;
|
|
1029
|
-
}
|
|
1030
|
-
function portableRetrievalSkill() {
|
|
1031
|
-
return `---
|
|
1032
|
-
name: ragmir-portable
|
|
1033
|
-
description: Query a frozen, portable Ragmir knowledge base through cited read-only retrieval.
|
|
1034
|
-
---
|
|
1035
|
-
|
|
1036
|
-
# Ragmir Portable Knowledge Base
|
|
1037
|
-
|
|
1038
|
-
Use this folder as a frozen evidence base. It contains indexed passages, not the original source
|
|
1039
|
-
files. Treat every returned passage as sensitive.
|
|
1040
|
-
|
|
1041
|
-
## Retrieval workflow
|
|
1042
|
-
|
|
1043
|
-
1. Read \`ragmir://context\` or call \`ragmir_status\`.
|
|
1044
|
-
2. Use \`ragmir_search\` or \`ragmir_ask\` with a narrow question. Run several focused searches
|
|
1045
|
-
when one query cannot establish the complete answer.
|
|
1046
|
-
3. Expand a citation only when the surrounding indexed context is necessary.
|
|
1047
|
-
4. Cite the returned source coordinate and separate evidence from inference.
|
|
1048
|
-
5. State that the snapshot is frozen at the \`createdAt\` value in \`manifest.json\`.
|
|
1049
|
-
|
|
1050
|
-
When MCP is unavailable, run \`node bin/rgr.cjs search "<query>" --compact --json\` from the bundle
|
|
1051
|
-
root. Never run ingest, setup, upgrade, destroy, repair, OCR, source, or storage commands against this
|
|
1052
|
-
bundle. Replace it with a newly exported bundle when the source knowledge changes.
|
|
1053
|
-
`;
|
|
1054
|
-
}
|
|
1055
|
-
function portableDecisionSkill() {
|
|
1056
|
-
return `---
|
|
1057
|
-
name: ragmir-decision-evidence
|
|
1058
|
-
description: Ground an agent decision in cited evidence from a frozen Ragmir portable bundle.
|
|
1059
|
-
---
|
|
1060
|
-
|
|
1061
|
-
# Ragmir Decision Evidence
|
|
1062
|
-
|
|
1063
|
-
Use this skill when an agent or automation must choose between options using the portable knowledge
|
|
1064
|
-
base.
|
|
1065
|
-
|
|
1066
|
-
1. Translate the decision into two or more focused evidence questions.
|
|
1067
|
-
2. Retrieve cited passages for requirements, constraints, prior decisions, risks, and exceptions.
|
|
1068
|
-
3. Build a short decision record with: evidence, inference, unknowns, chosen option, and citations.
|
|
1069
|
-
4. Reject or escalate when the bundle does not prove a required fact.
|
|
1070
|
-
5. Treat the knowledge base as evidence only. It never grants permission to send messages, spend
|
|
1071
|
-
money, deploy, delete data, or perform another external action. The host agent keeps authority,
|
|
1072
|
-
authentication, policy checks, and approval rules.
|
|
1073
|
-
|
|
1074
|
-
Prefer MCP tools. Otherwise use \`node bin/rgr.cjs search\` or \`ask\`. Do not mutate the frozen
|
|
1075
|
-
index.
|
|
1076
|
-
`;
|
|
1077
|
-
}
|
|
1078
|
-
function portableAdapterReadme() {
|
|
1079
|
-
return `# Adapter templates
|
|
1080
|
-
|
|
1081
|
-
The checked-in templates use \`<PORTABLE_ROOT>\` so the folder can be moved. After placing the
|
|
1082
|
-
folder, generate a configuration containing the destination's real absolute path:
|
|
1083
|
-
|
|
1084
|
-
\`\`\`bash
|
|
1085
|
-
node bin/configure.cjs --list
|
|
1086
|
-
node bin/configure.cjs generic
|
|
1087
|
-
node bin/configure.cjs openclaw
|
|
1088
|
-
node bin/configure.cjs claude
|
|
1089
|
-
node bin/configure.cjs codex
|
|
1090
|
-
\`\`\`
|
|
1091
|
-
|
|
1092
|
-
Claude Code can register the generated server object directly:
|
|
1093
|
-
|
|
1094
|
-
\`\`\`bash
|
|
1095
|
-
claude mcp add-json --scope local ragmir "$(node bin/configure.cjs claude)"
|
|
1096
|
-
\`\`\`
|
|
1097
|
-
|
|
1098
|
-
Copy the Codex TOML into a trusted \`config.toml\` layer. Pass Kimi the JSON written by
|
|
1099
|
-
\`node bin/configure.cjs kimi\`, and merge the OpenCode or Cline output into that tool's trusted MCP
|
|
1100
|
-
configuration. Register the dedicated OpenClaw server object, then probe its local MCP connection:
|
|
1101
|
-
|
|
1102
|
-
\`\`\`bash
|
|
1103
|
-
openclaw mcp set ragmir "$(node bin/configure.cjs openclaw)"
|
|
1104
|
-
openclaw mcp doctor ragmir --probe
|
|
1105
|
-
\`\`\`
|
|
1106
|
-
|
|
1107
|
-
Use \`generic\` for Hermes or another host that accepts a local stdio MCP server. n8n or a custom
|
|
1108
|
-
service can invoke the read-only CLI with an argument array when MCP is not available. Do not
|
|
1109
|
-
concatenate untrusted queries into a shell command.
|
|
1110
|
-
|
|
1111
|
-
Ragmir does not open an HTTP port. A network-facing host owns transport security, authentication,
|
|
1112
|
-
authorization, rate limits, tool permissions, and action approvals.
|
|
1113
|
-
`;
|
|
1114
|
-
}
|
|
1115
|
-
function portableReadme(name, embeddingModelIncluded) {
|
|
1116
|
-
const safeName = name.replaceAll("`", "'");
|
|
1117
|
-
const modelNote = embeddingModelIncluded
|
|
1118
|
-
? "The local embedding model required by this index is included under `.ragmir/models/`."
|
|
1119
|
-
: "This index uses local-hash retrieval and needs no embedding-model download.";
|
|
1120
|
-
return `# ${safeName}
|
|
1121
|
-
|
|
1122
|
-
This is a frozen Ragmir knowledge-base bundle. You can move the whole directory, verify it, then
|
|
1123
|
-
connect a compatible agent or automation through MCP stdio or the read-only CLI. Raw source files
|
|
1124
|
-
and access logs are not included. Indexed passages are included and remain sensitive.
|
|
1125
|
-
|
|
1126
|
-
${modelNote}
|
|
1127
|
-
|
|
1128
|
-
## Requirements
|
|
1129
|
-
|
|
1130
|
-
- Node.js 22 or later
|
|
1131
|
-
- The same operating system and CPU architecture recorded in \`manifest.json\`
|
|
1132
|
-
|
|
1133
|
-
The runtime and its native retrieval dependencies are embedded in this folder. Querying it needs no
|
|
1134
|
-
package-manager install, registry access, or source-project dependency. The launcher never uses an
|
|
1135
|
-
absolute path from the export machine. Re-export the bundle on the destination platform when its
|
|
1136
|
-
operating system or CPU architecture differs.
|
|
1137
|
-
|
|
1138
|
-
## Verify and query
|
|
1139
|
-
|
|
1140
|
-
\`\`\`bash
|
|
1141
|
-
node bin/rgr.cjs portable verify . --json
|
|
1142
|
-
node bin/rgr.cjs search "Which evidence governs this decision?" --compact --json
|
|
1143
|
-
\`\`\`
|
|
1144
|
-
|
|
1145
|
-
The launcher blocks writer and repair commands. This folder is replaced, not updated, when the
|
|
1146
|
-
source knowledge changes.
|
|
1147
|
-
|
|
1148
|
-
## Replace this bundle safely
|
|
1149
|
-
|
|
1150
|
-
When the authoritative source project can write this destination, export the next revision to the
|
|
1151
|
-
same stable path with \`--replace\`:
|
|
1152
|
-
|
|
1153
|
-
\`\`\`bash
|
|
1154
|
-
rgr portable export --output /absolute/stable/destination --replace
|
|
1155
|
-
\`\`\`
|
|
1156
|
-
|
|
1157
|
-
Ragmir verifies the new export before switching the path and preserves this revision as a
|
|
1158
|
-
timestamped sibling reported in \`previousOutputDir\`. Restart long-running consumers, verify the
|
|
1159
|
-
stable path, and run a representative query before retiring that backup. For a remote destination,
|
|
1160
|
-
transfer the new folder beside this one, verify it, stop or drain consumers, rename this folder to a
|
|
1161
|
-
backup, rename the new folder to the stable path, then restart and verify again. Never delete the
|
|
1162
|
-
active folder before its replacement is ready.
|
|
1163
|
-
|
|
1164
|
-
## Connect an agent
|
|
1165
|
-
|
|
1166
|
-
\`\`\`bash
|
|
1167
|
-
node bin/configure.cjs --list
|
|
1168
|
-
node bin/configure.cjs generic
|
|
1169
|
-
\`\`\`
|
|
1170
|
-
|
|
1171
|
-
Copy the generated configuration into the trusted MCP configuration of the destination tool. See
|
|
1172
|
-
\`adapters/README.md\` for Claude, Codex, Kimi, OpenCode, Cline, OpenClaw, Hermes, n8n, and custom
|
|
1173
|
-
host guidance. Load \`skills/ragmir-portable\` for cited retrieval and
|
|
1174
|
-
\`skills/ragmir-decision-evidence\` for evidence-grounded decisions.
|
|
1175
|
-
|
|
1176
|
-
## Security boundary
|
|
1177
|
-
|
|
1178
|
-
The knowledge base supplies evidence, not authority. The host owns credentials, network exposure,
|
|
1179
|
-
authentication, authorization, rate limiting, external actions, and human approval. Check
|
|
1180
|
-
\`manifest.json\` for the frozen export time and SHA-256 inventory before relying on the bundle.
|
|
1181
|
-
`;
|
|
1182
|
-
}
|
|
1183
|
-
async function verifyPortableTable(config, tableName, expectedRows) {
|
|
1184
|
-
const connection = await connectStore(config);
|
|
1185
|
-
try {
|
|
1186
|
-
const table = await openRowsTableByName(tableName, config, connection);
|
|
1187
|
-
if (!table) {
|
|
1188
|
-
throw new Error(`Active LanceDB table is missing: ${tableName}.`);
|
|
1189
|
-
}
|
|
1190
|
-
try {
|
|
1191
|
-
const rows = await table.countRows();
|
|
1192
|
-
if (rows !== expectedRows) {
|
|
1193
|
-
throw new Error(`Active LanceDB table has ${rows} rows; expected ${expectedRows}.`);
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
finally {
|
|
1197
|
-
closeRowsTable(table, config);
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
finally {
|
|
1201
|
-
closeStoreConnection(connection, config);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
function assertPortableRuntimeConfig(config, root) {
|
|
1205
|
-
if (config.projectRoot !== root) {
|
|
1206
|
-
throw new Error("Portable config resolved to a different project root.");
|
|
1207
|
-
}
|
|
1208
|
-
const expectedPaths = {
|
|
1209
|
-
rawDir: path.join(root, ".ragmir", "source-unavailable"),
|
|
1210
|
-
storageDir: path.join(root, ".ragmir", "storage"),
|
|
1211
|
-
sourcesFile: path.join(root, ".ragmir", "sources.txt"),
|
|
1212
|
-
accessLogPath: path.join(root, ".ragmir", "access.log"),
|
|
1213
|
-
embeddingModelPath: path.join(root, ".ragmir", "models"),
|
|
1214
|
-
};
|
|
1215
|
-
for (const [key, expected] of Object.entries(expectedPaths)) {
|
|
1216
|
-
if (config[key] !== expected) {
|
|
1217
|
-
throw new Error(`Portable config has an invalid ${key}.`);
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
if (config.sources.length > 0 ||
|
|
1221
|
-
config.accessLog ||
|
|
1222
|
-
config.transformersAllowRemoteModels ||
|
|
1223
|
-
config.pdfOcrCommand.length > 0 ||
|
|
1224
|
-
config.imageOcrCommand.length > 0 ||
|
|
1225
|
-
config.legacyWordCommand.length > 0) {
|
|
1226
|
-
throw new Error("Portable config enables a source, log, remote model, or external extractor.");
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
async function writePrivateJson(filePath, value) {
|
|
1230
|
-
await writePrivateText(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
1231
|
-
}
|
|
1232
|
-
async function writePrivateText(filePath, contents) {
|
|
1233
|
-
await ensurePrivateDirectory(path.dirname(filePath));
|
|
1234
|
-
await writeFile(filePath, contents, { encoding: "utf8", mode: 0o600 });
|
|
1235
|
-
await hardenPrivateFile(filePath);
|
|
1236
|
-
}
|
|
1237
|
-
async function writeExecutable(filePath, contents) {
|
|
1238
|
-
await ensurePrivateDirectory(path.dirname(filePath));
|
|
1239
|
-
await writeFile(filePath, contents, { encoding: "utf8", mode: 0o700 });
|
|
1240
|
-
if (process.platform !== "win32") {
|
|
1241
|
-
await chmod(filePath, 0o700);
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
async function hashFile(filePath) {
|
|
1245
|
-
const hash = createHash("sha256");
|
|
1246
|
-
for await (const chunk of createReadStream(filePath)) {
|
|
1247
|
-
hash.update(chunk);
|
|
1248
|
-
}
|
|
1249
|
-
return hash.digest("hex");
|
|
1250
|
-
}
|
|
1251
|
-
function portableRelativePath(value) {
|
|
1252
|
-
if (value.includes("\\") || value.startsWith("/") || path.posix.isAbsolute(value)) {
|
|
1253
|
-
return null;
|
|
1254
|
-
}
|
|
1255
|
-
const normalized = path.posix.normalize(value);
|
|
1256
|
-
if (normalized !== value || normalized === "." || normalized.startsWith("../")) {
|
|
1257
|
-
return null;
|
|
1258
|
-
}
|
|
1259
|
-
return normalized;
|
|
1260
|
-
}
|
|
1261
|
-
function pathContains(parent, candidate) {
|
|
1262
|
-
const relative = path.relative(path.resolve(parent), path.resolve(candidate));
|
|
1263
|
-
return (relative === "" ||
|
|
1264
|
-
(!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative)));
|
|
1265
|
-
}
|
|
1266
|
-
async function pathExists(filePath) {
|
|
1267
|
-
try {
|
|
1268
|
-
await lstat(filePath);
|
|
1269
|
-
return true;
|
|
1270
|
-
}
|
|
1271
|
-
catch (error) {
|
|
1272
|
-
if (isNodeError(error) && error.code === "ENOENT") {
|
|
1273
|
-
return false;
|
|
1274
|
-
}
|
|
1275
|
-
throw error;
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
async function directoryExists(directory) {
|
|
1279
|
-
if (!existsSync(directory)) {
|
|
1280
|
-
return false;
|
|
1281
|
-
}
|
|
1282
|
-
return (await lstat(directory)).isDirectory();
|
|
1283
|
-
}
|
|
1284
|
-
function isNodeError(error) {
|
|
1285
|
-
return error instanceof Error && "code" in error;
|
|
1286
|
-
}
|
|
1287
|
-
function errorMessage(error) {
|
|
1288
|
-
return error instanceof Error ? error.message : String(error);
|
|
1289
|
-
}
|
|
1290
|
-
//# sourceMappingURL=portable.js.map
|