@knolo/cli 5.5.0 → 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.
Files changed (3) hide show
  1. package/README.md +18 -2
  2. package/bin/knolo.mjs +184 -20
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @knolo/cli
2
2
 
3
- The CLI preserves the Knolo V4 pack workflow and adds read-only V5 Knowledge
4
- Image inspection. Normal builds emit V4; the ICP legacy build path explicitly
3
+ The CLI preserves the Knolo V4 pack workflow and adds explicit V5 Knowledge
4
+ Image inspection and querying. Normal builds emit V4; the ICP legacy build path explicitly
5
5
  emits V3 until the canister gains a V4 profile. V5 Studio output is a verified
6
6
  management snapshot, not a mutation interface.
7
7
 
@@ -134,6 +134,22 @@ knolo explain receipt.json --pack dist/knowledge.knolo
134
134
  knolo diff old.knolo new.knolo
135
135
  ```
136
136
 
137
+ `knolo build` produces a V4 pack, so its natural-language query and receipt
138
+ contract applies to that artifact. V5 images are verified Knowledge Images and
139
+ use bounded EQL with roots-bound query results:
140
+
141
+ ```bash
142
+ knolo query 'FROM chunk SEARCH "billing" LIMIT 5' \
143
+ --pack ./dist/knowledge.v5 --json --receipt ./query-result.json
144
+ knolo v5 query ./dist/knowledge.v5 \
145
+ 'FROM chunk SEARCH "billing" LIMIT 5' --json
146
+ knolo explain ./query-result.json --pack ./dist/knowledge.v5
147
+ ```
148
+
149
+ V5 images are never mounted through the V4 pack query API. Semantic sidecars
150
+ currently target V4 packs and are rejected for V5 images with a contract-specific
151
+ message.
152
+
137
153
  V5 runtime diagnostics:
138
154
 
139
155
  ```bash
package/bin/knolo.mjs CHANGED
@@ -146,7 +146,7 @@ Commands:
146
146
  publish Publish a pack with a Hub Bearer token
147
147
  yank <pack>@<version> Yank a released pack with a Hub Bearer token
148
148
  build Build a .knolo pack from configured sources
149
- 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
150
150
  inspect <pack> Inspect pack format, sections, and manifest
151
151
  migrate <pack> Migrate a legacy pack to v4
152
152
  verify <pack> Verify pack structure and cryptographic digests
@@ -179,7 +179,7 @@ function printCommandHelp(command) {
179
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.',
180
180
  build: 'Usage: knolo build',
181
181
  query:
182
- 'Usage: knolo query <question> [--pack <path>] [--k <number>] [--receipt <file>] [--json]',
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.',
183
183
  inspect: 'Usage: knolo inspect <pack.knolo>',
184
184
  migrate:
185
185
  'Usage: knolo migrate <pack.knolo> --out <new-pack.knolo> [--to 4]',
@@ -187,7 +187,7 @@ function printCommandHelp(command) {
187
187
  explain: 'Usage: knolo explain <receipt.json> --pack <pack.knolo>',
188
188
  diff: 'Usage: knolo diff <pack-a.knolo> <pack-b.knolo>',
189
189
  dev: 'Usage: knolo dev',
190
- v5: 'Usage: knolo v5 <info|health|studio|compress|decompress> <image.v5> [--out <file>] [--mode fast|balanced|max] [--attach-index]',
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>].',
191
191
  icp: 'Usage: knolo icp <command> [options]',
192
192
  'semantic:index':
193
193
  'Usage: knolo semantic:index --pack <path> [--out <path>] [--model <id>] [--endpoint <url>]',
@@ -877,6 +877,13 @@ async function cmdQuery(core, args) {
877
877
 
878
878
  const packBuffer = readFileSync(packPath);
879
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
+ }
880
887
  const kb = await mountPackFromBytes(core, bytes);
881
888
  const receiptResult =
882
889
  opts.receipt && core.queryWithReceipt
@@ -936,6 +943,66 @@ async function cmdQuery(core, args) {
936
943
  });
937
944
  }
938
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
+
939
1006
  function parsePackCommandArgs(args, command) {
940
1007
  const positional = [];
941
1008
  const flags = {};
@@ -961,6 +1028,24 @@ async function cmdInspect(core, args) {
961
1028
  `Pack file not found at ${path.relative(process.cwd(), packPath)}.`
962
1029
  );
963
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
+ }
964
1049
  const pack = await mountPackFromBytes(core, bytes);
