@knolo/cli 3.1.3 → 3.2.2
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 +10 -2
- package/bin/knolo.mjs +95 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Global:
|
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npm install -g @knolo/cli
|
|
15
|
-
|
|
15
|
+
```
|
|
16
16
|
|
|
17
17
|
Or use via npx:
|
|
18
18
|
|
|
@@ -112,6 +112,14 @@ This ensures:
|
|
|
112
112
|
|
|
113
113
|
---
|
|
114
114
|
|
|
115
|
+
## ClaimGraph section compatibility
|
|
116
|
+
|
|
117
|
+
New `.knolo` packs may include an optional trailing **ClaimGraph** JSON section.
|
|
118
|
+
This section is deterministic, offline-safe, and additive; runtimes that ignore trailing sections remain backward compatible.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
115
122
|
## 📄 License
|
|
116
123
|
|
|
117
|
-
MIT
|
|
124
|
+
MIT
|
|
125
|
+
|
package/bin/knolo.mjs
CHANGED
|
@@ -27,7 +27,7 @@ const DEFAULT_CONFIG = {
|
|
|
27
27
|
};
|
|
28
28
|
const SUPPORTED_EXTENSIONS = new Set(['.md', '.txt', '.json']);
|
|
29
29
|
const SKIP_DIRS = new Set(['node_modules', 'dist', '.git']);
|
|
30
|
-
const SUBCOMMANDS = new Set(['init', 'add', 'build', 'query', 'dev']);
|
|
30
|
+
const SUBCOMMANDS = new Set(['init', 'add', 'build', 'query', 'dev', 'semantic:index', 'semantic:inspect', 'semantic:validate']);
|
|
31
31
|
|
|
32
32
|
function createError(message) {
|
|
33
33
|
return new Error(message);
|
|
@@ -47,7 +47,10 @@ async function tryImport(filePath) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
async function loadCore() {
|
|
50
|
-
const candidates = [
|
|
50
|
+
const candidates = [
|
|
51
|
+
path.resolve(__dirname, '../../core/dist/index.js'),
|
|
52
|
+
'@knolo/core',
|
|
53
|
+
];
|
|
51
54
|
for (const candidate of candidates) {
|
|
52
55
|
const mod = await tryImport(candidate);
|
|
53
56
|
if (mod?.buildPack && mod?.mountPack && mod?.query) return mod;
|
|
@@ -84,6 +87,9 @@ function printCommandHelp(command) {
|
|
|
84
87
|
build: 'Usage: knolo build',
|
|
85
88
|
query: 'Usage: knolo query <question> [--pack <path>] [--k <number>] [--json]',
|
|
86
89
|
dev: 'Usage: knolo dev',
|
|
90
|
+
'semantic:index': 'Usage: knolo semantic:index --pack <path> [--out <path>] [--model <id>] [--endpoint <url>]',
|
|
91
|
+
'semantic:inspect': 'Usage: knolo semantic:inspect --sidecar <path>',
|
|
92
|
+
'semantic:validate': 'Usage: knolo semantic:validate --pack <path> --sidecar <path> --model <id>',
|
|
87
93
|
};
|
|
88
94
|
console.log(help[command] ?? 'Unknown command.');
|
|
89
95
|
}
|
|
@@ -278,7 +284,9 @@ async function cmdQuery(core, args) {
|
|
|
278
284
|
const topK = opts.k !== undefined ? Number(opts.k) : (config.query?.topK ?? 5);
|
|
279
285
|
if (!Number.isInteger(topK) || topK <= 0) throw createError('--k must be a positive integer.');
|
|
280
286
|
|
|
281
|
-
const
|
|
287
|
+
const packBuffer = readFileSync(packPath);
|
|
288
|
+
const bytes = Uint8Array.from(packBuffer);
|
|
289
|
+
const kb = await mountPackFromBytes(core, bytes);
|
|
282
290
|
const hits = core.query(kb, question, { topK }).map((hit) => ({
|
|
283
291
|
title: kb.headings?.[hit.blockId] || hit.source || `Block ${hit.blockId}`,
|
|
284
292
|
source: hit.source || kb.docIds?.[hit.blockId] || 'unknown',
|
|
@@ -308,6 +316,87 @@ async function cmdQuery(core, args) {
|
|
|
308
316
|
});
|
|
309
317
|
}
|
|
310
318
|
|
|
319
|
+
function parseKeyValueArgs(args) {
|
|
320
|
+
const out = {};
|
|
321
|
+
for (let i = 0; i < args.length; i++) {
|
|
322
|
+
const key = args[i];
|
|
323
|
+
if (!key.startsWith('--')) throw createError(`Unexpected argument: ${key}`);
|
|
324
|
+
out[key.slice(2)] = args[++i];
|
|
325
|
+
}
|
|
326
|
+
return out;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function loadOllamaProvider() {
|
|
330
|
+
const mod = await tryImport(path.resolve(__dirname, '../../semantic-ollama/dist/index.js'));
|
|
331
|
+
if (mod?.OllamaEmbeddingProvider) return mod.OllamaEmbeddingProvider;
|
|
332
|
+
const pkg = await tryImport('@knolo/semantic-ollama');
|
|
333
|
+
if (pkg?.OllamaEmbeddingProvider) return pkg.OllamaEmbeddingProvider;
|
|
334
|
+
throw createError('Could not load @knolo/semantic-ollama. Build packages/semantic-ollama first.');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function cmdSemanticIndex(core, args) {
|
|
338
|
+
const flags = parseKeyValueArgs(args);
|
|
339
|
+
const packPath = path.resolve(process.cwd(), flags.pack || 'dist/knowledge.knolo');
|
|
340
|
+
const outPath = path.resolve(process.cwd(), flags.out || `${packPath}.semantic.json`);
|
|
341
|
+
const modelId = flags.model || 'qwen3-embedding:4b';
|
|
342
|
+
const endpoint = flags.endpoint || 'http://localhost:11434';
|
|
343
|
+
if (!existsSync(packPath)) throw createError(`Pack file not found at ${path.relative(process.cwd(), packPath)}.`);
|
|
344
|
+
|
|
345
|
+
const bytes = Uint8Array.from(readFileSync(packPath));
|
|
346
|
+
const pack = await mountPackFromBytes(core, bytes);
|
|
347
|
+
const OllamaEmbeddingProvider = await loadOllamaProvider();
|
|
348
|
+
const provider = new OllamaEmbeddingProvider({ modelId, endpoint });
|
|
349
|
+
const vectors = await provider.embedTexts(pack.blocks);
|
|
350
|
+
const sidecar = {
|
|
351
|
+
version: 1,
|
|
352
|
+
packFingerprint: core.createPackFingerprint(pack),
|
|
353
|
+
modelId: provider.modelId,
|
|
354
|
+
dimension: vectors[0]?.length ?? 0,
|
|
355
|
+
metric: 'cosine',
|
|
356
|
+
createdAt: new Date().toISOString(),
|
|
357
|
+
blocks: vectors.map((vector, blockId) => ({ blockId, vector: Array.from(core.normalizeVector(vector)) })),
|
|
358
|
+
};
|
|
359
|
+
writeFileSync(outPath, core.serializeSidecar(sidecar));
|
|
360
|
+
console.log(`✔ wrote ${path.relative(process.cwd(), outPath)}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function cmdSemanticInspect(core, args) {
|
|
364
|
+
const flags = parseKeyValueArgs(args);
|
|
365
|
+
const sidecarPath = path.resolve(process.cwd(), flags.sidecar);
|
|
366
|
+
const sidecar = core.parseSidecar(readFileSync(sidecarPath, 'utf8'));
|
|
367
|
+
console.log(JSON.stringify({
|
|
368
|
+
version: sidecar.version,
|
|
369
|
+
packFingerprint: sidecar.packFingerprint,
|
|
370
|
+
modelId: sidecar.modelId,
|
|
371
|
+
dimension: sidecar.dimension,
|
|
372
|
+
metric: sidecar.metric,
|
|
373
|
+
createdAt: sidecar.createdAt,
|
|
374
|
+
blocks: sidecar.blocks.length,
|
|
375
|
+
}, null, 2));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async function cmdSemanticValidate(core, args) {
|
|
379
|
+
const flags = parseKeyValueArgs(args);
|
|
380
|
+
const packPath = path.resolve(process.cwd(), flags.pack || 'dist/knowledge.knolo');
|
|
381
|
+
const sidecarPath = path.resolve(process.cwd(), flags.sidecar);
|
|
382
|
+
const modelId = flags.model;
|
|
383
|
+
if (!modelId) throw createError('semantic:validate requires --model <id>.');
|
|
384
|
+
const pack = await mountPackFromBytes(core, Uint8Array.from(readFileSync(packPath)));
|
|
385
|
+
const sidecar = core.parseSidecar(readFileSync(sidecarPath, 'utf8'));
|
|
386
|
+
core.validateSidecarForPack({ sidecar, pack, modelId });
|
|
387
|
+
if (sidecar.blocks.length !== pack.blocks.length) throw createError(`Semantic block count mismatch: sidecar=${sidecar.blocks.length}, pack=${pack.blocks.length}`);
|
|
388
|
+
if (sidecar.dimension <= 0) throw createError('Semantic sidecar dimension must be > 0.');
|
|
389
|
+
console.log('✔ semantic sidecar validation passed');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function mountPackFromBytes(core, bytes) {
|
|
393
|
+
try {
|
|
394
|
+
return await core.mountPack({ bytes });
|
|
395
|
+
} catch {
|
|
396
|
+
return await core.mountPack({ src: bytes });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
311
400
|
function listDirectoriesRecursively(root) {
|
|
312
401
|
const dirs = [root];
|
|
313
402
|
for (let i = 0; i < dirs.length; i++) {
|
|
@@ -585,6 +674,9 @@ async function main() {
|
|
|
585
674
|
if (command === 'build') return await cmdBuild(core);
|
|
586
675
|
if (command === 'query') return await cmdQuery(core, commandArgs);
|
|
587
676
|
if (command === 'dev') return await cmdDev(core);
|
|
677
|
+
if (command === 'semantic:index') return await cmdSemanticIndex(core, commandArgs);
|
|
678
|
+
if (command === 'semantic:inspect') return await cmdSemanticInspect(core, commandArgs);
|
|
679
|
+
if (command === 'semantic:validate') return await cmdSemanticValidate(core, commandArgs);
|
|
588
680
|
}
|
|
589
681
|
|
|
590
682
|
if (command.startsWith('-')) throw createError(`Unknown option: ${command}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knolo/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"knolo": "bin/knolo.mjs"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"test": "npm run build --prefix ../core && node --test test/cli.test.mjs"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@knolo/core": "^3.
|
|
16
|
+
"@knolo/core": "^3.2.0"
|
|
17
17
|
},
|
|
18
18
|
"private": false
|
|
19
19
|
}
|