@knolo/cli 5.2.3 → 5.5.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/bin/knolo.mjs CHANGED
@@ -20,7 +20,17 @@ import path from 'node:path';
20
20
  import process from 'node:process';
21
21
  import { fileURLToPath, pathToFileURL } from 'node:url';
22
22
  import { createRequire } from 'node:module';
23
- import { isHubAddInvocation, runHubAdd, runHubInfo, runHubLogin, runHubLogout, runHubPublish, runHubSearch, runHubWhoami, runHubYank } from './registry/commands.mjs';
23
+ import {
24
+ isHubAddInvocation,
25
+ runHubAdd,
26
+ runHubInfo,
27
+ runHubLogin,
28
+ runHubLogout,
29
+ runHubPublish,
30
+ runHubSearch,
31
+ runHubWhoami,
32
+ runHubYank,
33
+ } from './registry/commands.mjs';
24
34
 
25
35
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
26
36
  const require = createRequire(import.meta.url);
@@ -33,9 +43,45 @@ const DEFAULT_CONFIG = {
33
43
  query: { topK: 5 },
34
44
  };
35
45
  const SUPPORTED_EXTENSIONS = new Set(['.md', '.txt', '.json']);
36
- const SKIP_DIRS = new Set(['node_modules', 'dist', '.git', '.dfx', '.cargo-target']);
37
- const SUBCOMMANDS = new Set(['init', 'add', 'build', 'query', 'inspect', 'migrate', 'verify', 'explain', 'diff', 'dev', 'v5', 'search', 'info', 'login', 'whoami', 'logout', 'publish', 'yank', 'semantic:index', 'semantic:inspect', 'semantic:validate']);
38
- const ICP_SUBCOMMANDS = new Set(['init', 'build-pack', 'upload', 'query', 'health', 'info', 'clear']);
46
+ const SKIP_DIRS = new Set([
47
+ 'node_modules',
48
+ 'dist',
49
+ '.git',
50
+ '.dfx',
51
+ '.cargo-target',
52
+ ]);
53
+ const SUBCOMMANDS = new Set([
54
+ 'init',
55
+ 'add',
56
+ 'build',
57
+ 'query',
58
+ 'inspect',
59
+ 'migrate',
60
+ 'verify',
61
+ 'explain',
62
+ 'diff',
63
+ 'dev',
64
+ 'v5',
65
+ 'search',
66
+ 'info',
67
+ 'login',
68
+ 'whoami',
69
+ 'logout',
70
+ 'publish',
71
+ 'yank',
72
+ 'semantic:index',
73
+ 'semantic:inspect',
74
+ 'semantic:validate',
75
+ ]);
76
+ const ICP_SUBCOMMANDS = new Set([
77
+ 'init',
78
+ 'build-pack',
79
+ 'upload',
80
+ 'query',
81
+ 'health',
82
+ 'info',
83
+ 'clear',
84
+ ]);
39
85
  const ICP_TEMPLATE_CANDIDATES = [
40
86
  path.resolve(__dirname, '../templates/icp-knowledge-canister'),
41
87
  path.resolve(__dirname, '../../../examples/icp-knowledge-canister'),
@@ -47,7 +93,11 @@ function createError(message) {
47
93
 
48
94
  async function tryImport(filePath) {
49
95
  try {
50
- if (filePath.startsWith('.') || filePath.startsWith('/') || /^[A-Za-z]:[\\/]/.test(filePath)) {
96
+ if (
97
+ filePath.startsWith('.') ||
98
+ filePath.startsWith('/') ||
99
+ /^[A-Za-z]:[\\/]/.test(filePath)
100
+ ) {
51
101
  return await import(pathToFileURL(filePath).href);
52
102
  }
53
103
  return await import(filePath);
@@ -66,9 +116,16 @@ async function loadCore() {
66
116
  for (const candidate of candidates) {
67
117
  const mod = await tryImport(candidate);
68
118
  if (mod?.buildPack && mod?.mountPack && mod?.query) return mod;
69
- if (mod?.default?.buildPack && mod?.default?.mountPack && mod?.default?.query) return mod.default;
119
+ if (
120
+ mod?.default?.buildPack &&
121
+ mod?.default?.mountPack &&
122
+ mod?.default?.query
123
+ )
124
+ return mod.default;
70
125
  }
71
- throw createError('Could not load @knolo/core. Build packages/core first (npm run build --workspace @knolo/core).');
126
+ throw createError(
127
+ 'Could not load @knolo/core. Build packages/core first (npm run build --workspace @knolo/core).'
128
+ );
72
129
  }
73
130
 
74
131
  function printRootHelp() {
@@ -89,14 +146,14 @@ Commands:
89
146
  publish Publish a pack with a Hub Bearer token
90
147
  yank <pack>@<version> Yank a released pack with a Hub Bearer token
91
148
  build Build a .knolo pack from configured sources
92
- query <question> Query a built pack and print top hits
149
+ query <question|EQL> Query a V4 pack or a V5 image using its declared contract
93
150
  inspect <pack> Inspect pack format, sections, and manifest
94
151
  migrate <pack> Migrate a legacy pack to v4
95
152
  verify <pack> Verify pack structure and cryptographic digests
96
153
  explain <receipt> Verify and explain a query receipt
97
154
  diff <pack-a> <pack-b> Compare pack identities and source/chunk counts
98
155
  dev Watch config/sources and rebuild on change
99
- v5 Inspect, health-check, and manage a V5 Knowledge Image
156
+ v5 Inspect, compress, decompress, and health-check a V5 Knowledge Image
100
157
  icp Scaffold and operate an ICP-native knowledge canister
101
158
 
102
159
  Global options:
@@ -110,26 +167,33 @@ function printCommandHelp(command) {
110
167
  const help = {
111
168
  init: 'Usage: knolo init',
112
169
  add: 'Usage: knolo add <name> <path> | <publisher>/<slug>[@<version>] [--out <path>] [--force] [--json] [--registry <url>]',
113
- search: 'Usage: knolo search <query> [--format V4|V5] [--license <id>] [--official] [--agents] [--json] [--registry <url>]',
170
+ search:
171
+ 'Usage: knolo search <query> [--format V4|V5] [--license <id>] [--official] [--agents] [--json] [--registry <url>]',
114
172
  info: 'Usage: knolo info <publisher>/<slug> [--json] [--registry <url>]',
115
- login: 'Usage: knolo login [--token kno_…] [--stdin] [--registry <url>]\n\nStores the raw token locally. Hub write calls send Authorization: Bearer kno_….\nMint tokens at https://hub.knolo.dev/dashboard/tokens; login does not call POST /api/v1/tokens.',
173
+ login:
174
+ 'Usage: knolo login [--token kno_…] [--stdin] [--registry <url>]\n\nStores the raw token locally. Hub write calls send Authorization: Bearer kno_….\nMint tokens at https://hub.knolo.dev/dashboard/tokens; login does not call POST /api/v1/tokens.',
116
175
  whoami: 'Usage: knolo whoami',
117
176
  logout: 'Usage: knolo logout',
118
- publish: 'Usage: knolo publish <pack.knolo> --slug <slug> --version <version> --license <SPDX> [--publisher <handle>] [--readme <text>] [--sources <text>] [--intended-use <text>] [--json] [--registry <url>]\n\nUses the stored dashboard token as Authorization: Bearer kno_…. Hub issues a public Blob PUT grant; the CLI never needs PACKS_READ_WRITE_TOKEN.\nUploads go to the grant URL without the kno_ token. Complete sends the public Blob URL, then Hub verifies before release.',
177
+ publish:
178
+ 'Usage: knolo publish <pack.knolo> --slug <slug> --version <version> --license <SPDX> [--publisher <handle>] [--readme <text>] [--sources <text>] [--intended-use <text>] [--json] [--registry <url>]\n\nUses the stored dashboard token as Authorization: Bearer kno_…. Hub issues a public Blob PUT grant; the CLI never needs PACKS_READ_WRITE_TOKEN.\nUploads go to the grant URL without the kno_ token. Complete sends the public Blob URL, then Hub verifies before release.',
119
179
  yank: 'Usage: knolo yank <publisher>/<slug>@<version> [--json] [--registry <url>]\n\nUses the stored token as Authorization: Bearer kno_…. Yank is owner-only and leaves the public Blob in place.',
120
180
  build: 'Usage: knolo build',
121
- query: 'Usage: knolo query <question> [--pack <path>] [--k <number>] [--receipt <file>] [--json]',
181
+ query:
182
+ 'Usage: knolo query <question|EQL> [--pack <path>] [--k <number>] [--receipt <file>] [--json]\n\nV4 packs accept natural-language queries. V5 images require bounded EQL such as: FROM chunk SEARCH "billing" LIMIT 5.',
122
183
  inspect: 'Usage: knolo inspect <pack.knolo>',
123
- migrate: 'Usage: knolo migrate <pack.knolo> --out <new-pack.knolo> [--to 4]',
184
+ migrate:
185
+ 'Usage: knolo migrate <pack.knolo> --out <new-pack.knolo> [--to 4]',
124
186
  verify: 'Usage: knolo verify <pack.knolo>',
125
187
  explain: 'Usage: knolo explain <receipt.json> --pack <pack.knolo>',
126
188
  diff: 'Usage: knolo diff <pack-a.knolo> <pack-b.knolo>',
127
189
  dev: 'Usage: knolo dev',
128
- v5: 'Usage: knolo v5 <info|health|studio> <image.v5> [--index <file>] [--history <file>] [--run <file>] [--replay <file>]',
190
+ v5: 'Usage: knolo v5 <info|health|studio|query|compress|decompress> <image.v5> [--out <file>] [--mode fast|balanced|max] [--attach-index]\n\nFor query: knolo v5 query <image.v5> <EQL> [--json] [--receipt <file>].',
129
191
  icp: 'Usage: knolo icp <command> [options]',
130
- 'semantic:index': 'Usage: knolo semantic:index --pack <path> [--out <path>] [--model <id>] [--endpoint <url>]',
192
+ 'semantic:index':
193
+ 'Usage: knolo semantic:index --pack <path> [--out <path>] [--model <id>] [--endpoint <url>]',
131
194
  'semantic:inspect': 'Usage: knolo semantic:inspect --sidecar <path>',
132
- 'semantic:validate': 'Usage: knolo semantic:validate --pack <path> --sidecar <path> --model <id>',
195
+ 'semantic:validate':
196
+ 'Usage: knolo semantic:validate --pack <path> --sidecar <path> --model <id>',
133
197
  };
134
198
  console.log(help[command] ?? 'Unknown command.');
135
199
  }
@@ -222,13 +286,16 @@ function parseArgv(argv) {
222
286
  global.debug = true;
223
287
  args.shift();
224
288
  }
225
- if (args[0] === '--help' || args[0] === '-h') return { global, command: 'help', commandArgs: [] };
289
+ if (args[0] === '--help' || args[0] === '-h')
290
+ return { global, command: 'help', commandArgs: [] };
226
291
  return { global, command: args[0] || 'help', commandArgs: args.slice(1) };
227
292
  }
228
293
 
229
294
  function readConfig(configPath = path.resolve(process.cwd(), CONFIG_FILE)) {
230
295
  if (!existsSync(configPath)) {
231
- throw createError(`Missing config file at ${path.relative(process.cwd(), configPath)}. Run "knolo init" first.`);
296
+ throw createError(
297
+ `Missing config file at ${path.relative(process.cwd(), configPath)}. Run "knolo init" first.`
298
+ );
232
299
  }
233
300
  let parsed;
234
301
  try {
@@ -241,12 +308,17 @@ function readConfig(configPath = path.resolve(process.cwd(), CONFIG_FILE)) {
241
308
  }
242
309
 
243
310
  function validateConfig(config) {
244
- if (!config || typeof config !== 'object') throw createError(`${CONFIG_FILE} must be a JSON object.`);
245
- if (config.version !== 1) throw createError(`${CONFIG_FILE} must include "version": 1.`);
246
- if (!Array.isArray(config.sources)) throw createError(`${CONFIG_FILE} must include a "sources" array.`);
311
+ if (!config || typeof config !== 'object')
312
+ throw createError(`${CONFIG_FILE} must be a JSON object.`);
313
+ if (config.version !== 1)
314
+ throw createError(`${CONFIG_FILE} must include "version": 1.`);
315
+ if (!Array.isArray(config.sources))
316
+ throw createError(`${CONFIG_FILE} must include a "sources" array.`);
247
317
  for (const [i, s] of config.sources.entries()) {
248
- if (!s?.name || typeof s.name !== 'string') throw createError(`sources[${i}].name must be a string.`);
249
- if (!s?.path || typeof s.path !== 'string') throw createError(`sources[${i}].path must be a string.`);
318
+ if (!s?.name || typeof s.name !== 'string')
319
+ throw createError(`sources[${i}].name must be a string.`);
320
+ if (!s?.path || typeof s.path !== 'string')
321
+ throw createError(`sources[${i}].path must be a string.`);
250
322
  }
251
323
  if (!config.output?.path || typeof config.output.path !== 'string') {
252
324
  throw createError(`${CONFIG_FILE} must include output.path.`);
@@ -254,7 +326,10 @@ function validateConfig(config) {
254
326
  }
255
327
 
256
328
  function writeConfig(config) {
257
- writeFileSync(path.resolve(process.cwd(), CONFIG_FILE), `${JSON.stringify(config, null, 2)}\n`);
329
+ writeFileSync(
330
+ path.resolve(process.cwd(), CONFIG_FILE),
331
+ `${JSON.stringify(config, null, 2)}\n`
332
+ );
258
333
  }
259
334
 
260
335
  async function cmdInit() {
@@ -277,9 +352,15 @@ async function cmdInit() {
277
352
  console.log('• kept existing docs/');
278
353
  }
279
354
 
280
- const visibleEntries = readdirSync(docsDir).filter((name) => !name.startsWith('.'));
355
+ const visibleEntries = readdirSync(docsDir).filter(
356
+ (name) => !name.startsWith('.')
357
+ );
281
358
  if (visibleEntries.length === 0 && !existsSync(samplePath)) {
282
- writeFileSync(samplePath, '# Hello from KnoLo\n\nThis is a starter document for KnoLo CLI demos.\n', 'utf8');
359
+ writeFileSync(
360
+ samplePath,
361
+ '# Hello from KnoLo\n\nThis is a starter document for KnoLo CLI demos.\n',
362
+ 'utf8'
363
+ );
283
364
  console.log('✔ created docs/hello.md');
284
365
  } else {
285
366
  console.log('• did not create sample doc because docs/ is not empty');
@@ -299,10 +380,14 @@ async function cmdAddLocal(args) {
299
380
  if (!name || !sourcePath) throw createError('Usage: knolo add <name> <path>');
300
381
 
301
382
  const resolved = path.resolve(process.cwd(), sourcePath);
302
- if (!existsSync(resolved)) throw createError(`Source path does not exist: ${sourcePath}`);
383
+ if (!existsSync(resolved))
384
+ throw createError(`Source path does not exist: ${sourcePath}`);
303
385
 
304
386
  const config = readConfig();
305
- const normalized = sourcePath.startsWith('.') || path.isAbsolute(sourcePath) ? sourcePath : `./${sourcePath}`;
387
+ const normalized =
388
+ sourcePath.startsWith('.') || path.isAbsolute(sourcePath)
389
+ ? sourcePath
390
+ : `./${sourcePath}`;
306
391
  const existing = config.sources.find((s) => s.name === name);
307
392
  if (existing) {
308
393
  existing.path = normalized;
@@ -317,15 +402,21 @@ async function cmdAddLocal(args) {
317
402
 
318
403
  function resolveIcpTemplateDir() {
319
404
  for (const candidate of ICP_TEMPLATE_CANDIDATES) {
320
- if (existsSync(candidate) && statSync(candidate).isDirectory()) return candidate;
405
+ if (existsSync(candidate) && statSync(candidate).isDirectory())
406
+ return candidate;
321
407
  }
322
- throw createError('Bundled ICP template not found. Reinstall @knolo/cli or run from the Knolo repo checkout.');
408
+ throw createError(
409
+ 'Bundled ICP template not found. Reinstall @knolo/cli or run from the Knolo repo checkout.'
410
+ );
323
411
  }
324
412
 
325
413
  function ensureDirectoryMissingOrEmpty(targetDir) {
326
414
  if (!existsSync(targetDir)) return;
327
415
  const stats = statSync(targetDir);
328
- if (!stats.isDirectory()) throw createError(`Target path already exists and is not a directory: ${targetDir}`);
416
+ if (!stats.isDirectory())
417
+ throw createError(
418
+ `Target path already exists and is not a directory: ${targetDir}`
419
+ );
329
420
  const visibleEntries = readdirSync(targetDir);
330
421
  if (visibleEntries.length > 0) {
331
422
  throw createError(`Target directory is not empty: ${targetDir}`);
@@ -334,7 +425,9 @@ function ensureDirectoryMissingOrEmpty(targetDir) {
334
425
 
335
426
  function copyDirectoryContents(sourceDir, targetDir) {
336
427
  mkdirSync(targetDir, { recursive: true });
337
- const entries = readdirSync(sourceDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
428
+ const entries = readdirSync(sourceDir, { withFileTypes: true }).sort((a, b) =>
429
+ a.name.localeCompare(b.name)
430
+ );
338
431
  for (const entry of entries) {
339
432
  const sourcePath = path.join(sourceDir, entry.name);
340
433
  const targetPath = path.join(targetDir, entry.name);
@@ -389,7 +482,9 @@ function parsePositiveInteger(value, flagName) {
389
482
  }
390
483
 
391
484
  function renderDidNat8Vec(bytes) {
392
- return bytes.length ? ` ${Array.from(bytes, (value) => `${value}`).join('; ')} ` : '';
485
+ return bytes.length
486
+ ? ` ${Array.from(bytes, (value) => `${value}`).join('; ')} `
487
+ : '';
393
488
  }
394
489
 
395
490
  function withArgumentFile(prefix, contents, run) {
@@ -440,7 +535,10 @@ async function walkDir(dir) {
440
535
  if (entry.isDirectory()) {
441
536
  if (SKIP_DIRS.has(entry.name)) continue;
442
537
  out.push(...(await walkDir(full)));
443
- } else if (entry.isFile() && SUPPORTED_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
538
+ } else if (
539
+ entry.isFile() &&
540
+ SUPPORTED_EXTENSIONS.has(path.extname(entry.name).toLowerCase())
541
+ ) {
444
542
  out.push(full);
445
543
  }
446
544
  }
@@ -451,7 +549,10 @@ async function collectSourceFiles(sources) {
451
549
  const files = [];
452
550
  for (const source of sources) {
453
551
  const resolved = path.resolve(process.cwd(), source.path);
454
- if (!existsSync(resolved)) throw createError(`Configured source "${source.name}" not found at ${source.path}.`);
552
+ if (!existsSync(resolved))
553
+ throw createError(
554
+ `Configured source "${source.name}" not found at ${source.path}.`
555
+ );
455
556
  const stats = await fs.lstat(resolved);
456
557
  if (stats.isSymbolicLink()) continue;
457
558
  if (stats.isFile()) {
@@ -459,9 +560,12 @@ async function collectSourceFiles(sources) {
459
560
  files.push({ sourceName: source.name, absolutePath: resolved });
460
561
  }
461
562
  } else if (stats.isDirectory()) {
462
- for (const file of await walkDir(resolved)) files.push({ sourceName: source.name, absolutePath: file });
563
+ for (const file of await walkDir(resolved))
564
+ files.push({ sourceName: source.name, absolutePath: file });
463
565
  } else {
464
- throw createError(`Configured source "${source.name}" must be a file or directory.`);
566
+ throw createError(
567
+ `Configured source "${source.name}" must be a file or directory.`
568
+ );
465
569
  }
466
570
  }
467
571
  files.sort((a, b) => a.absolutePath.localeCompare(b.absolutePath));
@@ -472,10 +576,13 @@ async function buildFromConfig(core, { silent = false } = {}) {
472
576
  const start = Date.now();
473
577
  const config = readConfig();
474
578
  const files = await collectSourceFiles(config.sources);
475
- if (files.length === 0) throw createError('No supported files found (.md, .txt, .json).');
579
+ if (files.length === 0)
580
+ throw createError('No supported files found (.md, .txt, .json).');
476
581
 
477
582
  const docs = files.map((f) => {
478
- const rel = path.relative(process.cwd(), f.absolutePath).replace(/\\/g, '/');
583
+ const rel = path
584
+ .relative(process.cwd(), f.absolutePath)
585
+ .replace(/\\/g, '/');
479
586
  return {
480
587
  id: rel,
481
588
  heading: path.basename(rel),
@@ -503,17 +610,21 @@ async function cmdBuild(core) {
503
610
 
504
611
  async function collectNamedFiles(sourceName, inputPath) {
505
612
  const resolved = path.resolve(process.cwd(), inputPath);
506
- if (!existsSync(resolved)) throw createError(`Source path not found: ${inputPath}`);
613
+ if (!existsSync(resolved))
614
+ throw createError(`Source path not found: ${inputPath}`);
507
615
 
508
616
  const stats = await fs.lstat(resolved);
509
617
  if (stats.isSymbolicLink()) return [];
510
618
  if (stats.isFile()) {
511
619
  if (!SUPPORTED_EXTENSIONS.has(path.extname(resolved).toLowerCase())) {
512
- throw createError(`Unsupported file type for ${inputPath}. Expected .md, .txt, or .json.`);
620
+ throw createError(
621
+ `Unsupported file type for ${inputPath}. Expected .md, .txt, or .json.`
622
+ );
513
623
  }
514
624
  return [{ sourceName, absolutePath: resolved }];
515
625
  }
516
- if (!stats.isDirectory()) throw createError(`Source path must be a file or directory: ${inputPath}`);
626
+ if (!stats.isDirectory())
627
+ throw createError(`Source path must be a file or directory: ${inputPath}`);
517
628
 
518
629
  return (await walkDir(resolved))
519
630
  .map((absolutePath) => ({ sourceName, absolutePath }))
@@ -548,8 +659,12 @@ async function cmdIcpInit(args) {
548
659
  console.log(` cd ${relativeTarget}`);
549
660
  console.log(' dfx start --background');
550
661
  console.log(' dfx deploy');
551
- console.log(' knolo icp build-pack ./knowledge --out ./dist/knowledge.knolo');
552
- console.log(' knolo icp upload ./dist/knowledge.knolo --canister knolo_knowledge');
662
+ console.log(
663
+ ' knolo icp build-pack ./knowledge --out ./dist/knowledge.knolo'
664
+ );
665
+ console.log(
666
+ ' knolo icp upload ./dist/knowledge.knolo --canister knolo_knowledge'
667
+ );
553
668
  console.log(' knolo icp query "alpha beta" --canister knolo_knowledge');
554
669
  }
555
670
 
@@ -562,9 +677,12 @@ async function cmdIcpBuildPack(core, args) {
562
677
 
563
678
  const resolvedInput = path.resolve(process.cwd(), docsDir);
564
679
  const files = await collectNamedFiles('docs', docsDir);
565
- if (!files.length) throw createError('No supported files found (.md, .txt, .json).');
680
+ if (!files.length)
681
+ throw createError('No supported files found (.md, .txt, .json).');
566
682
 
567
- const rootDir = statSync(resolvedInput).isDirectory() ? resolvedInput : path.dirname(resolvedInput);
683
+ const rootDir = statSync(resolvedInput).isDirectory()
684
+ ? resolvedInput
685
+ : path.dirname(resolvedInput);
568
686
  const docs = buildDocsFromFiles(files, rootDir);
569
687
  const bytes = await core.buildPack(docs, { format: 3 });
570
688
  const outPath = path.resolve(process.cwd(), flags.out);
@@ -585,11 +703,16 @@ async function cmdIcpUpload(args) {
585
703
  const { positional, flags } = parseFlagArgs(args);
586
704
  const packFile = positional[0];
587
705
  if (!packFile || !flags.canister) {
588
- throw createError('Usage: knolo icp upload <packFile> --canister <name-or-id> [--label <text>]');
706
+ throw createError(
707
+ 'Usage: knolo icp upload <packFile> --canister <name-or-id> [--label <text>]'
708
+ );
589
709
  }
590
710
 
591
711
  const packPath = path.resolve(process.cwd(), packFile);
592
- if (!existsSync(packPath)) throw createError(`Pack file not found at ${path.relative(process.cwd(), packPath)}.`);
712
+ if (!existsSync(packPath))
713
+ throw createError(
714
+ `Pack file not found at ${path.relative(process.cwd(), packPath)}.`
715
+ );
593
716
 
594
717
  const label = flags.label || defaultPackLabel(packPath);
595
718
  const bytes = readFileSync(packPath);
@@ -597,7 +720,16 @@ async function cmdIcpUpload(args) {
597
720
 
598
721
  withArgumentFile('knolo-icp-upload-', didArgs, (argumentFile) =>
599
722
  runDfx(
600
- ['canister', 'call', flags.canister, 'set_pack', '--argument-file', argumentFile, '--output', 'json'],
723
+ [
724
+ 'canister',
725
+ 'call',
726
+ flags.canister,
727
+ 'set_pack',
728
+ '--argument-file',
729
+ argumentFile,
730
+ '--output',
731
+ 'json',
732
+ ],
601
733
  { cwd: process.cwd() }
602
734
  )
603
735
  );
@@ -607,7 +739,9 @@ async function cmdIcpQuery(args) {
607
739
  const { positional, flags } = parseFlagArgs(args);
608
740
  const question = positional.join(' ').trim();
609
741
  if (!question || !flags.canister) {
610
- throw createError('Usage: knolo icp query <question> --canister <name-or-id> [--k <number>]');
742
+ throw createError(
743
+ 'Usage: knolo icp query <question> --canister <name-or-id> [--k <number>]'
744
+ );
611
745
  }
612
746
 
613
747
  const topK = flags.k === undefined ? 5 : parsePositiveInteger(flags.k, '--k');
@@ -615,7 +749,17 @@ async function cmdIcpQuery(args) {
615
749
 
616
750
  withArgumentFile('knolo-icp-query-', didArgs, (argumentFile) =>
617
751
  runDfx(
618
- ['canister', 'call', flags.canister, 'search', '--query', '--argument-file', argumentFile, '--output', 'json'],
752
+ [
753
+ 'canister',
754
+ 'call',
755
+ flags.canister,
756
+ 'search',
757
+ '--query',
758
+ '--argument-file',
759
+ argumentFile,
760
+ '--output',
761
+ 'json',
762
+ ],
619
763
  { cwd: process.cwd() }
620
764
  )
621
765
  );
@@ -623,33 +767,63 @@ async function cmdIcpQuery(args) {
623
767
 
624
768
  async function cmdIcpHealth(args) {
625
769
  const { flags } = parseFlagArgs(args);
626
- if (!flags.canister) throw createError('Usage: knolo icp health --canister <name-or-id>');
627
- runDfx(['canister', 'call', flags.canister, 'health', '--query', '--output', 'json'], {
628
- cwd: process.cwd(),
629
- });
770
+ if (!flags.canister)
771
+ throw createError('Usage: knolo icp health --canister <name-or-id>');
772
+ runDfx(
773
+ [
774
+ 'canister',
775
+ 'call',
776
+ flags.canister,
777
+ 'health',
778
+ '--query',
779
+ '--output',
780
+ 'json',
781
+ ],
782
+ {
783
+ cwd: process.cwd(),
784
+ }
785
+ );
630
786
  }
631
787
 
632
788
  async function cmdIcpInfo(args) {
633
789
  const { flags } = parseFlagArgs(args);
634
- if (!flags.canister) throw createError('Usage: knolo icp info --canister <name-or-id>');
635
- runDfx(['canister', 'call', flags.canister, 'pack_info', '--query', '--output', 'json'], {
636
- cwd: process.cwd(),
637
- });
790
+ if (!flags.canister)
791
+ throw createError('Usage: knolo icp info --canister <name-or-id>');
792
+ runDfx(
793
+ [
794
+ 'canister',
795
+ 'call',
796
+ flags.canister,
797
+ 'pack_info',
798
+ '--query',
799
+ '--output',
800
+ 'json',
801
+ ],
802
+ {
803
+ cwd: process.cwd(),
804
+ }
805
+ );
638
806
  }
639
807
 
640
808
  async function cmdIcpClear(args) {
641
809
  const { flags } = parseFlagArgs(args);
642
- if (!flags.canister) throw createError('Usage: knolo icp clear --canister <name-or-id>');
643
- runDfx(['canister', 'call', flags.canister, 'clear_pack', '--output', 'json'], {
644
- cwd: process.cwd(),
645
- });
810
+ if (!flags.canister)
811
+ throw createError('Usage: knolo icp clear --canister <name-or-id>');
812
+ runDfx(
813
+ ['canister', 'call', flags.canister, 'clear_pack', '--output', 'json'],
814
+ {
815
+ cwd: process.cwd(),
816
+ }
817
+ );
646
818
  }
647
819
 
648
820
  async function cmdIcp(args) {
649
821
  const [subcommand, ...rest] = args;
650
822
  if (!subcommand) return printIcpHelp();
651
- if (!ICP_SUBCOMMANDS.has(subcommand)) throw createError(`Unknown ICP command: ${subcommand}`);
652
- if (rest.includes('--help') || rest.includes('-h')) return printIcpCommandHelp(subcommand);
823
+ if (!ICP_SUBCOMMANDS.has(subcommand))
824
+ throw createError(`Unknown ICP command: ${subcommand}`);
825
+ if (rest.includes('--help') || rest.includes('-h'))
826
+ return printIcpCommandHelp(subcommand);
653
827
 
654
828
  if (subcommand === 'init') return await cmdIcpInit(rest);
655
829
  if (subcommand === 'upload') return await cmdIcpUpload(rest);
@@ -672,7 +846,8 @@ function parseQueryArgs(args) {
672
846
  else if (arg === '--pack') opts.pack = args[++i];
673
847
  else if (arg === '--k') opts.k = args[++i];
674
848
  else if (arg === '--receipt') opts.receipt = args[++i];
675
- else if (arg.startsWith('--')) throw createError(`Unknown flag for query: ${arg}`);
849
+ else if (arg.startsWith('--'))
850
+ throw createError(`Unknown flag for query: ${arg}`);
676
851
  else positional.push(arg);
677
852
  }
678
853
  return { positional, opts };
@@ -681,21 +856,44 @@ function parseQueryArgs(args) {
681
856
  async function cmdQuery(core, args) {
682
857
  const { positional, opts } = parseQueryArgs(args);
683
858
  const question = positional.join(' ').trim();
684
- if (!question) throw createError('Usage: knolo query <question> [--pack <path>] [--k <number>] [--json]');
859
+ if (!question)
860
+ throw createError(
861
+ 'Usage: knolo query <question> [--pack <path>] [--k <number>] [--json]'
862
+ );
685
863
 
686
- const config = existsSync(path.resolve(process.cwd(), CONFIG_FILE)) ? readConfig() : DEFAULT_CONFIG;
864
+ const config = existsSync(path.resolve(process.cwd(), CONFIG_FILE))
865
+ ? readConfig()
866
+ : DEFAULT_CONFIG;
687
867
  const packPath = path.resolve(process.cwd(), opts.pack || config.output.path);
688
- if (!existsSync(packPath)) throw createError(`Pack file not found at ${path.relative(process.cwd(), packPath)}.`);
868
+ if (!existsSync(packPath))
869
+ throw createError(
870
+ `Pack file not found at ${path.relative(process.cwd(), packPath)}.`
871
+ );
689
872
 
690
- const topK = opts.k !== undefined ? Number(opts.k) : (config.query?.topK ?? 5);
691
- if (!Number.isInteger(topK) || topK <= 0) throw createError('--k must be a positive integer.');
873
+ const topK =
874
+ opts.k !== undefined ? Number(opts.k) : (config.query?.topK ?? 5);
875
+ if (!Number.isInteger(topK) || topK <= 0)
876
+ throw createError('--k must be a positive integer.');
692
877
 
693
878
  const packBuffer = readFileSync(packPath);
694
879
  const bytes = Uint8Array.from(packBuffer);
880
+ if (core.isKnowledgeImageV5?.(bytes)) {
881
+ if (opts.k !== undefined)
882
+ throw createError(
883
+ 'V5 queries use EQL LIMIT. Put LIMIT in the query instead of --k.'
884
+ );
885
+ return await cmdQueryV5(core, bytes, packPath, question, opts);
886
+ }
695
887
  const kb = await mountPackFromBytes(core, bytes);
696
- const receiptResult = opts.receipt && core.queryWithReceipt ? core.queryWithReceipt(kb, question, { topK }) : null;
888
+ const receiptResult =
889
+ opts.receipt && core.queryWithReceipt
890
+ ? core.queryWithReceipt(kb, question, { topK })
891
+ : null;
697
892
  if (receiptResult && opts.receipt) {
698
- writeFileSync(path.resolve(process.cwd(), opts.receipt), `${JSON.stringify(receiptResult.receipt, null, 2)}\n`);
893
+ writeFileSync(
894
+ path.resolve(process.cwd(), opts.receipt),
895
+ `${JSON.stringify(receiptResult.receipt, null, 2)}\n`
896
+ );
699
897
  }
700
898
  const rawHits = receiptResult?.hits ?? core.query(kb, question, { topK });
701
899
  const hits = rawHits.map((hit) => ({
@@ -707,10 +905,28 @@ async function cmdQuery(core, args) {
707
905
 
708
906
  if (opts.json) {
709
907
  const patch = core.makeContextPatch(
710
- hits.map((h, i) => ({ blockId: i, score: h.score, text: h.snippet, source: h.source })),
908
+ hits.map((h, i) => ({
909
+ blockId: i,
910
+ score: h.score,
911
+ text: h.snippet,
912
+ source: h.source,
913
+ })),
711
914
  { budget: 'small' }
712
915
  );
713
- console.log(JSON.stringify({ question, packPath, topK, hits, receipt: receiptResult?.receipt, contextPatch: patch }, null, 2));
916
+ console.log(
917
+ JSON.stringify(
918
+ {
919
+ question,
920
+ packPath,
921
+ topK,
922
+ hits,
923
+ receipt: receiptResult?.receipt,
924
+ contextPatch: patch,
925
+ },
926
+ null,
927
+ 2
928
+ )
929
+ );
714
930
  return;
715
931
  }
716
932
 
@@ -727,13 +943,75 @@ async function cmdQuery(core, args) {
727
943
  });
728
944
  }
729
945
 
946
+ async function cmdQueryV5(core, bytes, imagePath, expression, opts) {
947
+ const image = core.mountKnowledgeImageV5(bytes);
948
+ const result = core.queryKnowledgeImageV5(image, expression);
949
+ if (opts.receipt) {
950
+ writeFileSync(
951
+ path.resolve(process.cwd(), opts.receipt),
952
+ `${JSON.stringify(result, null, 2)}\n`
953
+ );
954
+ }
955
+ const objects = new Map(image.objects.map((object) => [object.id, object]));
956
+ const hits = result.hits.map((hit) => {
957
+ const object = objects.get(hit.objectId);
958
+ const text = object
959
+ ? new TextDecoder()
960
+ .decode(object.bytes)
961
+ .replace(/\s+/gu, ' ')
962
+ .slice(0, 200)
963
+ : '';
964
+ const metadata = object?.meta ?? {};
965
+ const source =
966
+ typeof metadata.source === 'string'
967
+ ? metadata.source
968
+ : typeof metadata.docId === 'string'
969
+ ? metadata.docId
970
+ : hit.objectId;
971
+ return {
972
+ objectId: hit.objectId,
973
+ kind: hit.kind,
974
+ source,
975
+ snippet: text,
976
+ joinedObjectIds: hit.joinedObjectIds,
977
+ };
978
+ });
979
+ const payload = {
980
+ contract: 'v5-knowledge-image',
981
+ query: expression,
982
+ imagePath,
983
+ stateRoot: result.stateRoot,
984
+ planRoot: result.planRoot,
985
+ resultRoot: result.resultRoot,
986
+ plan: result.plan,
987
+ hits,
988
+ queryResult: result,
989
+ };
990
+ if (opts.json) {
991
+ console.log(JSON.stringify(payload, null, 2));
992
+ return;
993
+ }
994
+ if (!hits.length) {
995
+ console.log('No V5 hits found.');
996
+ return;
997
+ }
998
+ console.log(`V5 query returned ${hits.length} hit(s):`);
999
+ hits.forEach((hit, index) => {
1000
+ console.log(`\n${index + 1}. ${hit.kind} ${hit.objectId}`);
1001
+ console.log(` source: ${hit.source}`);
1002
+ console.log(` snippet: ${hit.snippet}`);
1003
+ });
1004
+ }
1005
+
730
1006
  function parsePackCommandArgs(args, command) {
731
1007
  const positional = [];
732
1008
  const flags = {};
733
1009
  for (let i = 0; i < args.length; i++) {
734
1010
  const arg = args[i];
735
- if (arg === '--out' || arg === '--to' || arg === '--pack') flags[arg.slice(2)] = args[++i];
736
- else if (arg.startsWith('--')) throw createError(`Unknown flag for ${command}: ${arg}`);
1011
+ if (arg === '--out' || arg === '--to' || arg === '--pack')
1012
+ flags[arg.slice(2)] = args[++i];
1013
+ else if (arg.startsWith('--'))
1014
+ throw createError(`Unknown flag for ${command}: ${arg}`);
737
1015
  else positional.push(arg);
738
1016
  }
739
1017
  return { positional, flags };
@@ -741,61 +1019,232 @@ function parsePackCommandArgs(args, command) {
741
1019
 
742
1020
  async function cmdInspect(core, args) {
743
1021
  const { positional } = parsePackCommandArgs(args, 'inspect');
744
- const packPath = path.resolve(process.cwd(), positional[0] || 'dist/knowledge.knolo');
745
- if (!existsSync(packPath)) throw createError(`Pack file not found at ${path.relative(process.cwd(), packPath)}.`);
1022
+ const packPath = path.resolve(
1023
+ process.cwd(),
1024
+ positional[0] || 'dist/knowledge.knolo'
1025
+ );
1026
+ if (!existsSync(packPath))
1027
+ throw createError(
1028
+ `Pack file not found at ${path.relative(process.cwd(), packPath)}.`
1029
+ );
746
1030
  const bytes = Uint8Array.from(readFileSync(packPath));
1031
+ if (core.isKnowledgeImageV5?.(bytes)) {
1032
+ const image = core.mountKnowledgeImageV5(bytes);
1033
+ console.log(
1034
+ JSON.stringify(
1035
+ {
1036
+ format: 'v5-knowledge-image',
1037
+ version: 5,
1038
+ stateRoot: image.stateRoot,
1039
+ commitDigest: image.commitDigest,
1040
+ objects: image.objects.length,
1041
+ events: image.events.length,
1042
+ },
1043
+ null,
1044
+ 2
1045
+ )
1046
+ );
1047
+ return;
1048
+ }
747
1049
  const pack = await mountPackFromBytes(core, bytes);
748
- const result = { format: pack.meta?.format || (pack.meta?.version === 4 ? 'v4' : `v${pack.meta?.version ?? 'unknown'}`), version: pack.meta?.version, stats: pack.meta?.stats, blocks: pack.blocks.length, semantic: Boolean(pack.semantic), claims: Boolean(pack.claimGraph) };
749
- if (core.isPackV4?.(bytes.buffer)) result.container = core.inspectPackV4(bytes.buffer);
1050
+ const result = {
1051
+ format:
1052
+ pack.meta?.format ||
1053
+ (pack.meta?.version === 4 ? 'v4' : `v${pack.meta?.version ?? 'unknown'}`),
1054
+ version: pack.meta?.version,
1055
+ stats: pack.meta?.stats,
1056
+ blocks: pack.blocks.length,
1057
+ semantic: Boolean(pack.semantic),
1058
+ claims: Boolean(pack.claimGraph),
1059
+ };
1060
+ if (core.isPackV4?.(bytes.buffer))
1061
+ result.container = core.inspectPackV4(bytes.buffer);
750
1062
  console.log(JSON.stringify(result, null, 2));
751
1063
  }
752
1064
 
753
1065
  async function cmdVerify(core, args) {
754
1066
  const { positional } = parsePackCommandArgs(args, 'verify');
755
- const packPath = path.resolve(process.cwd(), positional[0] || 'dist/knowledge.knolo');
756
- if (!existsSync(packPath)) throw createError(`Pack file not found at ${path.relative(process.cwd(), packPath)}.`);
1067
+ const packPath = path.resolve(
1068
+ process.cwd(),
1069
+ positional[0] || 'dist/knowledge.knolo'
1070
+ );
1071
+ if (!existsSync(packPath))
1072
+ throw createError(
1073
+ `Pack file not found at ${path.relative(process.cwd(), packPath)}.`
1074
+ );
757
1075
  const bytes = Uint8Array.from(readFileSync(packPath));
1076
+ if (core.isKnowledgeImageV5?.(bytes)) {
1077
+ const verification = core.verifyKnowledgeImageV5(bytes);
1078
+ console.log(
1079
+ JSON.stringify(
1080
+ {
1081
+ verified: verification.valid,
1082
+ format: 'v5-knowledge-image',
1083
+ version: 5,
1084
+ stateRoot: verification.stateRoot,
1085
+ commitDigest: verification.commitDigest,
1086
+ },
1087
+ null,
1088
+ 2
1089
+ )
1090
+ );
1091
+ return;
1092
+ }
758
1093
  const pack = await mountPackFromBytes(core, bytes);
759
- console.log(JSON.stringify({ verified: true, format: pack.meta?.format || (pack.meta?.version === 4 ? 'v4' : `v${pack.meta?.version}`), blocks: pack.blocks.length, terms: pack.meta?.stats?.terms ?? pack.lexicon.size }, null, 2));
1094
+ console.log(
1095
+ JSON.stringify(
1096
+ {
1097
+ verified: true,
1098
+ format:
1099
+ pack.meta?.format ||
1100
+ (pack.meta?.version === 4 ? 'v4' : `v${pack.meta?.version}`),
1101
+ blocks: pack.blocks.length,
1102
+ terms: pack.meta?.stats?.terms ?? pack.lexicon.size,
1103
+ },
1104
+ null,
1105
+ 2
1106
+ )
1107
+ );
760
1108
  }
761
1109
 
762
1110
  async function cmdMigrate(core, args) {
763
1111
  const { positional, flags } = parsePackCommandArgs(args, 'migrate');
764
1112
  const input = positional[0];
765
- if (!input || !flags.out) throw createError('Usage: knolo migrate <pack.knolo> --out <new-pack.knolo> --to 4');
766
- if (String(flags.to || '4') !== '4') throw createError('Only migration to format 4 is currently supported.');
1113
+ if (!input || !flags.out)
1114
+ throw createError(
1115
+ 'Usage: knolo migrate <pack.knolo> --out <new-pack.knolo> --to 4'
1116
+ );
1117
+ if (String(flags.to || '4') !== '4')
1118
+ throw createError('Only migration to format 4 is currently supported.');
767
1119
  const inputPath = path.resolve(process.cwd(), input);
768
- if (!existsSync(inputPath)) throw createError(`Pack file not found at ${path.relative(process.cwd(), inputPath)}.`);
1120
+ if (!existsSync(inputPath))
1121
+ throw createError(
1122
+ `Pack file not found at ${path.relative(process.cwd(), inputPath)}.`
1123
+ );
769
1124
  const bytes = Uint8Array.from(readFileSync(inputPath));
770
1125
  const pack = await mountPackFromBytes(core, bytes);
771
- if (core.isPackV4?.(bytes.buffer)) throw createError('Input pack is already v4.');
772
- const docs = pack.blocks.map((text, i) => ({ id: pack.docIds?.[i] ?? undefined, heading: pack.headings?.[i] ?? undefined, namespace: pack.namespaces?.[i] ?? undefined, text }));
1126
+ if (core.isPackV4?.(bytes.buffer))
1127
+ throw createError('Input pack is already v4.');
1128
+ const docs = pack.blocks.map((text, i) => ({
1129
+ id: pack.docIds?.[i] ?? undefined,
1130
+ heading: pack.headings?.[i] ?? undefined,
1131
+ namespace: pack.namespaces?.[i] ?? undefined,
1132
+ text,
1133
+ }));
773
1134
  const migrated = core.serializePackV4(pack, docs);
774
1135
  const outPath = path.resolve(process.cwd(), flags.out);
775
1136
  mkdirSync(path.dirname(outPath), { recursive: true });
776
1137
  writeFileSync(outPath, Buffer.from(migrated));
777
- console.log(`✔ migrated ${path.relative(process.cwd(), inputPath)} to ${path.relative(process.cwd(), outPath)}`);
1138
+ console.log(
1139
+ `✔ migrated ${path.relative(process.cwd(), inputPath)} to ${path.relative(process.cwd(), outPath)}`
1140
+ );
778
1141
  }
779
1142
 
780
1143
  async function cmdExplain(core, args) {
781
1144
  const { positional, flags } = parsePackCommandArgs(args, 'explain');
782
1145
  const receiptPath = positional[0];
783
- if (!receiptPath || !flags.out && !flags.pack) throw createError('Usage: knolo explain <receipt.json> --pack <pack.knolo>');
1146
+ if (!receiptPath || (!flags.out && !flags.pack))
1147
+ throw createError(
1148
+ 'Usage: knolo explain <receipt.json> --pack <pack.knolo|image.v5>'
1149
+ );
784
1150
  const packPath = path.resolve(process.cwd(), flags.pack);
785
- const receipt = JSON.parse(readFileSync(path.resolve(process.cwd(), receiptPath), 'utf8'));
786
- const pack = await mountPackFromBytes(core, Uint8Array.from(readFileSync(packPath)));
1151
+ const receipt = JSON.parse(
1152
+ readFileSync(path.resolve(process.cwd(), receiptPath), 'utf8')
1153
+ );
1154
+ const bytes = Uint8Array.from(readFileSync(packPath));
1155
+ if (core.isKnowledgeImageV5?.(bytes)) {
1156
+ core.verifyKnowledgeQueryResultV5(bytes, receipt);
1157
+ const image = core.mountKnowledgeImageV5(bytes);
1158
+ const objects = new Map(image.objects.map((object) => [object.id, object]));
1159
+ console.log(
1160
+ JSON.stringify(
1161
+ {
1162
+ verified: true,
1163
+ contract: 'v5-knowledge-image',
1164
+ query: receipt.plan,
1165
+ stateRoot: receipt.stateRoot,
1166
+ planRoot: receipt.planRoot,
1167
+ resultRoot: receipt.resultRoot,
1168
+ hits: receipt.hits.map((hit) => {
1169
+ const object = objects.get(hit.objectId);
1170
+ return {
1171
+ objectId: hit.objectId,
1172
+ kind: hit.kind,
1173
+ source:
1174
+ typeof object?.meta?.source === 'string'
1175
+ ? object.meta.source
1176
+ : typeof object?.meta?.docId === 'string'
1177
+ ? object.meta.docId
1178
+ : undefined,
1179
+ };
1180
+ }),
1181
+ },
1182
+ null,
1183
+ 2
1184
+ )
1185
+ );
1186
+ return;
1187
+ }
1188
+ const pack = await mountPackFromBytes(core, bytes);
787
1189
  core.verifyReceipt(receipt, pack);
788
- console.log(JSON.stringify({ verified: true, decision: receipt.decision, answerability: receipt.answerability, reasons: receipt.reasons, query: receipt.normalizedQuery, hits: receipt.hits.map((hit) => ({ blockId: hit.blockId, score: hit.score, source: hit.source, spans: hit.spans })) }, null, 2));
1190
+ console.log(
1191
+ JSON.stringify(
1192
+ {
1193
+ verified: true,
1194
+ decision: receipt.decision,
1195
+ answerability: receipt.answerability,
1196
+ reasons: receipt.reasons,
1197
+ query: receipt.normalizedQuery,
1198
+ hits: receipt.hits.map((hit) => ({
1199
+ blockId: hit.blockId,
1200
+ score: hit.score,
1201
+ source: hit.source,
1202
+ spans: hit.spans,
1203
+ })),
1204
+ },
1205
+ null,
1206
+ 2
1207
+ )
1208
+ );
789
1209
  }
790
1210
 
791
1211
  async function cmdDiff(core, args) {
792
1212
  const { positional } = parsePackCommandArgs(args, 'diff');
793
- if (positional.length < 2) throw createError('Usage: knolo diff <pack-a.knolo> <pack-b.knolo>');
794
- const load = async (file) => { const bytes = Uint8Array.from(readFileSync(path.resolve(process.cwd(), file))); return { bytes, pack: await mountPackFromBytes(core, bytes) }; };
795
- const a = await load(positional[0]); const b = await load(positional[1]);
796
- const aDigest = core.packDigest(a.pack); const bDigest = core.packDigest(b.pack);
797
- const aSources = new Set(a.pack.docIds?.filter(Boolean) ?? []); const bSources = new Set(b.pack.docIds?.filter(Boolean) ?? []);
798
- console.log(JSON.stringify({ equal: aDigest === bDigest, a: { packDigest: aDigest, blocks: a.pack.blocks.length, terms: a.pack.lexicon.size }, b: { packDigest: bDigest, blocks: b.pack.blocks.length, terms: b.pack.lexicon.size }, addedSources: [...bSources].filter((id) => !aSources.has(id)).sort(), removedSources: [...aSources].filter((id) => !bSources.has(id)).sort() }, null, 2));
1213
+ if (positional.length < 2)
1214
+ throw createError('Usage: knolo diff <pack-a.knolo> <pack-b.knolo>');
1215
+ const load = async (file) => {
1216
+ const bytes = Uint8Array.from(
1217
+ readFileSync(path.resolve(process.cwd(), file))
1218
+ );
1219
+ return { bytes, pack: await mountPackFromBytes(core, bytes) };
1220
+ };
1221
+ const a = await load(positional[0]);
1222
+ const b = await load(positional[1]);
1223
+ const aDigest = core.packDigest(a.pack);
1224
+ const bDigest = core.packDigest(b.pack);
1225
+ const aSources = new Set(a.pack.docIds?.filter(Boolean) ?? []);
1226
+ const bSources = new Set(b.pack.docIds?.filter(Boolean) ?? []);
1227
+ console.log(
1228
+ JSON.stringify(
1229
+ {
1230
+ equal: aDigest === bDigest,
1231
+ a: {
1232
+ packDigest: aDigest,
1233
+ blocks: a.pack.blocks.length,
1234
+ terms: a.pack.lexicon.size,
1235
+ },
1236
+ b: {
1237
+ packDigest: bDigest,
1238
+ blocks: b.pack.blocks.length,
1239
+ terms: b.pack.lexicon.size,
1240
+ },
1241
+ addedSources: [...bSources].filter((id) => !aSources.has(id)).sort(),
1242
+ removedSources: [...aSources].filter((id) => !bSources.has(id)).sort(),
1243
+ },
1244
+ null,
1245
+ 2
1246
+ )
1247
+ );
799
1248
  }
800
1249
 
801
1250
  function parseV5Args(args) {
@@ -803,8 +1252,21 @@ function parseV5Args(args) {
803
1252
  const flags = {};
804
1253
  for (let i = 0; i < args.length; i++) {
805
1254
  const arg = args[i];
806
- if (arg === '--image' || arg === '--index' || arg === '--history' || arg === '--run' || arg === '--replay') flags[arg.slice(2)] = args[++i];
807
- else if (arg.startsWith('--')) throw createError(`Unknown flag for v5: ${arg}`);
1255
+ if (arg === '--attach-index') flags.attachIndex = true;
1256
+ else if (arg === '--json') flags.json = true;
1257
+ else if (arg === '--receipt') flags.receipt = args[++i];
1258
+ else if (
1259
+ arg === '--image' ||
1260
+ arg === '--index' ||
1261
+ arg === '--history' ||
1262
+ arg === '--run' ||
1263
+ arg === '--replay' ||
1264
+ arg === '--out' ||
1265
+ arg === '--mode'
1266
+ )
1267
+ flags[arg.slice(2)] = args[++i];
1268
+ else if (arg.startsWith('--'))
1269
+ throw createError(`Unknown flag for v5: ${arg}`);
808
1270
  else positional.push(arg);
809
1271
  }
810
1272
  return { positional, flags };
@@ -812,24 +1274,129 @@ function parseV5Args(args) {
812
1274
 
813
1275
  async function cmdV5(core, args) {
814
1276
  const subcommand = args[0] || 'info';
815
- if (!['info', 'health', 'studio'].includes(subcommand)) throw createError(`Unknown V5 command: ${subcommand}`);
816
- const { positional, flags } = parseV5Args(args.slice(['info', 'health', 'studio'].includes(args[0]) ? 1 : 0));
817
- const imagePath = path.resolve(process.cwd(), flags.image || positional[0] || 'dist/knowledge.v5');
818
- if (!existsSync(imagePath)) throw createError(`V5 image file not found at ${path.relative(process.cwd(), imagePath)}.`);
819
- const readOptional = (file, decode) => file ? decode(Uint8Array.from(readFileSync(path.resolve(process.cwd(), file)))) : undefined;
1277
+ const subcommands = [
1278
+ 'info',
1279
+ 'health',
1280
+ 'studio',
1281
+ 'query',
1282
+ 'compress',
1283
+ 'decompress',
1284
+ ];
1285
+ if (!subcommands.includes(subcommand))
1286
+ throw createError(`Unknown V5 command: ${subcommand}`);
1287
+ const { positional, flags } = parseV5Args(
1288
+ args.slice(subcommands.includes(args[0]) ? 1 : 0)
1289
+ );
1290
+ if (subcommand === 'query') {
1291
+ const imagePath = path.resolve(
1292
+ process.cwd(),
1293
+ flags.image || positional[0] || 'dist/knowledge.v5'
1294
+ );
1295
+ if (!existsSync(imagePath))
1296
+ throw createError(
1297
+ `V5 image file not found at ${path.relative(process.cwd(), imagePath)}.`
1298
+ );
1299
+ const expression = (flags.image ? positional : positional.slice(1))
1300
+ .join(' ')
1301
+ .trim();
1302
+ if (!expression)
1303
+ throw createError(
1304
+ 'Usage: knolo v5 query <image.v5> <EQL> [--json] [--receipt <file>]'
1305
+ );
1306
+ const bytes = Uint8Array.from(readFileSync(imagePath));
1307
+ if (!core.isKnowledgeImageV5?.(bytes))
1308
+ throw createError('V5 query requires a V5 Knowledge Image input.');
1309
+ return await cmdQueryV5(core, bytes, imagePath, expression, flags);
1310
+ }
1311
+ const imagePath = path.resolve(
1312
+ process.cwd(),
1313
+ flags.image || positional[0] || 'dist/knowledge.v5'
1314
+ );
1315
+ if (!existsSync(imagePath))
1316
+ throw createError(
1317
+ `V5 image file not found at ${path.relative(process.cwd(), imagePath)}.`
1318
+ );
1319
+ if (subcommand === 'compress' || subcommand === 'decompress') {
1320
+ if (!flags.out)
1321
+ throw createError(
1322
+ `Usage: knolo v5 ${subcommand} <image.v5> --out <file>${subcommand === 'compress' ? ' [--mode fast|balanced|max] [--index]' : ''}`
1323
+ );
1324
+ const input = Uint8Array.from(readFileSync(imagePath));
1325
+ const result =
1326
+ subcommand === 'compress'
1327
+ ? core.compressKnowledgeImageV5(input, {
1328
+ mode: flags.mode,
1329
+ index: flags.attachIndex,
1330
+ })
1331
+ : core.compressKnowledgeImageV5(input, {
1332
+ objects: false,
1333
+ events: false,
1334
+ });
1335
+ const outPath = path.resolve(process.cwd(), flags.out);
1336
+ mkdirSync(path.dirname(outPath), { recursive: true });
1337
+ writeFileSync(outPath, Buffer.from(result.bytes));
1338
+ const before = input.length;
1339
+ console.log(
1340
+ JSON.stringify(
1341
+ {
1342
+ operation: subcommand,
1343
+ input: path.relative(process.cwd(), imagePath),
1344
+ output: path.relative(process.cwd(), outPath),
1345
+ inputBytes: before,
1346
+ outputBytes: result.bytes.length,
1347
+ reduction: before ? 1 - result.bytes.length / before : 0,
1348
+ stateRoot: result.stateRoot,
1349
+ commitDigest: result.commitDigest,
1350
+ },
1351
+ null,
1352
+ 2
1353
+ )
1354
+ );
1355
+ return;
1356
+ }
1357
+ const readOptional = (file, decode) =>
1358
+ file
1359
+ ? decode(Uint8Array.from(readFileSync(path.resolve(process.cwd(), file))))
1360
+ : undefined;
820
1361
  const input = {
821
1362
  image: Uint8Array.from(readFileSync(imagePath)),
822
- ...(flags.index ? { queryIndex: readOptional(flags.index, core.deserializeKnowledgeQueryIndexV1) } : {}),
823
- ...(flags.history ? { queryHistory: readOptional(flags.history, core.deserializeKnowledgeQueryHistoryV1) } : {}),
824
- ...(flags.run ? { run: readOptional(flags.run, core.deserializeKnowledgeRunV1) } : {}),
825
- ...(flags.replay ? { replayState: readOptional(flags.replay, core.deserializeKnowledgeSyncReplayStateV1) } : {}),
1363
+ ...(flags.index
1364
+ ? {
1365
+ queryIndex: readOptional(
1366
+ flags.index,
1367
+ core.deserializeKnowledgeQueryIndexV1
1368
+ ),
1369
+ }
1370
+ : {}),
1371
+ ...(flags.history
1372
+ ? {
1373
+ queryHistory: readOptional(
1374
+ flags.history,
1375
+ core.deserializeKnowledgeQueryHistoryV1
1376
+ ),
1377
+ }
1378
+ : {}),
1379
+ ...(flags.run
1380
+ ? { run: readOptional(flags.run, core.deserializeKnowledgeRunV1) }
1381
+ : {}),
1382
+ ...(flags.replay
1383
+ ? {
1384
+ replayState: readOptional(
1385
+ flags.replay,
1386
+ core.deserializeKnowledgeSyncReplayStateV1
1387
+ ),
1388
+ }
1389
+ : {}),
826
1390
  };
827
1391
  if (subcommand === 'studio') {
828
- console.log(JSON.stringify(core.inspectKnowledgeStudioManagementV5(input), null, 2));
1392
+ console.log(
1393
+ JSON.stringify(core.inspectKnowledgeStudioManagementV5(input), null, 2)
1394
+ );
829
1395
  return;
830
1396
  }
831
1397
  const diagnostics = core.inspectKnowledgeRuntimeV5(input);
832
- if (subcommand === 'health') console.log(JSON.stringify({ healthy: true, ...diagnostics }, null, 2));
1398
+ if (subcommand === 'health')
1399
+ console.log(JSON.stringify({ healthy: true, ...diagnostics }, null, 2));
833
1400
  else console.log(JSON.stringify(diagnostics, null, 2));
834
1401
  }
835
1402
 
@@ -844,22 +1411,39 @@ function parseKeyValueArgs(args) {
844
1411
  }
845
1412
 
846
1413
  async function loadOllamaProvider() {
847
- const mod = await tryImport(path.resolve(__dirname, '../../semantic-ollama/dist/index.js'));
1414
+ const mod = await tryImport(
1415
+ path.resolve(__dirname, '../../semantic-ollama/dist/index.js')
1416
+ );
848
1417
  if (mod?.OllamaEmbeddingProvider) return mod.OllamaEmbeddingProvider;
849
1418
  const pkg = await tryImport('@knolo/semantic-ollama');
850
1419
  if (pkg?.OllamaEmbeddingProvider) return pkg.OllamaEmbeddingProvider;
851
- throw createError('Could not load @knolo/semantic-ollama. Build packages/semantic-ollama first.');
1420
+ throw createError(
1421
+ 'Could not load @knolo/semantic-ollama. Build packages/semantic-ollama first.'
1422
+ );
852
1423
  }
853
1424
 
854
1425
  async function cmdSemanticIndex(core, args) {
855
1426
  const flags = parseKeyValueArgs(args);
856
- const packPath = path.resolve(process.cwd(), flags.pack || 'dist/knowledge.knolo');
857
- const outPath = path.resolve(process.cwd(), flags.out || `${packPath}.semantic.json`);
1427
+ const packPath = path.resolve(
1428
+ process.cwd(),
1429
+ flags.pack || 'dist/knowledge.knolo'
1430
+ );
1431
+ const outPath = path.resolve(
1432
+ process.cwd(),
1433
+ flags.out || `${packPath}.semantic.json`
1434
+ );
858
1435
  const modelId = flags.model || 'qwen3-embedding:4b';
859
1436
  const endpoint = flags.endpoint || 'http://localhost:11434';
860
- if (!existsSync(packPath)) throw createError(`Pack file not found at ${path.relative(process.cwd(), packPath)}.`);
1437
+ if (!existsSync(packPath))
1438
+ throw createError(
1439
+ `Pack file not found at ${path.relative(process.cwd(), packPath)}.`
1440
+ );
861
1441
 
862
1442
  const bytes = Uint8Array.from(readFileSync(packPath));
1443
+ if (core.isKnowledgeImageV5?.(bytes))
1444
+ throw createError(
1445
+ 'semantic:index targets V4 packs. Query V5 images with bounded EQL through knolo query.'
1446
+ );
863
1447
  const pack = await mountPackFromBytes(core, bytes);
864
1448
  const OllamaEmbeddingProvider = await loadOllamaProvider();
865
1449
  const provider = new OllamaEmbeddingProvider({ modelId, endpoint });
@@ -871,7 +1455,10 @@ async function cmdSemanticIndex(core, args) {
871
1455
  dimension: vectors[0]?.length ?? 0,
872
1456
  metric: 'cosine',
873
1457
  createdAt: new Date().toISOString(),
874
- blocks: vectors.map((vector, blockId) => ({ blockId, vector: Array.from(core.normalizeVector(vector)) })),
1458
+ blocks: vectors.map((vector, blockId) => ({
1459
+ blockId,
1460
+ vector: Array.from(core.normalizeVector(vector)),
1461
+ })),
875
1462
  };
876
1463
  writeFileSync(outPath, core.serializeSidecar(sidecar));
877
1464
  console.log(`✔ wrote ${path.relative(process.cwd(), outPath)}`);
@@ -881,28 +1468,46 @@ async function cmdSemanticInspect(core, args) {
881
1468
  const flags = parseKeyValueArgs(args);
882
1469
  const sidecarPath = path.resolve(process.cwd(), flags.sidecar);
883
1470
  const sidecar = core.parseSidecar(readFileSync(sidecarPath, 'utf8'));
884
- console.log(JSON.stringify({
885
- version: sidecar.version,
886
- packFingerprint: sidecar.packFingerprint,
887
- modelId: sidecar.modelId,
888
- dimension: sidecar.dimension,
889
- metric: sidecar.metric,
890
- createdAt: sidecar.createdAt,
891
- blocks: sidecar.blocks.length,
892
- }, null, 2));
1471
+ console.log(
1472
+ JSON.stringify(
1473
+ {
1474
+ version: sidecar.version,
1475
+ packFingerprint: sidecar.packFingerprint,
1476
+ modelId: sidecar.modelId,
1477
+ dimension: sidecar.dimension,
1478
+ metric: sidecar.metric,
1479
+ createdAt: sidecar.createdAt,
1480
+ blocks: sidecar.blocks.length,
1481
+ },
1482
+ null,
1483
+ 2
1484
+ )
1485
+ );
893
1486
  }
894
1487
 
895
1488
  async function cmdSemanticValidate(core, args) {
896
1489
  const flags = parseKeyValueArgs(args);
897
- const packPath = path.resolve(process.cwd(), flags.pack || 'dist/knowledge.knolo');
1490
+ const packPath = path.resolve(
1491
+ process.cwd(),
1492
+ flags.pack || 'dist/knowledge.knolo'
1493
+ );
898
1494
  const sidecarPath = path.resolve(process.cwd(), flags.sidecar);
899
1495
  const modelId = flags.model;
900
1496
  if (!modelId) throw createError('semantic:validate requires --model <id>.');
901
- const pack = await mountPackFromBytes(core, Uint8Array.from(readFileSync(packPath)));
1497
+ const packBytes = Uint8Array.from(readFileSync(packPath));
1498
+ if (core.isKnowledgeImageV5?.(packBytes))
1499
+ throw createError(
1500
+ 'semantic:validate targets V4 packs. Query V5 images with bounded EQL through knolo query.'
1501
+ );
1502
+ const pack = await mountPackFromBytes(core, packBytes);
902
1503
  const sidecar = core.parseSidecar(readFileSync(sidecarPath, 'utf8'));
903
1504
  core.validateSidecarForPack({ sidecar, pack, modelId });
904
- if (sidecar.blocks.length !== pack.blocks.length) throw createError(`Semantic block count mismatch: sidecar=${sidecar.blocks.length}, pack=${pack.blocks.length}`);
905
- if (sidecar.dimension <= 0) throw createError('Semantic sidecar dimension must be > 0.');
1505
+ if (sidecar.blocks.length !== pack.blocks.length)
1506
+ throw createError(
1507
+ `Semantic block count mismatch: sidecar=${sidecar.blocks.length}, pack=${pack.blocks.length}`
1508
+ );
1509
+ if (sidecar.dimension <= 0)
1510
+ throw createError('Semantic sidecar dimension must be > 0.');
906
1511
  console.log('✔ semantic sidecar validation passed');
907
1512
  }
908
1513
 
@@ -951,7 +1556,10 @@ async function cmdDev(core) {
951
1556
  };
952
1557
 
953
1558
  const closers = [];
954
- const configWatcher = watch(path.resolve(process.cwd(), CONFIG_FILE), schedule);
1559
+ const configWatcher = watch(
1560
+ path.resolve(process.cwd(), CONFIG_FILE),
1561
+ schedule
1562
+ );
955
1563
  closers.push(() => configWatcher.close());
956
1564
 
957
1565
  const config = readConfig();
@@ -1084,14 +1692,18 @@ function loadAgentsFromDir(agentsDir, core) {
1084
1692
  const entries = readdirSync(path.resolve(agentsDir), { withFileTypes: true })
1085
1693
  .filter((e) => e.isFile())
1086
1694
  .map((e) => e.name)
1087
- .filter((name) => ['.json', '.yaml', '.yml'].includes(path.extname(name).toLowerCase()))
1695
+ .filter((name) =>
1696
+ ['.json', '.yaml', '.yml'].includes(path.extname(name).toLowerCase())
1697
+ )
1088
1698
  .sort((a, b) => a.localeCompare(b));
1089
1699
 
1090
1700
  const loaded = entries.map((file) => {
1091
1701
  const full = path.join(path.resolve(agentsDir), file);
1092
1702
  const parsed = parseAgentFileContent(readFileSync(full, 'utf8'), full);
1093
- const agent = parsed?.agent && typeof parsed.agent === 'object' ? parsed.agent : parsed;
1094
- if (typeof core.validateAgentDefinition === 'function') core.validateAgentDefinition(agent);
1703
+ const agent =
1704
+ parsed?.agent && typeof parsed.agent === 'object' ? parsed.agent : parsed;
1705
+ if (typeof core.validateAgentDefinition === 'function')
1706
+ core.validateAgentDefinition(agent);
1095
1707
  return { file, agent };
1096
1708
  });
1097
1709
 
@@ -1103,26 +1715,41 @@ function loadAgentsFromDir(agentsDir, core) {
1103
1715
  }
1104
1716
  for (const [id, files] of dupes.entries()) {
1105
1717
  if (files.length > 1) {
1106
- throw createError(`Duplicate agent id "${id}" found in files: ${files.sort((a, b) => a.localeCompare(b)).join(', ')}`);
1718
+ throw createError(
1719
+ `Duplicate agent id "${id}" found in files: ${files.sort((a, b) => a.localeCompare(b)).join(', ')}`
1720
+ );
1107
1721
  }
1108
1722
  }
1109
1723
 
1110
- const agents = loaded.map((x) => x.agent).sort((a, b) => String(a.id).localeCompare(String(b.id)));
1724
+ const agents = loaded
1725
+ .map((x) => x.agent)
1726
+ .sort((a, b) => String(a.id).localeCompare(String(b.id)));
1111
1727
  const registry = { version: 1, agents };
1112
- if (typeof core.validateAgentRegistry === 'function') core.validateAgentRegistry(registry);
1728
+ if (typeof core.validateAgentRegistry === 'function')
1729
+ core.validateAgentRegistry(registry);
1113
1730
  return registry;
1114
1731
  }
1115
1732
 
1116
1733
  function loadEmbeddingsFromJson(filePath, expectedCount) {
1117
1734
  const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
1118
- const vectors = Array.isArray(parsed?.embeddings) ? parsed.embeddings : parsed;
1119
- if (!Array.isArray(vectors)) throw createError('Embeddings JSON must be an array or { embeddings: [...] }.');
1120
- if (vectors.length !== expectedCount) throw createError(`Embeddings length mismatch: expected ${expectedCount}, got ${vectors.length}.`);
1735
+ const vectors = Array.isArray(parsed?.embeddings)
1736
+ ? parsed.embeddings
1737
+ : parsed;
1738
+ if (!Array.isArray(vectors))
1739
+ throw createError(
1740
+ 'Embeddings JSON must be an array or { embeddings: [...] }.'
1741
+ );
1742
+ if (vectors.length !== expectedCount)
1743
+ throw createError(
1744
+ `Embeddings length mismatch: expected ${expectedCount}, got ${vectors.length}.`
1745
+ );
1121
1746
  return vectors.map((entry, i) => {
1122
- if (!Array.isArray(entry)) throw createError(`Embeddings[${i}] must be an array.`);
1747
+ if (!Array.isArray(entry))
1748
+ throw createError(`Embeddings[${i}] must be an array.`);
1123
1749
  const vec = new Float32Array(entry.length);
1124
1750
  for (let d = 0; d < entry.length; d++) {
1125
- if (!Number.isFinite(entry[d])) throw createError(`Embeddings[${i}][${d}] must be numeric.`);
1751
+ if (!Number.isFinite(entry[d]))
1752
+ throw createError(`Embeddings[${i}][${d}] must be numeric.`);
1126
1753
  vec[d] = entry[d];
1127
1754
  }
1128
1755
  return vec;
@@ -1147,7 +1774,9 @@ function parseDirectModeArgs(args) {
1147
1774
  async function runDirectMode(core, firstArg, restArgs) {
1148
1775
  const { positional, flags } = parseDirectModeArgs([firstArg, ...restArgs]);
1149
1776
  if (flags.help) {
1150
- console.log('Usage: knolo <input.json> [output.knolo] [--agents ./agents] [--embeddings embeddings.json --model-id model]');
1777
+ console.log(
1778
+ 'Usage: knolo <input.json> [output.knolo] [--agents ./agents] [--embeddings embeddings.json --model-id model]'
1779
+ );
1151
1780
  return;
1152
1781
  }
1153
1782
 
@@ -1155,12 +1784,18 @@ async function runDirectMode(core, firstArg, restArgs) {
1155
1784
  const outFile = positional[1] || 'knowledge.knolo';
1156
1785
  if (!inFile) throw createError('Usage: knolo <input.json> [output.knolo]');
1157
1786
 
1158
- const docs = JSON.parse(readFileSync(path.resolve(process.cwd(), inFile), 'utf8'));
1159
- if (!Array.isArray(docs)) throw createError('Input JSON must be an array of docs.');
1787
+ const docs = JSON.parse(
1788
+ readFileSync(path.resolve(process.cwd(), inFile), 'utf8')
1789
+ );
1790
+ if (!Array.isArray(docs))
1791
+ throw createError('Input JSON must be an array of docs.');
1160
1792
 
1161
1793
  const options = {};
1162
1794
  if (flags.embeddingsPath || flags.modelId) {
1163
- if (!flags.embeddingsPath || !flags.modelId) throw createError('Both --embeddings and --model-id are required together.');
1795
+ if (!flags.embeddingsPath || !flags.modelId)
1796
+ throw createError(
1797
+ 'Both --embeddings and --model-id are required together.'
1798
+ );
1164
1799
  options.semantic = {
1165
1800
  enabled: true,
1166
1801
  modelId: flags.modelId,
@@ -1182,14 +1817,19 @@ async function main() {
1182
1817
  try {
1183
1818
  if (command === 'help') return printRootHelp();
1184
1819
  if (command === 'icp') {
1185
- if (commandArgs.length === 0 || commandArgs[0] === '--help' || commandArgs[0] === '-h') {
1820
+ if (
1821
+ commandArgs.length === 0 ||
1822
+ commandArgs[0] === '--help' ||
1823
+ commandArgs[0] === '-h'
1824
+ ) {
1186
1825
  return printIcpHelp();
1187
1826
  }
1188
1827
  return await cmdIcp(commandArgs);
1189
1828
  }
1190
1829
 
1191
1830
  if (SUBCOMMANDS.has(command)) {
1192
- if (commandArgs.includes('--help') || commandArgs.includes('-h')) return printCommandHelp(command);
1831
+ if (commandArgs.includes('--help') || commandArgs.includes('-h'))
1832
+ return printCommandHelp(command);
1193
1833
  if (command === 'init') return await cmdInit();
1194
1834
  if (command === 'add') return await cmdAdd(commandArgs);
1195
1835
  if (command === 'search') return await runHubSearch(commandArgs);
@@ -1210,17 +1850,24 @@ async function main() {
1210
1850
  if (command === 'diff') return await cmdDiff(core, commandArgs);
1211
1851
  if (command === 'dev') return await cmdDev(core);
1212
1852
  if (command === 'v5') return await cmdV5(core, commandArgs);
1213
- if (command === 'semantic:index') return await cmdSemanticIndex(core, commandArgs);
1214
- if (command === 'semantic:inspect') return await cmdSemanticInspect(core, commandArgs);
1215
- if (command === 'semantic:validate') return await cmdSemanticValidate(core, commandArgs);
1853
+ if (command === 'semantic:index')
1854
+ return await cmdSemanticIndex(core, commandArgs);
1855
+ if (command === 'semantic:inspect')
1856
+ return await cmdSemanticInspect(core, commandArgs);
1857
+ if (command === 'semantic:validate')
1858
+ return await cmdSemanticValidate(core, commandArgs);
1216
1859
  }
1217
1860
 
1218
- if (command.startsWith('-')) throw createError(`Unknown option: ${command}`);
1861
+ if (command.startsWith('-'))
1862
+ throw createError(`Unknown option: ${command}`);
1219
1863
  const core = await loadCore();
1220
1864
  return await runDirectMode(core, command, commandArgs);
1221
1865
  } catch (error) {
1222
- console.error(`knolo: ${error instanceof Error ? error.message : String(error)}`);
1223
- if (global.debug && error instanceof Error && error.stack) console.error(error.stack);
1866
+ console.error(
1867
+ `knolo: ${error instanceof Error ? error.message : String(error)}`
1868
+ );
1869
+ if (global.debug && error instanceof Error && error.stack)
1870
+ console.error(error.stack);
1224
1871
  process.exit(1);
1225
1872
  }
1226
1873
  }