@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/README.md +24 -2
- package/bin/knolo.mjs +804 -157
- package/package.json +2 -2
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/Cargo.toml +1 -1
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/lib.rs +294 -3
- package/templates/icp-knowledge-canister/canisters/knolo-icp-canister/Cargo.lock +2 -2
- package/templates/icp-knowledge-canister/canisters/knolo-icp-canister/Cargo.toml +2 -2
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 {
|
|
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([
|
|
37
|
-
|
|
38
|
-
|
|
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 (
|
|
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 (
|
|
119
|
+
if (
|
|
120
|
+
mod?.default?.buildPack &&
|
|
121
|
+
mod?.default?.mountPack &&
|
|
122
|
+
mod?.default?.query
|
|
123
|
+
)
|
|
124
|
+
return mod.default;
|
|
70
125
|
}
|
|
71
|
-
throw createError(
|
|
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>
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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> [--
|
|
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':
|
|
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':
|
|
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')
|
|
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(
|
|
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')
|
|
245
|
-
|
|
246
|
-
if (
|
|
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')
|
|
249
|
-
|
|
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(
|
|
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(
|
|
355
|
+
const visibleEntries = readdirSync(docsDir).filter(
|
|
356
|
+
(name) => !name.startsWith('.')
|
|
357
|
+
);
|
|
281
358
|
if (visibleEntries.length === 0 && !existsSync(samplePath)) {
|
|
282
|
-
writeFileSync(
|
|
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))
|
|
383
|
+
if (!existsSync(resolved))
|
|
384
|
+
throw createError(`Source path does not exist: ${sourcePath}`);
|
|
303
385
|
|
|
304
386
|
const config = readConfig();
|
|
305
|
-
const normalized =
|
|
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())
|
|
405
|
+
if (existsSync(candidate) && statSync(candidate).isDirectory())
|
|
406
|
+
return candidate;
|
|
321
407
|
}
|
|
322
|
-
throw createError(
|
|
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())
|
|
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) =>
|
|
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
|
|
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 (
|
|
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))
|
|
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))
|
|
563
|
+
for (const file of await walkDir(resolved))
|
|
564
|
+
files.push({ sourceName: source.name, absolutePath: file });
|
|
463
565
|
} else {
|
|
464
|
-
throw createError(
|
|
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)
|
|
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
|
|
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))
|
|
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(
|
|
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())
|
|
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(
|
|
552
|
-
|
|
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)
|
|
680
|
+
if (!files.length)
|
|
681
|
+
throw createError('No supported files found (.md, .txt, .json).');
|
|
566
682
|
|
|
567
|
-
const rootDir = statSync(resolvedInput).isDirectory()
|
|
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(
|
|
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))
|
|
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
|
-
[
|
|
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(
|
|
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
|
-
[
|
|
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)
|
|
627
|
-
|
|
628
|
-
|
|
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)
|
|
635
|
-
|
|
636
|
-
|
|
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)
|
|
643
|
-
|
|
644
|
-
|
|
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))
|
|
652
|
-
|
|
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('--'))
|
|
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)
|
|
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))
|
|
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))
|
|
868
|
+
if (!existsSync(packPath))
|
|
869
|
+
throw createError(
|
|
870
|
+
`Pack file not found at ${path.relative(process.cwd(), packPath)}.`
|
|
871
|
+
);
|
|
689
872
|
|
|
690
|
-
const topK =
|
|
691
|
-
|
|
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 =
|
|
888
|
+
const receiptResult =
|
|
889
|
+
opts.receipt && core.queryWithReceipt
|
|
890
|
+
? core.queryWithReceipt(kb, question, { topK })
|
|
891
|
+
: null;
|
|
697
892
|
if (receiptResult && opts.receipt) {
|
|
698
|
-
writeFileSync(
|
|
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) => ({
|
|
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(
|
|
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')
|
|
736
|
-
|
|
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(
|
|
745
|
-
|
|
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 = {
|
|
749
|
-
|
|
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(
|
|
756
|
-
|
|
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(
|
|
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)
|
|
766
|
-
|
|
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))
|
|
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))
|
|
772
|
-
|
|
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(
|
|
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)
|
|
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(
|
|
786
|
-
|
|
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(
|
|
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)
|
|
794
|
-
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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 === '--
|
|
807
|
-
else if (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
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
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
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
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(
|
|
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')
|
|
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(
|
|
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(
|
|
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(
|
|
857
|
-
|
|
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))
|
|
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) => ({
|
|
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(
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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(
|
|
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
|
|
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)
|
|
905
|
-
|
|
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(
|
|
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) =>
|
|
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 =
|
|
1094
|
-
|
|
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(
|
|
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
|
|
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')
|
|
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)
|
|
1119
|
-
|
|
1120
|
-
|
|
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))
|
|
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]))
|
|
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(
|
|
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(
|
|
1159
|
-
|
|
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)
|
|
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 (
|
|
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'))
|
|
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')
|
|
1214
|
-
|
|
1215
|
-
if (command === 'semantic:
|
|
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('-'))
|
|
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(
|
|
1223
|
-
|
|
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
|
}
|