@knolo/cli 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/knolo.mjs +599 -0
  2. package/package.json +19 -0
package/bin/knolo.mjs ADDED
@@ -0,0 +1,599 @@
1
+ #!/usr/bin/env node
2
+
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ readFileSync,
7
+ readdirSync,
8
+ statSync,
9
+ watch,
10
+ writeFileSync,
11
+ } from 'node:fs';
12
+ import { promises as fs } from 'node:fs';
13
+ import path from 'node:path';
14
+ import process from 'node:process';
15
+ import { fileURLToPath, pathToFileURL } from 'node:url';
16
+ import { createRequire } from 'node:module';
17
+
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+ const require = createRequire(import.meta.url);
20
+
21
+ const CONFIG_FILE = 'knolo.config.json';
22
+ const DEFAULT_CONFIG = {
23
+ version: 1,
24
+ sources: [{ name: 'docs', path: './docs' }],
25
+ output: { path: './dist/knowledge.knolo' },
26
+ query: { topK: 5 },
27
+ };
28
+ const SUPPORTED_EXTENSIONS = new Set(['.md', '.txt', '.json']);
29
+ const SKIP_DIRS = new Set(['node_modules', 'dist', '.git']);
30
+ const SUBCOMMANDS = new Set(['init', 'add', 'build', 'query', 'dev']);
31
+
32
+ function createError(message) {
33
+ return new Error(message);
34
+ }
35
+
36
+ async function tryImport(filePath) {
37
+ try {
38
+ if (filePath.startsWith('.') || filePath.startsWith('/') || /^[A-Za-z]:[\\/]/.test(filePath)) {
39
+ return await import(pathToFileURL(filePath).href);
40
+ }
41
+ return await import(filePath);
42
+ } catch {}
43
+ try {
44
+ return require(filePath);
45
+ } catch {}
46
+ return null;
47
+ }
48
+
49
+ async function loadCore() {
50
+ const candidates = ['@knolo/core', path.resolve(__dirname, '../../core/dist/index.js')];
51
+ for (const candidate of candidates) {
52
+ const mod = await tryImport(candidate);
53
+ if (mod?.buildPack && mod?.mountPack && mod?.query) return mod;
54
+ if (mod?.default?.buildPack && mod?.default?.mountPack && mod?.default?.query) return mod.default;
55
+ }
56
+ throw createError('Could not load @knolo/core. Build packages/core first (npm run build --workspace @knolo/core).');
57
+ }
58
+
59
+ function printRootHelp() {
60
+ console.log(`KnoLo CLI
61
+
62
+ Usage:
63
+ knolo <command> [options]
64
+ knolo <input.json> [output.knolo] [--agents ./agents] [--embeddings embeddings.json --model-id model]
65
+
66
+ Commands:
67
+ init Initialize knolo.config.json and starter docs
68
+ add <name> <path> Add or update a source entry in config
69
+ build Build a .knolo pack from configured sources
70
+ query <question> Query a built pack and print top hits
71
+ dev Watch config/sources and rebuild on change
72
+
73
+ Global options:
74
+ --debug Print stack traces for errors
75
+ -h, --help Show help
76
+
77
+ Run "knolo <command> --help" for command details.`);
78
+ }
79
+
80
+ function printCommandHelp(command) {
81
+ const help = {
82
+ init: 'Usage: knolo init',
83
+ add: 'Usage: knolo add <name> <path>',
84
+ build: 'Usage: knolo build',
85
+ query: 'Usage: knolo query <question> [--pack <path>] [--k <number>] [--json]',
86
+ dev: 'Usage: knolo dev',
87
+ };
88
+ console.log(help[command] ?? 'Unknown command.');
89
+ }
90
+
91
+ function parseArgv(argv) {
92
+ const args = [...argv];
93
+ const global = { debug: false };
94
+ while (args[0] === '--debug') {
95
+ global.debug = true;
96
+ args.shift();
97
+ }
98
+ if (args[0] === '--help' || args[0] === '-h') return { global, command: 'help', commandArgs: [] };
99
+ return { global, command: args[0] || 'help', commandArgs: args.slice(1) };
100
+ }
101
+
102
+ function readConfig(configPath = path.resolve(process.cwd(), CONFIG_FILE)) {
103
+ if (!existsSync(configPath)) {
104
+ throw createError(`Missing config file at ${path.relative(process.cwd(), configPath)}. Run "knolo init" first.`);
105
+ }
106
+ let parsed;
107
+ try {
108
+ parsed = JSON.parse(readFileSync(configPath, 'utf8'));
109
+ } catch (error) {
110
+ throw createError(`Invalid JSON in ${CONFIG_FILE}: ${error.message}`);
111
+ }
112
+ validateConfig(parsed);
113
+ return parsed;
114
+ }
115
+
116
+ function validateConfig(config) {
117
+ if (!config || typeof config !== 'object') throw createError(`${CONFIG_FILE} must be a JSON object.`);
118
+ if (config.version !== 1) throw createError(`${CONFIG_FILE} must include "version": 1.`);
119
+ if (!Array.isArray(config.sources)) throw createError(`${CONFIG_FILE} must include a "sources" array.`);
120
+ for (const [i, s] of config.sources.entries()) {
121
+ if (!s?.name || typeof s.name !== 'string') throw createError(`sources[${i}].name must be a string.`);
122
+ if (!s?.path || typeof s.path !== 'string') throw createError(`sources[${i}].path must be a string.`);
123
+ }
124
+ if (!config.output?.path || typeof config.output.path !== 'string') {
125
+ throw createError(`${CONFIG_FILE} must include output.path.`);
126
+ }
127
+ }
128
+
129
+ function writeConfig(config) {
130
+ writeFileSync(path.resolve(process.cwd(), CONFIG_FILE), `${JSON.stringify(config, null, 2)}\n`);
131
+ }
132
+
133
+ async function cmdInit() {
134
+ const cwd = process.cwd();
135
+ const configPath = path.join(cwd, CONFIG_FILE);
136
+ const docsDir = path.join(cwd, 'docs');
137
+ const samplePath = path.join(docsDir, 'hello.md');
138
+
139
+ if (!existsSync(configPath)) {
140
+ writeFileSync(configPath, `${JSON.stringify(DEFAULT_CONFIG, null, 2)}\n`);
141
+ console.log(`✔ created ${CONFIG_FILE}`);
142
+ } else {
143
+ console.log(`• kept existing ${CONFIG_FILE}`);
144
+ }
145
+
146
+ if (!existsSync(docsDir)) {
147
+ mkdirSync(docsDir, { recursive: true });
148
+ console.log('✔ created docs/');
149
+ } else {
150
+ console.log('• kept existing docs/');
151
+ }
152
+
153
+ const visibleEntries = readdirSync(docsDir).filter((name) => !name.startsWith('.'));
154
+ if (visibleEntries.length === 0 && !existsSync(samplePath)) {
155
+ writeFileSync(samplePath, '# Hello from KnoLo\n\nThis is a starter document for KnoLo CLI demos.\n', 'utf8');
156
+ console.log('✔ created docs/hello.md');
157
+ } else {
158
+ console.log('• did not create sample doc because docs/ is not empty');
159
+ }
160
+ }
161
+
162
+ async function cmdAdd(args) {
163
+ const [name, sourcePath] = args;
164
+ if (!name || !sourcePath) throw createError('Usage: knolo add <name> <path>');
165
+
166
+ const resolved = path.resolve(process.cwd(), sourcePath);
167
+ if (!existsSync(resolved)) throw createError(`Source path does not exist: ${sourcePath}`);
168
+
169
+ const config = readConfig();
170
+ const normalized = sourcePath.startsWith('.') || path.isAbsolute(sourcePath) ? sourcePath : `./${sourcePath}`;
171
+ const existing = config.sources.find((s) => s.name === name);
172
+ if (existing) {
173
+ existing.path = normalized;
174
+ console.log(`✔ updated source "${name}" -> ${normalized}`);
175
+ } else {
176
+ config.sources.push({ name, path: normalized });
177
+ console.log(`✔ added source "${name}" -> ${normalized}`);
178
+ }
179
+ writeConfig(config);
180
+ console.log(`✔ wrote ${CONFIG_FILE}`);
181
+ }
182
+
183
+ async function walkDir(dir) {
184
+ const out = [];
185
+ const entries = await fs.readdir(dir, { withFileTypes: true });
186
+ entries.sort((a, b) => a.name.localeCompare(b.name));
187
+
188
+ for (const entry of entries) {
189
+ const full = path.join(dir, entry.name);
190
+ if (entry.isSymbolicLink()) continue;
191
+ if (entry.isDirectory()) {
192
+ if (SKIP_DIRS.has(entry.name)) continue;
193
+ out.push(...(await walkDir(full)));
194
+ } else if (entry.isFile() && SUPPORTED_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
195
+ out.push(full);
196
+ }
197
+ }
198
+ return out;
199
+ }
200
+
201
+ async function collectSourceFiles(sources) {
202
+ const files = [];
203
+ for (const source of sources) {
204
+ const resolved = path.resolve(process.cwd(), source.path);
205
+ if (!existsSync(resolved)) throw createError(`Configured source "${source.name}" not found at ${source.path}.`);
206
+ const stats = await fs.lstat(resolved);
207
+ if (stats.isSymbolicLink()) continue;
208
+ if (stats.isFile()) {
209
+ if (SUPPORTED_EXTENSIONS.has(path.extname(resolved).toLowerCase())) {
210
+ files.push({ sourceName: source.name, absolutePath: resolved });
211
+ }
212
+ } else if (stats.isDirectory()) {
213
+ for (const file of await walkDir(resolved)) files.push({ sourceName: source.name, absolutePath: file });
214
+ } else {
215
+ throw createError(`Configured source "${source.name}" must be a file or directory.`);
216
+ }
217
+ }
218
+ files.sort((a, b) => a.absolutePath.localeCompare(b.absolutePath));
219
+ return files;
220
+ }
221
+
222
+ async function buildFromConfig(core, { silent = false } = {}) {
223
+ const start = Date.now();
224
+ const config = readConfig();
225
+ const files = await collectSourceFiles(config.sources);
226
+ if (files.length === 0) throw createError('No supported files found (.md, .txt, .json).');
227
+
228
+ const docs = files.map((f) => {
229
+ const rel = path.relative(process.cwd(), f.absolutePath).replace(/\\/g, '/');
230
+ return {
231
+ id: rel,
232
+ heading: path.basename(rel),
233
+ namespace: f.sourceName,
234
+ text: readFileSync(f.absolutePath, 'utf8'),
235
+ };
236
+ });
237
+
238
+ const bytes = await core.buildPack(docs);
239
+ const outPath = path.resolve(process.cwd(), config.output.path);
240
+ mkdirSync(path.dirname(outPath), { recursive: true });
241
+ writeFileSync(outPath, Buffer.from(bytes));
242
+
243
+ if (!silent) {
244
+ console.log(`✔ indexed ${files.length} files`);
245
+ console.log(`✔ wrote ${path.relative(process.cwd(), outPath)}`);
246
+ console.log(`✔ build completed in ${Date.now() - start}ms`);
247
+ }
248
+ return { config, outPath };
249
+ }
250
+
251
+ async function cmdBuild(core) {
252
+ await buildFromConfig(core);
253
+ }
254
+
255
+ function parseQueryArgs(args) {
256
+ const opts = { json: false };
257
+ const positional = [];
258
+ for (let i = 0; i < args.length; i++) {
259
+ const arg = args[i];
260
+ if (arg === '--json') opts.json = true;
261
+ else if (arg === '--pack') opts.pack = args[++i];
262
+ else if (arg === '--k') opts.k = args[++i];
263
+ else if (arg.startsWith('--')) throw createError(`Unknown flag for query: ${arg}`);
264
+ else positional.push(arg);
265
+ }
266
+ return { positional, opts };
267
+ }
268
+
269
+ async function cmdQuery(core, args) {
270
+ const { positional, opts } = parseQueryArgs(args);
271
+ const question = positional.join(' ').trim();
272
+ if (!question) throw createError('Usage: knolo query <question> [--pack <path>] [--k <number>] [--json]');
273
+
274
+ const config = existsSync(path.resolve(process.cwd(), CONFIG_FILE)) ? readConfig() : DEFAULT_CONFIG;
275
+ const packPath = path.resolve(process.cwd(), opts.pack || config.output.path);
276
+ if (!existsSync(packPath)) throw createError(`Pack file not found at ${path.relative(process.cwd(), packPath)}.`);
277
+
278
+ const topK = opts.k !== undefined ? Number(opts.k) : (config.query?.topK ?? 5);
279
+ if (!Number.isInteger(topK) || topK <= 0) throw createError('--k must be a positive integer.');
280
+
281
+ const kb = await core.mountPack({ src: packPath });
282
+ const hits = core.query(kb, question, { topK }).map((hit) => ({
283
+ title: kb.headings?.[hit.blockId] || hit.source || `Block ${hit.blockId}`,
284
+ source: hit.source || kb.docIds?.[hit.blockId] || 'unknown',
285
+ score: Number(hit.score.toFixed(4)),
286
+ snippet: hit.text.replace(/\s+/g, ' ').slice(0, 200),
287
+ }));
288
+
289
+ if (opts.json) {
290
+ const patch = core.makeContextPatch(
291
+ hits.map((h, i) => ({ blockId: i, score: h.score, text: h.snippet, source: h.source })),
292
+ { budget: 'small' }
293
+ );
294
+ console.log(JSON.stringify({ question, packPath, topK, hits, contextPatch: patch }, null, 2));
295
+ return;
296
+ }
297
+
298
+ if (!hits.length) {
299
+ console.log('No hits found.');
300
+ return;
301
+ }
302
+ console.log(`Top ${hits.length} hit(s):`);
303
+ hits.forEach((hit, i) => {
304
+ console.log(`\n${i + 1}. ${hit.title}`);
305
+ console.log(` source: ${hit.source}`);
306
+ console.log(` score: ${hit.score}`);
307
+ console.log(` snippet: ${hit.snippet}`);
308
+ });
309
+ }
310
+
311
+ function listDirectoriesRecursively(root) {
312
+ const dirs = [root];
313
+ for (let i = 0; i < dirs.length; i++) {
314
+ let entries = [];
315
+ try {
316
+ entries = readdirSync(dirs[i], { withFileTypes: true });
317
+ } catch {
318
+ continue;
319
+ }
320
+ entries.sort((a, b) => a.name.localeCompare(b.name));
321
+ for (const entry of entries) {
322
+ if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
323
+ if (SKIP_DIRS.has(entry.name)) continue;
324
+ dirs.push(path.join(dirs[i], entry.name));
325
+ }
326
+ }
327
+ return dirs;
328
+ }
329
+
330
+ async function cmdDev(core) {
331
+ await buildFromConfig(core);
332
+ console.log('Watching for changes... (Ctrl+C to stop)');
333
+
334
+ let timer;
335
+ const schedule = () => {
336
+ clearTimeout(timer);
337
+ timer = setTimeout(async () => {
338
+ try {
339
+ console.log('\n↻ change detected, rebuilding...');
340
+ await buildFromConfig(core);
341
+ } catch (error) {
342
+ console.error(`✖ rebuild failed: ${error.message}`);
343
+ }
344
+ }, 250);
345
+ };
346
+
347
+ const closers = [];
348
+ const configWatcher = watch(path.resolve(process.cwd(), CONFIG_FILE), schedule);
349
+ closers.push(() => configWatcher.close());
350
+
351
+ const config = readConfig();
352
+ for (const source of config.sources) {
353
+ const resolved = path.resolve(process.cwd(), source.path);
354
+ if (!existsSync(resolved)) continue;
355
+ const stats = statSync(resolved);
356
+ if (stats.isFile()) {
357
+ const w = watch(resolved, schedule);
358
+ closers.push(() => w.close());
359
+ } else if (stats.isDirectory()) {
360
+ for (const dir of listDirectoriesRecursively(resolved)) {
361
+ const w = watch(dir, schedule);
362
+ closers.push(() => w.close());
363
+ }
364
+ }
365
+ }
366
+
367
+ process.on('SIGINT', () => {
368
+ for (const close of closers) close();
369
+ process.exit(0);
370
+ });
371
+
372
+ await new Promise(() => {});
373
+ }
374
+
375
+ function parseScalar(value) {
376
+ if (value === 'true') return true;
377
+ if (value === 'false') return false;
378
+ if (value === 'null') return null;
379
+ if (/^-?\d+(?:\.\d+)?$/.test(value)) return Number(value);
380
+ return value;
381
+ }
382
+
383
+ function parseSimpleYaml(content) {
384
+ const lines = content
385
+ .split(/\n/)
386
+ .map((line) => line.replace(/\r$/, ''))
387
+ .map((line) => line.replace(/\t/g, ' ').replace(/\s+#.*$/, ''));
388
+
389
+ const root = {};
390
+ const stack = [{ indent: -1, value: root }];
391
+
392
+ const nextMeaningfulLine = (from) => {
393
+ for (let i = from + 1; i < lines.length; i++) {
394
+ if (lines[i].trim()) return lines[i].trim();
395
+ }
396
+ return '';
397
+ };
398
+
399
+ for (let i = 0; i < lines.length; i++) {
400
+ const raw = lines[i];
401
+ if (!raw.trim()) continue;
402
+
403
+ const indent = raw.match(/^\s*/)[0].length;
404
+ const line = raw.trim();
405
+
406
+ while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
407
+ stack.pop();
408
+ }
409
+
410
+ const parent = stack[stack.length - 1].value;
411
+
412
+ if (line.startsWith('- ')) {
413
+ if (!Array.isArray(parent)) {
414
+ throw createError('YAML array item found under non-array parent.');
415
+ }
416
+
417
+ const body = line.slice(2).trim();
418
+ const pair = body.match(/^([^:]+):(.*)$/);
419
+ if (!pair) {
420
+ parent.push(parseScalar(body));
421
+ continue;
422
+ }
423
+
424
+ const obj = {};
425
+ const key = pair[1].trim();
426
+ const rhs = pair[2].trim();
427
+ if (rhs) {
428
+ obj[key] = parseScalar(rhs);
429
+ } else {
430
+ const next = nextMeaningfulLine(i);
431
+ obj[key] = next.startsWith('- ') ? [] : {};
432
+ stack.push({ indent, value: obj[key] });
433
+ }
434
+ parent.push(obj);
435
+ continue;
436
+ }
437
+
438
+ const pair = line.match(/^([^:]+):(.*)$/);
439
+ if (!pair) {
440
+ throw createError(`Unsupported YAML line: ${line}`);
441
+ }
442
+
443
+ const key = pair[1].trim();
444
+ const rhs = pair[2].trim();
445
+ if (Array.isArray(parent)) {
446
+ const obj = {};
447
+ parent.push(obj);
448
+ if (rhs) {
449
+ obj[key] = parseScalar(rhs);
450
+ } else {
451
+ const next = nextMeaningfulLine(i);
452
+ obj[key] = next.startsWith('- ') ? [] : {};
453
+ stack.push({ indent, value: obj[key] });
454
+ }
455
+ continue;
456
+ }
457
+
458
+ if (rhs) {
459
+ parent[key] = parseScalar(rhs);
460
+ } else {
461
+ const next = nextMeaningfulLine(i);
462
+ parent[key] = next.startsWith('- ') ? [] : {};
463
+ stack.push({ indent, value: parent[key] });
464
+ }
465
+ }
466
+
467
+ return root;
468
+ }
469
+
470
+ function parseAgentFileContent(content, filePath) {
471
+ const ext = path.extname(filePath).toLowerCase();
472
+ if (ext === '.json') return JSON.parse(content);
473
+ if (ext === '.yaml' || ext === '.yml') return parseSimpleYaml(content);
474
+ throw createError(`Unsupported agent file extension: ${filePath}`);
475
+ }
476
+
477
+ function loadAgentsFromDir(agentsDir, core) {
478
+ const entries = readdirSync(path.resolve(agentsDir), { withFileTypes: true })
479
+ .filter((e) => e.isFile())
480
+ .map((e) => e.name)
481
+ .filter((name) => ['.json', '.yaml', '.yml'].includes(path.extname(name).toLowerCase()))
482
+ .sort((a, b) => a.localeCompare(b));
483
+
484
+ const loaded = entries.map((file) => {
485
+ const full = path.join(path.resolve(agentsDir), file);
486
+ const parsed = parseAgentFileContent(readFileSync(full, 'utf8'), full);
487
+ const agent = parsed?.agent && typeof parsed.agent === 'object' ? parsed.agent : parsed;
488
+ if (typeof core.validateAgentDefinition === 'function') core.validateAgentDefinition(agent);
489
+ return { file, agent };
490
+ });
491
+
492
+ const dupes = new Map();
493
+ for (const item of loaded) {
494
+ const id = String(item.agent?.id ?? '');
495
+ if (!dupes.has(id)) dupes.set(id, []);
496
+ dupes.get(id).push(item.file);
497
+ }
498
+ for (const [id, files] of dupes.entries()) {
499
+ if (files.length > 1) {
500
+ throw createError(`Duplicate agent id "${id}" found in files: ${files.sort((a, b) => a.localeCompare(b)).join(', ')}`);
501
+ }
502
+ }
503
+
504
+ const agents = loaded.map((x) => x.agent).sort((a, b) => String(a.id).localeCompare(String(b.id)));
505
+ const registry = { version: 1, agents };
506
+ if (typeof core.validateAgentRegistry === 'function') core.validateAgentRegistry(registry);
507
+ return registry;
508
+ }
509
+
510
+ function loadEmbeddingsFromJson(filePath, expectedCount) {
511
+ const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
512
+ const vectors = Array.isArray(parsed?.embeddings) ? parsed.embeddings : parsed;
513
+ if (!Array.isArray(vectors)) throw createError('Embeddings JSON must be an array or { embeddings: [...] }.');
514
+ if (vectors.length !== expectedCount) throw createError(`Embeddings length mismatch: expected ${expectedCount}, got ${vectors.length}.`);
515
+ return vectors.map((entry, i) => {
516
+ if (!Array.isArray(entry)) throw createError(`Embeddings[${i}] must be an array.`);
517
+ const vec = new Float32Array(entry.length);
518
+ for (let d = 0; d < entry.length; d++) {
519
+ if (!Number.isFinite(entry[d])) throw createError(`Embeddings[${i}][${d}] must be numeric.`);
520
+ vec[d] = entry[d];
521
+ }
522
+ return vec;
523
+ });
524
+ }
525
+
526
+ function parseDirectModeArgs(args) {
527
+ const positional = [];
528
+ const flags = {};
529
+ for (let i = 0; i < args.length; i++) {
530
+ const arg = args[i];
531
+ if (arg === '--embeddings') flags.embeddingsPath = args[++i];
532
+ else if (arg === '--model-id') flags.modelId = args[++i];
533
+ else if (arg === '--agents') flags.agentsDir = args[++i];
534
+ else if (arg === '--help' || arg === '-h') flags.help = true;
535
+ else if (arg.startsWith('--')) throw createError(`Unknown flag: ${arg}`);
536
+ else positional.push(arg);
537
+ }
538
+ return { positional, flags };
539
+ }
540
+
541
+ async function runDirectMode(core, firstArg, restArgs) {
542
+ const { positional, flags } = parseDirectModeArgs([firstArg, ...restArgs]);
543
+ if (flags.help) {
544
+ console.log('Usage: knolo <input.json> [output.knolo] [--agents ./agents] [--embeddings embeddings.json --model-id model]');
545
+ return;
546
+ }
547
+
548
+ const inFile = positional[0];
549
+ const outFile = positional[1] || 'knowledge.knolo';
550
+ if (!inFile) throw createError('Usage: knolo <input.json> [output.knolo]');
551
+
552
+ const docs = JSON.parse(readFileSync(path.resolve(process.cwd(), inFile), 'utf8'));
553
+ if (!Array.isArray(docs)) throw createError('Input JSON must be an array of docs.');
554
+
555
+ const options = {};
556
+ if (flags.embeddingsPath || flags.modelId) {
557
+ if (!flags.embeddingsPath || !flags.modelId) throw createError('Both --embeddings and --model-id are required together.');
558
+ options.semantic = {
559
+ enabled: true,
560
+ modelId: flags.modelId,
561
+ embeddings: loadEmbeddingsFromJson(flags.embeddingsPath, docs.length),
562
+ quantization: { type: 'int8_l2norm', perVectorScale: true },
563
+ };
564
+ }
565
+ if (flags.agentsDir) {
566
+ options.agents = loadAgentsFromDir(flags.agentsDir, core);
567
+ }
568
+
569
+ const bytes = await core.buildPack(docs, options);
570
+ writeFileSync(path.resolve(process.cwd(), outFile), Buffer.from(bytes));
571
+ console.log(`✔ wrote ${outFile}`);
572
+ }
573
+
574
+ async function main() {
575
+ const { global, command, commandArgs } = parseArgv(process.argv.slice(2));
576
+ try {
577
+ if (command === 'help') return printRootHelp();
578
+
579
+ const core = await loadCore();
580
+
581
+ if (SUBCOMMANDS.has(command)) {
582
+ if (commandArgs.includes('--help') || commandArgs.includes('-h')) return printCommandHelp(command);
583
+ if (command === 'init') return await cmdInit();
584
+ if (command === 'add') return await cmdAdd(commandArgs);
585
+ if (command === 'build') return await cmdBuild(core);
586
+ if (command === 'query') return await cmdQuery(core, commandArgs);
587
+ if (command === 'dev') return await cmdDev(core);
588
+ }
589
+
590
+ if (command.startsWith('-')) throw createError(`Unknown option: ${command}`);
591
+ return await runDirectMode(core, command, commandArgs);
592
+ } catch (error) {
593
+ console.error(`knolo: ${error instanceof Error ? error.message : String(error)}`);
594
+ if (global.debug && error instanceof Error && error.stack) console.error(error.stack);
595
+ process.exit(1);
596
+ }
597
+ }
598
+
599
+ await main();
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@knolo/cli",
3
+ "version": "3.1.2",
4
+ "type": "module",
5
+ "bin": {
6
+ "knolo": "bin/knolo.mjs"
7
+ },
8
+ "files": [
9
+ "bin/"
10
+ ],
11
+ "scripts": {
12
+ "build": "node -e \"console.log('cli is executable via bin/knolo.mjs')\"",
13
+ "test": "npm run build --prefix ../core && node --test test/cli.test.mjs"
14
+ },
15
+ "dependencies": {
16
+ "@knolo/core": "^3.1.2"
17
+ },
18
+ "private": false
19
+ }