965
1050
  const result = {
966
1051
  format:
@@ -988,6 +1073,23 @@ async function cmdVerify(core, args) {
988
1073
  `Pack file not found at ${path.relative(process.cwd(), packPath)}.`
989
1074
  );
990
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
+ }
991
1093
  const pack = await mountPackFromBytes(core, bytes);
992
1094
  console.log(
993
1095
  JSON.stringify(
@@ -1043,16 +1145,47 @@ async function cmdExplain(core, args) {
1043
1145
  const receiptPath = positional[0];
1044
1146
  if (!receiptPath || (!flags.out && !flags.pack))
1045
1147
  throw createError(
1046
- 'Usage: knolo explain <receipt.json> --pack <pack.knolo>'
1148
+ 'Usage: knolo explain <receipt.json> --pack <pack.knolo|image.v5>'
1047
1149
  );
1048
1150
  const packPath = path.resolve(process.cwd(), flags.pack);
1049
1151
  const receipt = JSON.parse(
1050
1152
  readFileSync(path.resolve(process.cwd(), receiptPath), 'utf8')
1051
1153
  );
1052
- const pack = await mountPackFromBytes(
1053
- core,
1054
- Uint8Array.from(readFileSync(packPath))
1055
- );
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);
1056
1189
  core.verifyReceipt(receipt, pack);
1057
1190
  console.log(
1058
1191
  JSON.stringify(
@@ -1120,6 +1253,8 @@ function parseV5Args(args) {
1120
1253
  for (let i = 0; i < args.length; i++) {
1121
1254
  const arg = args[i];
1122
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];
1123
1258
  else if (
1124
1259
  arg === '--image' ||
1125
1260
  arg === '--index' ||
@@ -1139,17 +1274,40 @@ function parseV5Args(args) {
1139
1274
 
1140
1275
  async function cmdV5(core, args) {
1141
1276
  const subcommand = args[0] || 'info';
1142
- if (
1143
- !['info', 'health', 'studio', 'compress', 'decompress'].includes(subcommand)
1144
- )
1277
+ const subcommands = [
1278
+ 'info',
1279
+ 'health',
1280
+ 'studio',
1281
+ 'query',
1282
+ 'compress',
1283
+ 'decompress',
1284
+ ];
1285
+ if (!subcommands.includes(subcommand))
1145
1286
  throw createError(`Unknown V5 command: ${subcommand}`);
1146
1287
  const { positional, flags } = parseV5Args(
1147
- args.slice(
1148
- ['info', 'health', 'studio', 'compress', 'decompress'].includes(args[0])
1149
- ? 1
1150
- : 0
1151
- )
1288
+ args.slice(subcommands.includes(args[0]) ? 1 : 0)
1152
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
+ }
1153
1311
  const imagePath = path.resolve(
1154
1312
  process.cwd(),
1155
1313
  flags.image || positional[0] || 'dist/knowledge.v5'
@@ -1282,6 +1440,10 @@ async function cmdSemanticIndex(core, args) {
1282
1440
  );
1283
1441
 
1284
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
+ );
1285
1447
  const pack = await mountPackFromBytes(core, bytes);
1286
1448
  const OllamaEmbeddingProvider = await loadOllamaProvider();
1287
1449
  const provider = new OllamaEmbeddingProvider({ modelId, endpoint });
@@ -1332,10 +1494,12 @@ async function cmdSemanticValidate(core, args) {
1332
1494
  const sidecarPath = path.resolve(process.cwd(), flags.sidecar);
1333
1495
  const modelId = flags.model;
1334
1496
  if (!modelId) throw createError('semantic:validate requires --model <id>.');
1335
- const pack = await mountPackFromBytes(
1336
- core,
1337
- Uint8Array.from(readFileSync(packPath))
1338
- );
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);
1339
1503
  const sidecar = core.parseSidecar(readFileSync(sidecarPath, 'utf8'));
1340
1504
  core.validateSidecarForPack({ sidecar, pack, modelId });
1341
1505
  if (sidecar.blocks.length !== pack.blocks.length)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knolo/cli",
3
- "version": "5.5.0",
3
+ "version": "5.5.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "knolo": "bin/knolo.mjs"