@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.
Files changed (3) hide show
  1. package/README.md +10 -2
  2. package/bin/knolo.mjs +95 -3
  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 = ['@knolo/core', path.resolve(__dirname, '../../core/dist/index.js')];
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 kb = await core.mountPack({ src: packPath });
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.1.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.1.2"
16
+ "@knolo/core": "^3.2.0"
17
17
  },
18
18
  "private": false
19
19
  }