@openbkn/bkn-sdk 0.1.3 → 0.1.5-rc.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/dist/cli.js CHANGED
@@ -1,10 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ BuildTaskExecuteType,
4
+ BuildTaskSort,
5
+ BuildTaskStatus,
3
6
  DEFAULT_BUSINESS_DOMAIN,
4
7
  DEFAULT_LIST_LIMIT,
5
8
  DEFAULT_QUERY_LIMIT,
9
+ DiscoverScheduleSort,
10
+ DiscoverStrategy,
11
+ DiscoverTaskSort,
12
+ DiscoverTaskTriggerType,
13
+ DryRunSignal,
6
14
  HttpError,
7
15
  InputError,
16
+ SemanticUnderstandingApplyMode,
17
+ SemanticUnderstandingScope,
18
+ SemanticUnderstandingTaskSort,
19
+ SortDirection,
20
+ VegaTaskStatus,
8
21
  activePlatform,
9
22
  attachToken,
10
23
  changePasswordSafe,
@@ -16,6 +29,7 @@ import {
16
29
  decodeJwt,
17
30
  deletePlatform,
18
31
  deviceLogin,
32
+ enableDryRun,
19
33
  exportCreds,
20
34
  filesUnder,
21
35
  formatError,
@@ -25,6 +39,7 @@ import {
25
39
  listPlatforms,
26
40
  logout,
27
41
  openBrowser,
42
+ parseBigIntJSON,
28
43
  parseEmbeddingFields,
29
44
  parsePkMap,
30
45
  rawCall,
@@ -35,21 +50,23 @@ import {
35
50
  resolveContext,
36
51
  setActivePlatform,
37
52
  status,
53
+ stringifyBigIntJSON,
38
54
  switchUser,
39
55
  toExitCode,
56
+ trimTrailingSlashes,
57
+ updatePlatformConfig,
40
58
  use,
41
59
  validateFixturePath,
42
- whoami,
43
- writePlatformConfig
44
- } from "./chunk-PC2F54XD.js";
60
+ whoami
61
+ } from "./chunk-WIYRGBAB.js";
45
62
 
46
- // src/cli.ts
47
- import { Command as Command17 } from "commander";
63
+ // src/cli-program.ts
64
+ import { Command as Command16 } from "commander";
48
65
 
49
66
  // package.json
50
67
  var package_default = {
51
68
  name: "@openbkn/bkn-sdk",
52
- version: "0.1.3",
69
+ version: "0.1.5-rc.1",
53
70
  description: "Unified TypeScript SDK + CLI for the BKN (Business Knowledge Network) platform.",
54
71
  type: "module",
55
72
  license: "Apache-2.0",
@@ -81,10 +98,14 @@ var package_default = {
81
98
  dev: "tsup --watch",
82
99
  typecheck: "tsc --noEmit",
83
100
  "check:deps": "node scripts/check-no-self-dep.mjs",
101
+ "capture:returns": "node scripts/capture-returns.mjs",
84
102
  lint: "npm run check:deps && biome check . && tsc --noEmit",
85
103
  format: "biome format --write .",
86
104
  test: "vitest run",
87
105
  "test:e2e:trace-business": "npm run build && node test/e2e/bkn-trace-business-interaction.mjs",
106
+ "test:e2e:smoke": "npm run build && test/e2e/live-smoke.sh",
107
+ "test:e2e:suite": "npm run build && test/e2e/live-suite.sh",
108
+ "test:e2e:write": "npm run build && test/e2e/live-write.sh",
88
109
  "test:cover": "vitest run --coverage",
89
110
  ci: "npm run lint && npm test",
90
111
  prepublishOnly: "npm run ci && npm run build"
@@ -102,7 +123,7 @@ var package_default = {
102
123
  devDependencies: {
103
124
  "@biomejs/biome": "^1.9.4",
104
125
  "@types/js-yaml": "^4.0.9",
105
- "@types/node": "^22.13.0",
126
+ "@types/node": "^24.0.0",
106
127
  tsup: "^8.4.0",
107
128
  typescript: "^5.8.0",
108
129
  vitest: "^3.0.0"
@@ -115,14 +136,72 @@ import { Command as Command2 } from "commander";
115
136
 
116
137
  // src/help/grouped-help.ts
117
138
  var GROUP = /* @__PURE__ */ Symbol("openbkn.group");
139
+ var GUIDE = /* @__PURE__ */ Symbol("openbkn.guide");
118
140
  var DEFAULT_GROUP = "COMMANDS";
119
141
  function group(cmd, name) {
120
142
  cmd[GROUP] = name;
121
143
  return cmd;
122
144
  }
145
+ function guide(cmd, text) {
146
+ cmd[GUIDE] = text;
147
+ return cmd;
148
+ }
149
+ var SECTION_ORDER = ["GROUPS", "READ", "RUN", "WRITE"];
150
+ function groupChildren(parent, sections) {
151
+ for (const [section, names] of Object.entries(sections)) {
152
+ for (const name of names) {
153
+ const child = parent.commands.find((c) => c.name() === name);
154
+ if (child) group(child, section);
155
+ }
156
+ }
157
+ }
158
+ var VERB_SECTIONS = [
159
+ [
160
+ /^(list|get|show|find|files|history|members|roles|tree|names|content|read-file|status|whoami|users|detail|spans|graph|health|resources|search|market|market-get|stats|export|pull|validate-fixture)$/,
161
+ "READ"
162
+ ],
163
+ [
164
+ /^(query|execute|debug|run|test|chat|embeddings|rerank|diagnose|scan|discover|build|dry-run|validate|token|download|install|call|sql|receipt|fingerprint|test-connection|test-connection-config|attempt|retry|start|resume|ensure-current|create-new-generation|close|complete|fail|cancel|handoff|operations|build-status|build-list)$/,
165
+ "RUN"
166
+ ],
167
+ [
168
+ /^(create|update|delete|add|edit|remove|set|register|upload|publish|unpublish|republish|import|activate|login|logout|use|switch|change-password|enable|disable|push|assign-role|revoke-role|add-member|remove-member|reset-password|grant-perm|revoke-perm|add-members|remove-members|set-status|regenerate|revoke|set-bd|list-bd|build-start|build-stop|build-delete|publish-history|update-metadata|update-package|create-from-catalog|export-config)$/,
169
+ "WRITE"
170
+ ]
171
+ ];
172
+ function autoGroup(parent) {
173
+ for (const child of parent.commands) {
174
+ if (child.name().startsWith("help")) continue;
175
+ if (child[GROUP] !== void 0) continue;
176
+ if (child.commands.filter((c) => !c.name().startsWith("help")).length > 0) {
177
+ group(child, "GROUPS");
178
+ continue;
179
+ }
180
+ const verb = VERB_SECTIONS.find(([re]) => re.test(child.name()));
181
+ if (verb) group(child, verb[1]);
182
+ }
183
+ }
184
+ function rankOf(name) {
185
+ if (name === DEFAULT_GROUP) return Number.MAX_SAFE_INTEGER;
186
+ const i = SECTION_ORDER.indexOf(name);
187
+ return i === -1 ? SECTION_ORDER.length : i;
188
+ }
123
189
  function groupOf(cmd) {
124
190
  return cmd[GROUP] ?? DEFAULT_GROUP;
125
191
  }
192
+ var SECTION_MEANINGS = {
193
+ GROUPS: "nested command groups \u2014 one level deeper",
194
+ READ: "changes nothing",
195
+ RUN: "acts without changing configuration (triggers a job, spends a model call, rotates a token)",
196
+ WRITE: "changes platform state \u2014 confirm with a person first",
197
+ [DEFAULT_GROUP]: "not sorted into a section yet"
198
+ };
199
+ function sectionOf(cmd) {
200
+ return groupOf(cmd);
201
+ }
202
+ function guideOf(cmd) {
203
+ return cmd[GUIDE];
204
+ }
126
205
  function formatHelp(cmd, helper) {
127
206
  const out = [];
128
207
  const desc = helper.commandDescription(cmd);
@@ -138,7 +217,8 @@ function formatHelp(cmd, helper) {
138
217
  if (bucket) bucket.push(c);
139
218
  else sections.set(g, [c]);
140
219
  }
141
- for (const [name, cmds] of sections) {
220
+ const ordered = [...sections.entries()].map((entry, index) => ({ entry, index })).sort((a, b) => rankOf(a.entry[0]) - rankOf(b.entry[0]) || a.index - b.index).map(({ entry }) => entry);
221
+ for (const [name, cmds] of ordered) {
142
222
  out.push(name);
143
223
  for (const c of cmds) {
144
224
  out.push(` ${helper.subcommandTerm(c).padEnd(width)} ${helper.subcommandDescription(c)}`);
@@ -146,6 +226,8 @@ function formatHelp(cmd, helper) {
146
226
  out.push("");
147
227
  }
148
228
  }
229
+ const extra = guideOf(cmd);
230
+ if (extra) out.push(extra.trim(), "");
149
231
  const opts = helper.visibleOptions(cmd);
150
232
  if (opts.length > 0) {
151
233
  const width = Math.max(...opts.map((o) => helper.optionTerm(o).length));
@@ -158,11 +240,15 @@ function formatHelp(cmd, helper) {
158
240
  return out.join("\n");
159
241
  }
160
242
  function installGroupedHelp(root) {
161
- const apply = (cmd) => {
243
+ const apply = (cmd, path) => {
162
244
  cmd.configureHelp({ formatHelp });
163
- for (const child of cmd.commands) apply(child);
245
+ if (cmd !== root) autoGroup(cmd);
246
+ cmd.showHelpAfterError(
247
+ path.length ? `Run \`openbkn describe ${path.join(" ")}\` to see its arguments and where their ids come from.` : "Run `openbkn describe --depth 1` for every command, or `openbkn --help` for the guide."
248
+ );
249
+ for (const child of cmd.commands) apply(child, [...path, child.name()]);
164
250
  };
165
- apply(root);
251
+ apply(root, []);
166
252
  }
167
253
 
168
254
  // src/utils/org-tree.ts
@@ -181,7 +267,7 @@ function renderOrgTree(nodes, prefix = "") {
181
267
  // src/utils/output.ts
182
268
  function printJson(value, opts = {}) {
183
269
  if (opts.json || opts.compact) {
184
- const json = JSON.stringify(value === void 0 ? null : value, null, opts.compact ? 0 : 2);
270
+ const json = stringifyBigIntJSON(value === void 0 ? null : value, opts.compact ? 0 : 2);
185
271
  process.stdout.write(`${json}
186
272
  `);
187
273
  return;
@@ -190,12 +276,12 @@ function printJson(value, opts = {}) {
190
276
  process.stdout.write("(ok)\n");
191
277
  return;
192
278
  }
193
- const rows = toRows(value);
194
- if (rows) {
195
- const fullColumns = columnsOf(rows).filter((c) => rows.some((r) => stringifyCell(r[c]) !== ""));
196
- const columns = opts.full ? fullColumns : selectColumns(rows);
279
+ const rows2 = toRows(value);
280
+ if (rows2) {
281
+ const fullColumns = columnsOf(rows2).filter((c) => rows2.some((r) => stringifyCell(r[c]) !== ""));
282
+ const columns = opts.full ? fullColumns : selectColumns(rows2);
197
283
  if (columns.length > 0) {
198
- printTable(rows, columns);
284
+ printTable(rows2, columns);
199
285
  const hidden = fullColumns.length - columns.length;
200
286
  if (hidden > 0 && !opts.full) {
201
287
  process.stdout.write(`\u2026 ${hidden} more column(s); use --full or --json for everything
@@ -208,7 +294,7 @@ function printJson(value, opts = {}) {
208
294
  process.stdout.write("(no results)\n");
209
295
  return;
210
296
  }
211
- process.stdout.write(`${JSON.stringify(value, null, 2)}
297
+ process.stdout.write(`${stringifyBigIntJSON(value, 2)}
212
298
  `);
213
299
  }
214
300
  function isEmptyEnvelope(value) {
@@ -241,9 +327,9 @@ function toRows(value) {
241
327
  }
242
328
  return null;
243
329
  }
244
- function columnsOf(rows) {
330
+ function columnsOf(rows2) {
245
331
  const seen = [];
246
- for (const row of rows) {
332
+ for (const row of rows2) {
247
333
  for (const k of Object.keys(row)) if (!seen.includes(k)) seen.push(k);
248
334
  }
249
335
  return seen;
@@ -264,16 +350,16 @@ var NOISE_COLS = /* @__PURE__ */ new Set([
264
350
  ]);
265
351
  var isNoiseCol = (c) => NOISE_COLS.has(c) || /_time$/.test(c);
266
352
  var isKeyCol = (c) => /^(id|name|key|title|label)$/i.test(c) || /_(id|name|key)$/i.test(c) || /^(status|state|type|category|mode|enabled|version|branch)$/i.test(c);
267
- function selectColumns(rows) {
353
+ function selectColumns(rows2) {
268
354
  const isObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
269
- const kept = columnsOf(rows).filter((c) => {
355
+ const kept = columnsOf(rows2).filter((c) => {
270
356
  if (isNoiseCol(c)) return false;
271
- const vals = rows.map((r) => r[c]);
357
+ const vals = rows2.map((r) => r[c]);
272
358
  if (!vals.some((v) => stringifyCell(v) !== "")) return false;
273
359
  if (vals.every((v) => v === null || v === void 0 || isObj(v))) return false;
274
360
  return true;
275
361
  });
276
- const isLongText = (c) => rows.every((r) => {
362
+ const isLongText = (c) => rows2.every((r) => {
277
363
  const s = stringifyCell(r[c]);
278
364
  return s === "" || s.length >= CELL_MAX - 1;
279
365
  });
@@ -281,12 +367,12 @@ function selectColumns(rows) {
281
367
  const ordered = kept.map((c, i) => ({ c, i, r: rank(c) })).sort((a, b) => a.r - b.r || a.i - b.i).map((x) => x.c);
282
368
  return ordered.slice(0, MAX_COLS);
283
369
  }
284
- function printTable(rows, columns, opts = {}) {
370
+ function printTable(rows2, columns, opts = {}) {
285
371
  if (opts.json || opts.compact) {
286
- printJson(rows, opts);
372
+ printJson(rows2, opts);
287
373
  return;
288
374
  }
289
- const cells = rows.map((row) => columns.map((c) => stringifyCell(row[c])));
375
+ const cells = rows2.map((row) => columns.map((c) => stringifyCell(row[c])));
290
376
  const widths = columns.map(
291
377
  (col, i) => Math.max(displayWidth(col), ...cells.map((r) => displayWidth(r[i] ?? "")))
292
378
  );
@@ -307,7 +393,7 @@ function pad(s, width) {
307
393
  }
308
394
  function stringifyCell(v) {
309
395
  if (v === null || v === void 0) return "";
310
- const raw = Array.isArray(v) && v.every((x) => x === null || typeof x !== "object") ? v.join(",") : typeof v === "object" ? JSON.stringify(v) : String(v);
396
+ const raw = Array.isArray(v) && v.every((x) => x === null || typeof x !== "object") ? v.join(",") : typeof v === "object" ? stringifyBigIntJSON(v) : String(v);
311
397
  const s = raw.replace(/\s+/g, " ").trim();
312
398
  return s.length > CELL_MAX ? `${s.slice(0, CELL_MAX - 1)}\u2026` : s;
313
399
  }
@@ -315,7 +401,7 @@ function stringifyCell(v) {
315
401
  // src/utils/prompt.ts
316
402
  import { createInterface } from "readline";
317
403
  function promptLine(query, hidden = false) {
318
- return new Promise((resolve2) => {
404
+ return new Promise((resolve3) => {
319
405
  const rl = createInterface({ input: process.stdin, output: process.stdout });
320
406
  if (hidden) {
321
407
  const mutable = rl;
@@ -326,15 +412,33 @@ function promptLine(query, hidden = false) {
326
412
  rl.question(query, (answer) => {
327
413
  rl.close();
328
414
  if (hidden) process.stdout.write("\n");
329
- resolve2(answer.trim());
415
+ resolve3(answer.trim());
330
416
  });
331
417
  });
332
418
  }
333
419
 
334
420
  // src/commands/_shared.ts
335
421
  import { readFileSync } from "fs";
422
+ function platformOf(o) {
423
+ const baseUrl = (typeof o.baseUrl === "string" ? o.baseUrl : void 0) ?? process.env.BKN_BASE_URL ?? activePlatform();
424
+ return baseUrl === void 0 ? void 0 : trimTrailingSlashes(baseUrl);
425
+ }
426
+ function transientIdentity(o) {
427
+ return Boolean(o.user || process.env.BKN_USER || o.token || process.env.BKN_TOKEN);
428
+ }
429
+ function conversationSource(o) {
430
+ const flag = typeof o.conversationId === "string" ? o.conversationId : void 0;
431
+ if (flag) return { id: flag, source: "flag" };
432
+ const env = process.env.BKN_CONVERSATION_ID;
433
+ if (env) return { id: env, source: "env" };
434
+ if (o.newConversation || transientIdentity(o)) return { source: "none" };
435
+ const baseUrl = platformOf(o);
436
+ const stored = baseUrl ? readPlatformConfig(baseUrl).conversationId : void 0;
437
+ return stored ? { id: stored, source: "stored" } : { source: "none" };
438
+ }
336
439
  function traceOptionsFrom(o) {
337
- const conversationId = (typeof o.conversationId === "string" ? o.conversationId : void 0) ?? process.env.BKN_CONVERSATION_ID;
440
+ const found = conversationSource(o);
441
+ const conversationId = found.source === "stored" ? void 0 : found.id;
338
442
  const interactionId = (typeof o.interactionId === "string" ? o.interactionId : void 0) ?? process.env.BKN_INTERACTION_ID;
339
443
  if (!conversationId && !interactionId) return void 0;
340
444
  return {
@@ -345,14 +449,36 @@ function traceOptionsFrom(o) {
345
449
  function clientFrom(cmd) {
346
450
  const o = cmd.optsWithGlobals();
347
451
  const trace = traceOptionsFrom(o);
348
- return createClient({
452
+ const storeBaseUrl = platformOf(o);
453
+ const remembered = conversationSource(o);
454
+ const client = createClient({
349
455
  baseUrl: o.baseUrl,
350
456
  token: o.token,
351
457
  user: o.user,
352
458
  businessDomain: o.bizDomain,
353
459
  insecure: o.insecure,
354
- ...trace ? { trace } : {}
460
+ ...trace ? { trace } : {},
461
+ ...remembered.source === "stored" && remembered.id ? { rememberedConversationId: remembered.id } : {},
462
+ // Remember a conversation this run opens, so the next command continues the
463
+ // same thread instead of starting a new one. Only for the active identity —
464
+ // `transientIdentity` explains which identities are left out.
465
+ // `--new-conversation` says "for this command", so it must not replace what
466
+ // it declined to use — otherwise one run with the flag would silently end
467
+ // the thread every other run was continuing.
468
+ ...transientIdentity(o) || o.newConversation ? {} : {
469
+ onConversationOpened: (conversationId) => {
470
+ if (!storeBaseUrl) return;
471
+ try {
472
+ updatePlatformConfig(storeBaseUrl, {
473
+ conversationId,
474
+ conversationOpenedAt: (/* @__PURE__ */ new Date()).toISOString()
475
+ });
476
+ } catch {
477
+ }
478
+ }
479
+ }
355
480
  });
481
+ return client;
356
482
  }
357
483
  function outputOptions(cmd) {
358
484
  const o = cmd.optsWithGlobals();
@@ -366,7 +492,7 @@ function readBody(opts) {
366
492
  const raw = opts.bodyFile ? readFileSync(opts.bodyFile, "utf8") : opts.body;
367
493
  if (!raw) throw new InputError("Provide --body '<json>' or --body-file <path>.");
368
494
  try {
369
- return JSON.parse(raw);
495
+ return parseBigIntJSON(raw);
370
496
  } catch {
371
497
  throw new InputError("Request body is not valid JSON.");
372
498
  }
@@ -520,16 +646,16 @@ User code: ${userCode}
520
646
  }
521
647
  const expMs = typeof me.exp === "number" ? me.exp * 1e3 : void 0;
522
648
  const expired = expMs !== void 0 && expMs < Date.now();
523
- const rows = [
649
+ const rows2 = [
524
650
  ["User", String(me.username ?? me.sub ?? "(unknown)")],
525
651
  ...me.name && me.name !== me.username ? [["Name", String(me.name)]] : [],
526
652
  ["ID", String(me.userId ?? me.sub ?? "-")],
527
653
  ["Platform", String(me.baseUrl ?? "-")],
528
654
  ...expMs !== void 0 ? [["Expires", `${new Date(expMs).toISOString()}${expired ? " (expired)" : ""}`]] : []
529
655
  ];
530
- const pad2 = Math.max(...rows.map(([k]) => k.length));
656
+ const pad2 = Math.max(...rows2.map(([k]) => k.length));
531
657
  process.stdout.write(
532
- `${rows.map(([k, v]) => `${k.padEnd(pad2)} ${v}`).join("\n")}
658
+ `${rows2.map(([k, v]) => `${k.padEnd(pad2)} ${v}`).join("\n")}
533
659
  \u2026 use --full or --json for all claims
534
660
  `
535
661
  );
@@ -557,7 +683,7 @@ User code: ${userCode}
557
683
  `);
558
684
  });
559
685
  cmd.command("users <url>").description("List saved users for a platform (* = active)").action((url, _opts, cmd2) => {
560
- const norm = url.replace(/\/+$/, "");
686
+ const norm = trimTrailingSlashes(url);
561
687
  const items = listPlatforms().filter((i) => i.baseUrl === norm);
562
688
  const out = outputOptions(cmd2);
563
689
  if (out.json || out.compact) printJson(items, out);
@@ -601,9 +727,33 @@ User code: ${userCode}
601
727
  });
602
728
  }
603
729
  function authCommand() {
604
- const cmd = new Command("auth").description("Login, session, and token management");
730
+ const cmd = new Command("auth").description("Log in; the token is saved and reused. Start here.");
605
731
  registerAuthLeaves(cmd);
606
- return group(cmd, "AUTHENTICATION & CONFIG");
732
+ groupChildren(cmd, {
733
+ READ: ["status", "whoami", "list", "users"],
734
+ RUN: ["token", "export"],
735
+ WRITE: ["login", "logout", "use", "switch", "delete", "change-password"]
736
+ });
737
+ guide(
738
+ cmd,
739
+ `WAYS IN
740
+ login <url> -u <user> -p <pass> password, no browser
741
+ login <url> --token <token> a token you already hold (CI)
742
+ login <url> --device device code, for a host with no browser
743
+ login <url> opens a browser; --no-browser prints the URL instead
744
+
745
+ MANY PLATFORMS, MANY USERS
746
+ A session is a platform plus a user. \`use <url>\` changes which platform is
747
+ active; \`switch <url> <user>\` changes which saved user is active on one.
748
+ \`list\` shows both, marking the active pair. \`--user\` on any command borrows a
749
+ saved user for that call alone.
750
+
751
+ TOKENS
752
+ \`token\` prints the access token, refreshing it first if it has expired;
753
+ \`--no-refresh\` prints what is stored. \`export\` hands the whole session to a
754
+ headless host. Both write a secret to stdout.`
755
+ );
756
+ return group(cmd, "SIGN IN & SETTINGS");
607
757
  }
608
758
 
609
759
  // src/commands/admin.ts
@@ -616,10 +766,10 @@ async function importLicenseFile(cmd, file, receipt) {
616
766
  if ("stored" in res && res.stored) process.exitCode = 1;
617
767
  }
618
768
  function adminCommand() {
619
- const admin = new Command2("admin").description("Operator CLI: org, user, role, models, audit");
769
+ const admin = new Command2("admin").description("Orgs, users, roles, license, audit log");
620
770
  registerAuthLeaves(admin.command("auth").description("Operator authentication"));
621
771
  const org = admin.command("org").description("Departments and org structure");
622
- org.command("list").description("List departments").option("--role <r>", "role qualifier", "super_admin").option("--name <s>", "filter by name").option("--limit <n>", "page size", int, 100).option("--offset <n>", "page offset", int, 0).action(async (opts, cmd) => {
772
+ org.command("list").description("List departments \u2192 {departments, total}").option("--role <r>", "role qualifier", "super_admin").option("--name <s>", "filter by name").option("--limit <n>", "page size", int, 100).option("--offset <n>", "page offset", int, 0).action(async (opts, cmd) => {
623
773
  printJson(
624
774
  await clientFrom(cmd).admin.orgList({
625
775
  role: opts.role,
@@ -680,7 +830,7 @@ function adminCommand() {
680
830
  else console.log(renderOrgTree(tree));
681
831
  });
682
832
  const user = admin.command("user").description("User management");
683
- user.command("list").description("List users").option("--org <id>", "filter by department id").option("--keyword <s>", "filter by name").option("--limit <n>", "page size", int, 100).option("--offset <n>", "page offset", int, 0).action(async (opts, cmd) => {
833
+ user.command("list").description("List users \u2192 {users, total}").option("--org <id>", "filter by department id").option("--keyword <s>", "filter by name").option("--limit <n>", "page size", int, 100).option("--offset <n>", "page offset", int, 0).action(async (opts, cmd) => {
684
834
  printJson(
685
835
  await clientFrom(cmd).admin.userList({
686
836
  orgId: opts.org,
@@ -766,7 +916,7 @@ function adminCommand() {
766
916
  );
767
917
  });
768
918
  const role = admin.command("role").description("Role management");
769
- role.command("list").description("List roles").option("--keyword <s>", "filter by keyword").option("--limit <n>", "page size", int, 100).option("--offset <n>", "page offset", int, 0).option("--source <s>", "role source filter (business | user)").action(async (opts, cmd) => {
919
+ role.command("list").description("List roles \u2192 {roles}").option("--keyword <s>", "filter by keyword").option("--limit <n>", "page size", int, 100).option("--offset <n>", "page offset", int, 0).option("--source <s>", "role source filter (business | user)").action(async (opts, cmd) => {
770
920
  printJson(
771
921
  await clientFrom(cmd).admin.roleList({ keyword: opts.keyword, limit: opts.limit }),
772
922
  outputOptions(cmd)
@@ -868,7 +1018,10 @@ function adminCommand() {
868
1018
  m.command("get <modelid>").description(`Get a ${kind} model`).action(async (id, _opts, cmd) => {
869
1019
  printJson(await clientFrom(cmd).models[ns].get(id), outputOptions(cmd));
870
1020
  });
871
- const add = m.command("add").description(`Register a ${kind} model (granular flags or --body/--body-file)`).option("--name <s>", "model name").option("--api-model <s>", "upstream API model id").option("--api-key <s>", "upstream API key").option("--body <json>", "model config JSON (overrides flags)").option("--body-file <path>", "read config JSON from a file");
1021
+ const add = m.command("add").description(`Register a ${kind} model (granular flags or --body/--body-file)`).option("--name <s>", "model name").option("--api-model <s>", "upstream API model id").option("--api-key <s>", "upstream API key").option(
1022
+ "--body <json>",
1023
+ "model config JSON (overrides flags) \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (mf-model-manager)"
1024
+ ).option("--body-file <path>", "read config JSON from a file");
872
1025
  if (isLlm) {
873
1026
  add.option("--series <s>", "model series").option("--api-base <url>", "upstream API base URL").option("--icon <url>", "icon URL");
874
1027
  } else {
@@ -877,7 +1030,10 @@ function adminCommand() {
877
1030
  add.action(async (opts, cmd) => {
878
1031
  printJson(await clientFrom(cmd).models[ns].add(modelBody(opts)), outputOptions(cmd));
879
1032
  });
880
- const edit = m.command("edit <modelid>").description(`Edit a ${kind} model (granular flags or --body/--body-file)`).option("--name <s>", "model name").option("--body <json>", "model config JSON (overrides flags)").option("--body-file <path>", "read config JSON from a file");
1033
+ const edit = m.command("edit <modelid>").description(`Edit a ${kind} model (granular flags or --body/--body-file)`).option("--name <s>", "model name").option(
1034
+ "--body <json>",
1035
+ "model config JSON (overrides flags) \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (mf-model-manager)"
1036
+ ).option("--body-file <path>", "read config JSON from a file");
881
1037
  if (isLlm) {
882
1038
  edit.option("--icon <url>", "icon URL");
883
1039
  } else {
@@ -892,7 +1048,10 @@ function adminCommand() {
892
1048
  m.command("delete <modelid...>").description(`Delete ${kind} model(s)`).action(async (ids, _opts, cmd) => {
893
1049
  printJson(await clientFrom(cmd).models[ns].delete(ids), outputOptions(cmd));
894
1050
  });
895
- m.command("test <modelid>").description(`Test a ${kind} model`).option("--body <json>", "test request JSON").option("--body-file <path>", "read test request JSON from a file").action(async (id, opts, cmd) => {
1051
+ m.command("test <modelid>").description(`Test a ${kind} model`).option(
1052
+ "--body <json>",
1053
+ "test request JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (mf-model-manager)"
1054
+ ).option("--body-file <path>", "read test request JSON from a file").action(async (id, opts, cmd) => {
896
1055
  const body = opts.body || opts.bodyFile ? readBody(opts) : {};
897
1056
  printJson(
898
1057
  await clientFrom(cmd).models[ns].test({ model_id: id, ...body }),
@@ -941,10 +1100,12 @@ function adminCommand() {
941
1100
  if (key !== "baseUrl") {
942
1101
  throw new Error(`Unknown config key: ${key} (only baseUrl supported)`);
943
1102
  }
944
- setActivePlatform(value.replace(/\/+$/, ""));
1103
+ setActivePlatform(trimTrailingSlashes(value));
945
1104
  printJson({ ok: true, baseUrl: value }, outputOptions(cmd));
946
1105
  });
947
- admin.command("call <url>").description("Operator API passthrough (curl-style; auto-injected auth)").option("-X, --request <method>", "HTTP method").option(
1106
+ admin.command("call <url>").description(
1107
+ "Operator API passthrough (curl-style; auto-injected auth) \u2014 paths at https://openbkn-ai.github.io/bkn-foundry/"
1108
+ ).option("-X, --request <method>", "HTTP method").option(
948
1109
  "-H, --header <header>",
949
1110
  'extra header "Name: value" (repeatable)',
950
1111
  (v, a) => {
@@ -967,132 +1128,23 @@ function adminCommand() {
967
1128
  );
968
1129
  const out = outputOptions(cmd);
969
1130
  try {
970
- printJson(JSON.parse(res.body), out);
1131
+ printJson(parseBigIntJSON(res.body), out);
971
1132
  } catch {
972
1133
  process.stdout.write(res.body.endsWith("\n") ? res.body : `${res.body}
973
1134
  `);
974
1135
  }
975
1136
  if (res.status >= 400) process.exitCode = 1;
976
1137
  });
977
- return group(admin, "OPERATOR");
978
- }
979
-
980
- // src/commands/agent.ts
981
- import { Command as Command3 } from "commander";
982
- var int2 = (v) => Number.parseInt(v, 10);
983
- function agentCommand() {
984
- const cmd = new Command3("agent").description(
985
- "[DEPRECATED] Decision Agent \u2014 CRUD, chat, sessions, publish (being phased out)"
986
- );
987
- cmd.hook("preAction", () => {
988
- process.stderr.write(
989
- "\u26A0\uFE0F `openbkn agent` is deprecated and may be removed in a future release.\n"
990
- );
991
- });
992
- cmd.command("list").description("List published agents").option("--name <s>", "filter by name").option("--limit <n>", "page size", int2, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int2, 0).option("--category-id <id>", "filter by category").action(async (opts, cmd2) => {
993
- const data = await clientFrom(cmd2).agents.list({
994
- name: opts.name,
995
- limit: opts.limit,
996
- offset: opts.offset,
997
- categoryId: opts.categoryId
998
- });
999
- printJson(data, outputOptions(cmd2));
1000
- });
1001
- cmd.command("personal-list").description("List personal-space agents").option("--name <s>", "filter by name").option("--limit <n>", "page size", int2, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int2, 0).action(async (opts, cmd2) => {
1002
- const data = await clientFrom(cmd2).agents.personalList({
1003
- name: opts.name,
1004
- limit: opts.limit,
1005
- offset: opts.offset
1006
- });
1007
- printJson(data, outputOptions(cmd2));
1008
- });
1009
- cmd.command("category-list").description("List agent categories").action(async (_opts, cmd2) => {
1010
- printJson(await clientFrom(cmd2).agents.categoryList(), outputOptions(cmd2));
1011
- });
1012
- cmd.command("template-list").description("List published agent templates").option("--name <s>", "filter by name").option("--limit <n>", "page size", int2, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int2, 0).action(async (opts, cmd2) => {
1013
- const data = await clientFrom(cmd2).agents.templateList({
1014
- name: opts.name,
1015
- limit: opts.limit,
1016
- offset: opts.offset
1017
- });
1018
- printJson(data, outputOptions(cmd2));
1019
- });
1020
- cmd.command("template-get <id>").description("Get a published agent template").action(async (id, _opts, cmd2) => {
1021
- printJson(await clientFrom(cmd2).agents.templateGet(id), outputOptions(cmd2));
1022
- });
1023
- cmd.command("get <id>").description("Get agent details").action(async (id, _opts, cmd2) => {
1024
- printJson(await clientFrom(cmd2).agents.get(id), outputOptions(cmd2));
1025
- });
1026
- cmd.command("get-by-key <key>").description("Get an agent by key").action(async (key, _opts, cmd2) => {
1027
- printJson(await clientFrom(cmd2).agents.getByKey(key), outputOptions(cmd2));
1028
- });
1029
- cmd.command("create").description("Create an agent (--body-file <json> or --body '<json>')").option("--body <json>", "agent definition JSON").option("--body-file <path>", "read agent definition JSON from a file").action(async (opts, cmd2) => {
1030
- printJson(await clientFrom(cmd2).agents.create(readBody(opts)), outputOptions(cmd2));
1031
- });
1032
- cmd.command("update <id>").description("Update an agent (--body-file <json> or --body '<json>')").option("--body <json>", "agent definition JSON").option("--body-file <path>", "read agent definition JSON from a file").action(async (id, opts, cmd2) => {
1033
- printJson(await clientFrom(cmd2).agents.update(id, readBody(opts)), outputOptions(cmd2));
1034
- });
1035
- cmd.command("delete <id>").description("Delete an agent").option("-y, --yes", "skip confirmation").action(async (id, _opts, cmd2) => {
1036
- printJson(await clientFrom(cmd2).agents.delete(id), outputOptions(cmd2));
1037
- });
1038
- cmd.command("publish <id>").description("Publish an agent").action(async (id, _opts, cmd2) => {
1039
- printJson(await clientFrom(cmd2).agents.publish(id), outputOptions(cmd2));
1040
- });
1041
- cmd.command("unpublish <id>").description("Unpublish an agent").action(async (id, _opts, cmd2) => {
1042
- printJson(await clientFrom(cmd2).agents.unpublish(id), outputOptions(cmd2));
1043
- });
1044
- cmd.command("sessions <agent>").description("List conversations for an agent (by agent key)").option("--limit <n>", "page size", int2, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int2, 1).action(async (agentKey, opts, cmd2) => {
1045
- printJson(
1046
- await clientFrom(cmd2).agents.sessions(agentKey, { size: opts.limit, page: opts.page }),
1047
- outputOptions(cmd2)
1048
- );
1049
- });
1050
- cmd.command("history <agent> <conversation-id>").description("Show message history for a conversation").action(async (agentKey, conversationId, _opts, cmd2) => {
1051
- printJson(await clientFrom(cmd2).agents.history(agentKey, conversationId), outputOptions(cmd2));
1052
- });
1053
- cmd.command("chat <agent-id>").description("Chat with an agent (SSE streaming with --stream)").requiredOption("-m, --message <text>", "user message").option("--version <v>", "agent version", "v0").option("--conversation-id <id>", "continue an existing conversation").option("--stream", "stream the reply to stdout as it arrives").action(async (agentId, opts, cmd2) => {
1054
- const client = clientFrom(cmd2);
1055
- if (opts.stream) {
1056
- const res = await client.agents.chat(agentId, opts.message, {
1057
- version: opts.version,
1058
- conversationId: opts.conversationId,
1059
- stream: true,
1060
- onDelta: (t) => process.stdout.write(t)
1061
- });
1062
- process.stdout.write("\n");
1063
- if (res.conversationId) console.error(`conversation_id: ${res.conversationId}`);
1064
- return;
1065
- }
1066
- printJson(
1067
- await client.agents.chat(agentId, opts.message, {
1068
- version: opts.version,
1069
- conversationId: opts.conversationId
1070
- }),
1071
- outputOptions(cmd2)
1072
- );
1073
- });
1074
- cmd.command("trace <conversation-id>").description("Get trace spans for a conversation (agent-scoped alias of `trace get`)").action(async (conversationId, _opts, cmd2) => {
1075
- printJson(await clientFrom(cmd2).trace.spans(conversationId), outputOptions(cmd2));
1076
- });
1077
- const skill = cmd.command("skill").description("Manage skills attached to an agent");
1078
- skill.command("list <agent-id>").description("List skill ids attached to an agent").action(async (agentId, _opts, cmd2) => {
1079
- printJson(await clientFrom(cmd2).agents.skillList(agentId), outputOptions(cmd2));
1080
- });
1081
- skill.command("add <agent-id> <skill-ids>").description("Attach skill(s) to an agent (comma-joined ids)").action(async (agentId, ids, _opts, cmd2) => {
1082
- printJson(await clientFrom(cmd2).agents.skillAdd(agentId, csv(ids) ?? []), outputOptions(cmd2));
1083
- });
1084
- skill.command("remove <agent-id> <skill-ids>").description("Detach skill(s) from an agent (comma-joined ids)").action(async (agentId, ids, _opts, cmd2) => {
1085
- printJson(
1086
- await clientFrom(cmd2).agents.skillRemove(agentId, csv(ids) ?? []),
1087
- outputOptions(cmd2)
1088
- );
1138
+ groupChildren(admin, {
1139
+ GROUPS: ["org", "user", "role", "llm", "small-model", "license", "audit", "auth", "config"],
1140
+ RUN: ["call"]
1089
1141
  });
1090
- return group(cmd, "DECISION AGENT");
1142
+ return group(admin, "ADMINISTRATION");
1091
1143
  }
1092
1144
 
1093
1145
  // src/commands/appkey.ts
1094
- import { Command as Command4 } from "commander";
1095
- var int3 = (v) => Number.parseInt(v, 10);
1146
+ import { Command as Command3 } from "commander";
1147
+ var int2 = (v) => Number.parseInt(v, 10);
1096
1148
  var DAY_MS = 864e5;
1097
1149
  function printNewKey(created, out) {
1098
1150
  if (out.json || out.compact) {
@@ -1114,13 +1166,13 @@ function printNewKey(created, out) {
1114
1166
  `);
1115
1167
  }
1116
1168
  function appkeyCommand() {
1117
- const appkey = new Command4("appkey").description(
1118
- "AppKeys \u2014 user-issued long-lived credentials (bak_) for the Context Loader"
1169
+ const appkey = new Command3("appkey").description(
1170
+ "Issue long-lived `bak_` keys for scripts and services"
1119
1171
  );
1120
- appkey.command("list").description("List your own AppKeys (no secrets)").action(async (_opts, cmd) => {
1172
+ appkey.command("list").description("List your own AppKeys, no secrets \u2192 {keys}").action(async (_opts, cmd) => {
1121
1173
  printJson(await clientFrom(cmd).appKeys.list(), outputOptions(cmd));
1122
1174
  });
1123
- appkey.command("create").description("Issue an AppKey \u2014 the plaintext key is shown ONCE, on create").requiredOption("--name <s>", "display name (to tell keys apart)").option("--expires-at <rfc3339>", "expiry as RFC3339 (e.g. 2027-01-01T00:00:00Z)").option("--expire-days <n>", "expiry in N days from now (alternative to --expires-at)", int3).option("--never-expire", "never expire (wins over --expires-at/--expire-days)").action(async (opts, cmd) => {
1175
+ appkey.command("create").description("Issue an AppKey \u2014 the plaintext key is shown ONCE, on create").requiredOption("--name <s>", "display name (to tell keys apart)").option("--expires-at <rfc3339>", "expiry as RFC3339 (e.g. 2027-01-01T00:00:00Z)").option("--expire-days <n>", "expiry in N days from now (alternative to --expires-at)", int2).option("--never-expire", "never expire (wins over --expires-at/--expire-days)").action(async (opts, cmd) => {
1124
1176
  let expiresAt = opts.expiresAt;
1125
1177
  if (opts.expireDays !== void 0) {
1126
1178
  if (!Number.isFinite(opts.expireDays) || opts.expireDays <= 0) {
@@ -1154,11 +1206,18 @@ function appkeyCommand() {
1154
1206
  await clientFrom(cmd).appKeys.adminRevoke(id);
1155
1207
  printJson({ revoked: id }, outputOptions(cmd));
1156
1208
  });
1157
- return group(appkey, "AUTHENTICATION & CONFIG");
1209
+ groupChildren(appkey, {
1210
+ GROUPS: ["admin"],
1211
+ READ: ["list"],
1212
+ WRITE: ["create", "regenerate", "revoke"]
1213
+ });
1214
+ const appkeyAdmin = appkey.commands.find((c) => c.name() === "admin");
1215
+ if (appkeyAdmin) groupChildren(appkeyAdmin, { READ: ["list"], WRITE: ["revoke"] });
1216
+ return group(appkey, "SIGN IN & SETTINGS");
1158
1217
  }
1159
1218
 
1160
1219
  // src/commands/bkn.ts
1161
- import { Command as Command5 } from "commander";
1220
+ import { Command as Command4 } from "commander";
1162
1221
 
1163
1222
  // src/utils/bkn-validate.ts
1164
1223
  import { existsSync, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
@@ -1276,10 +1335,12 @@ function validateBknDirectory(dirPath) {
1276
1335
  }
1277
1336
 
1278
1337
  // src/commands/bkn.ts
1279
- var int4 = (v) => Number.parseInt(v, 10);
1338
+ var int3 = (v) => Number.parseInt(v, 10);
1280
1339
  function bknCommand() {
1281
- const bkn = new Command5("bkn").description("Knowledge networks \u2014 list, query, schema, instances");
1282
- bkn.command("list").description("List knowledge networks").option("--limit <n>", "page size", int4, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int4, 0).option("--name-pattern <s>", "filter by name pattern").option("--tag <s>", "filter by tag").option("--sort <field>", "sort field", "update_time").option("--direction <dir>", "asc | desc", "desc").action(async (_opts, cmd) => {
1340
+ const bkn = new Command4("bkn").description(
1341
+ "Knowledge networks: schema, metrics, search, import/export"
1342
+ );
1343
+ bkn.command("list").description("List knowledge networks").option("--limit <n>", "page size", int3, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int3, 0).option("--name-pattern <s>", "filter by name pattern").option("--tag <s>", "filter by tag").option("--sort <field>", "sort field", "update_time").option("--direction <dir>", "asc | desc", "desc").action(async (_opts, cmd) => {
1283
1344
  const o = cmd.optsWithGlobals();
1284
1345
  const data = await clientFrom(cmd).kn.list({
1285
1346
  limit: o.limit,
@@ -1298,10 +1359,17 @@ function bknCommand() {
1298
1359
  });
1299
1360
  printJson(data, outputOptions(cmd));
1300
1361
  });
1301
- bkn.command("search <kn-id> <query>").description("Semantic search within a knowledge network").option("--max-concepts <n>", "max concepts to return", int4, 10).option("--mode <mode>", "retrieval mode", "keyword_vector_retrieval").action(async (knId, query, opts, cmd) => {
1362
+ bkn.command("search <kn-id> <query>").description(
1363
+ "Recall instances from a plain sentence \u2014 no object type or field names needed \u2192 {nodes, object_types}"
1364
+ ).option("--object-types <ids>", "pin recall to these object-type ids (comma-separated)").option("--exclude-object-types <ids>", "drop these object-type ids (comma-separated)").option("--concept-groups <names>", "limit recall to these concept groups (comma-separated)").option("--max-object-types <n>", "how many object types may take part", int3).option("--max-instances <n>", "instances per object type", int3).option("--rerank", "re-rank hits with a cross-encoder (needs a rerank model deployed)").option("--no-object-types-detail", "omit the object-type definitions that come with hits").action(async (knId, query, opts, cmd) => {
1302
1365
  const data = await clientFrom(cmd).kn.search(knId, query, {
1303
- maxConcepts: opts.maxConcepts,
1304
- mode: opts.mode
1366
+ objectTypes: csv(opts.objectTypes),
1367
+ excludeObjectTypes: csv(opts.excludeObjectTypes),
1368
+ conceptGroups: csv(opts.conceptGroups),
1369
+ maxObjectTypes: opts.maxObjectTypes,
1370
+ maxInstancesPerType: opts.maxInstances,
1371
+ rerank: opts.rerank,
1372
+ includeObjectTypes: opts.objectTypesDetail === false ? false : void 0
1305
1373
  });
1306
1374
  printJson(data, outputOptions(cmd));
1307
1375
  });
@@ -1319,16 +1387,22 @@ function bknCommand() {
1319
1387
  );
1320
1388
  });
1321
1389
  if (crud) {
1322
- g.command("get <kn-id> <id>").description(`Get ${name}`).action(async (knId, id, _o, cmd) => {
1390
+ g.command("get <kn-id> <id>").description(`Get ${name} \u2192 {entries}`).action(async (knId, id, _o, cmd) => {
1323
1391
  printJson(await clientFrom(cmd).kn[`${crud}Get`](knId, id), outputOptions(cmd));
1324
1392
  });
1325
- g.command("create <kn-id>").description(`Create ${name} (--body / --body-file)`).option("--body <json>", "body JSON").option("--body-file <path>", "read body JSON from a file").action(async (knId, opts, cmd) => {
1393
+ g.command("create <kn-id>").description(`Create ${name} (--body / --body-file)`).option(
1394
+ "--body <json>",
1395
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1396
+ ).option("--body-file <path>", "read body JSON from a file").action(async (knId, opts, cmd) => {
1326
1397
  printJson(
1327
1398
  await clientFrom(cmd).kn[`${crud}Create`](knId, readBody(opts)),
1328
1399
  outputOptions(cmd)
1329
1400
  );
1330
1401
  });
1331
- g.command("update <kn-id> <id>").description(`Update ${name} (--body / --body-file)`).option("--body <json>", "body JSON").option("--body-file <path>", "read body JSON from a file").action(async (knId, id, opts, cmd) => {
1402
+ g.command("update <kn-id> <id>").description(`Update ${name} (--body / --body-file)`).option(
1403
+ "--body <json>",
1404
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1405
+ ).option("--body-file <path>", "read body JSON from a file").action(async (knId, id, opts, cmd) => {
1332
1406
  printJson(
1333
1407
  await clientFrom(cmd).kn[`${crud}Update`](knId, id, readBody(opts)),
1334
1408
  outputOptions(cmd)
@@ -1340,21 +1414,24 @@ function bknCommand() {
1340
1414
  }
1341
1415
  }
1342
1416
  const actionType = bkn.commands.find((c) => c.name() === "action-type");
1343
- actionType?.command("query <kn-id> <at-id>").description("Query an action type (--body / --body-file JSON)").option("--body <json>", "query JSON").option("--body-file <path>", "read query JSON from a file").action(async (knId, atId, opts, cmd) => {
1417
+ actionType?.command("query <kn-id> <at-id>").description("Query an action type (--body / --body-file JSON)").option(
1418
+ "--body <json>",
1419
+ "query JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (ontology-query)"
1420
+ ).option("--body-file <path>", "read query JSON from a file").action(async (knId, atId, opts, cmd) => {
1344
1421
  printJson(
1345
1422
  await clientFrom(cmd).kn.actionTypeQuery(knId, atId, readBody(opts)),
1346
1423
  outputOptions(cmd)
1347
1424
  );
1348
1425
  });
1349
- actionType?.command("execute <kn-id> <at-id>").description("Execute an action type (--body / --body-file envelope JSON)").option("--body <json>", "execution envelope JSON").option("--body-file <path>", "read envelope JSON from a file").action(async (knId, atId, opts, cmd) => {
1426
+ actionType?.command("execute <kn-id> <at-id>").description("Execute an action type (--body / --body-file envelope JSON)").option(
1427
+ "--body <json>",
1428
+ "execution envelope JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (ontology-query)"
1429
+ ).option("--body-file <path>", "read envelope JSON from a file").action(async (knId, atId, opts, cmd) => {
1350
1430
  printJson(
1351
1431
  await clientFrom(cmd).kn.actionTypeExecute(knId, atId, readBody(opts)),
1352
1432
  outputOptions(cmd)
1353
1433
  );
1354
1434
  });
1355
- actionType?.command("inputs <kn-id> <at-id>").description("Get an action type's input schema").action(async (knId, atId, _o, cmd) => {
1356
- printJson(await clientFrom(cmd).kn.actionTypeInputs(knId, atId), outputOptions(cmd));
1357
- });
1358
1435
  actionType?.command("get <kn-id> <at-id>").description("Get an action type").action(async (knId, atId, _o, cmd) => {
1359
1436
  printJson(await clientFrom(cmd).kn.actionTypeGet(knId, atId), outputOptions(cmd));
1360
1437
  });
@@ -1365,7 +1442,10 @@ function bknCommand() {
1365
1442
  printJson(await clientFrom(cmd).kn.get(knId, { exportMode: true }), outputOptions(cmd));
1366
1443
  });
1367
1444
  const objectType = bkn.commands.find((c) => c.name() === "object-type");
1368
- objectType?.command("query <kn-id> <ot-id>").description("Query instances of an object type (--body / --body-file JSON)").option("--body <json>", "query JSON").option("--body-file <path>", "read query JSON from a file").action(async (knId, otId, opts, cmd) => {
1445
+ objectType?.command("query <kn-id> <ot-id>").description("Query instances of an object type (--body / --body-file JSON)").option(
1446
+ "--body <json>",
1447
+ "query JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (ontology-query)"
1448
+ ).option("--body-file <path>", "read query JSON from a file").action(async (knId, otId, opts, cmd) => {
1369
1449
  printJson(
1370
1450
  await clientFrom(cmd).kn.objectTypeQuery(knId, otId, readBody(opts)),
1371
1451
  outputOptions(cmd)
@@ -1374,17 +1454,23 @@ function bknCommand() {
1374
1454
  bkn.command("create <name>").description("Create an (empty) knowledge network").option("--branch <b>", "branch", "main").action(async (name, opts, cmd) => {
1375
1455
  printJson(await clientFrom(cmd).kn.create({ name, branch: opts.branch }), outputOptions(cmd));
1376
1456
  });
1377
- bkn.command("update <kn-id>").description("Update a knowledge network (--body / --body-file)").option("--body <json>", "update body JSON").option("--body-file <path>", "read update body JSON from a file").action(async (knId, opts, cmd) => {
1457
+ bkn.command("update <kn-id>").description("Update a knowledge network (--body / --body-file)").option(
1458
+ "--body <json>",
1459
+ "update body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1460
+ ).option("--body-file <path>", "read update body JSON from a file").action(async (knId, opts, cmd) => {
1378
1461
  printJson(await clientFrom(cmd).kn.update(knId, readBody(opts)), outputOptions(cmd));
1379
1462
  });
1380
1463
  bkn.command("delete <kn-id>").description("Delete a knowledge network").option("-y, --yes", "skip confirmation").action(async (knId, _opts, cmd) => {
1381
1464
  printJson(await clientFrom(cmd).kn.delete(knId), outputOptions(cmd));
1382
1465
  });
1383
- bkn.command("subgraph <kn-id>").description("Query a subgraph (--body / --body-file JSON)").option("--body <json>", "subgraph query JSON").option("--body-file <path>", "read subgraph query JSON from a file").action(async (knId, opts, cmd) => {
1466
+ bkn.command("subgraph <kn-id>").description("Query a subgraph (--body / --body-file JSON)").option(
1467
+ "--body <json>",
1468
+ "subgraph query JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (ontology-query)"
1469
+ ).option("--body-file <path>", "read subgraph query JSON from a file").action(async (knId, opts, cmd) => {
1384
1470
  printJson(await clientFrom(cmd).kn.subgraph(knId, readBody(opts)), outputOptions(cmd));
1385
1471
  });
1386
- const actionLog = bkn.command("action-log").description("Action logs \u2014 list/get/cancel");
1387
- actionLog.command("list <kn-id>").description("List action logs").option("--status <s>", "filter by status").option("--action-type-id <id>", "filter by action type").option("--limit <n>", "page size", int4, DEFAULT_LIST_LIMIT).action(async (knId, opts, cmd) => {
1472
+ const actionLog = bkn.command("action-log").description("Action logs \u2014 list/get/cancel; list pages with search_after");
1473
+ actionLog.command("list <kn-id>").description("List action logs").option("--status <s>", "filter by status").option("--action-type-id <id>", "filter by action type").option("--limit <n>", "page size", int3, DEFAULT_LIST_LIMIT).action(async (knId, opts, cmd) => {
1388
1474
  printJson(
1389
1475
  await clientFrom(cmd).kn.actionLogs(knId, {
1390
1476
  status: opts.status,
@@ -1404,25 +1490,37 @@ function bknCommand() {
1404
1490
  printJson(await clientFrom(cmd).kn.actionExecution(knId, execId), outputOptions(cmd));
1405
1491
  });
1406
1492
  const metric = bkn.command("metric").description("Metrics \u2014 query / dry-run");
1407
- metric.command("query <kn-id> <metric-id>").description("Query a metric's data (--body / --body-file JSON)").option("--body <json>", "query JSON").option("--body-file <path>", "read query JSON from a file").action(async (knId, metricId, opts, cmd) => {
1493
+ metric.command("query <kn-id> <metric-id>").description("Query a metric's data (--body / --body-file JSON)").option(
1494
+ "--body <json>",
1495
+ "query JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (ontology-query)"
1496
+ ).option("--body-file <path>", "read query JSON from a file").action(async (knId, metricId, opts, cmd) => {
1408
1497
  printJson(
1409
1498
  await clientFrom(cmd).kn.metricQuery(knId, metricId, readBody(opts)),
1410
1499
  outputOptions(cmd)
1411
1500
  );
1412
1501
  });
1413
- metric.command("dry-run <kn-id>").description("Dry-run a metric definition (--body / --body-file JSON)").option("--body <json>", "metric definition JSON").option("--body-file <path>", "read metric definition JSON from a file").action(async (knId, opts, cmd) => {
1502
+ metric.command("dry-run <kn-id>").description("Dry-run a metric definition (--body / --body-file JSON)").option(
1503
+ "--body <json>",
1504
+ "metric definition JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (ontology-query)"
1505
+ ).option("--body-file <path>", "read metric definition JSON from a file").action(async (knId, opts, cmd) => {
1414
1506
  printJson(await clientFrom(cmd).kn.metricDryRun(knId, readBody(opts)), outputOptions(cmd));
1415
1507
  });
1416
1508
  metric.command("list <kn-id>").description("List metrics").action(async (knId, _o, cmd) => {
1417
1509
  printJson(await clientFrom(cmd).kn.metricList(knId), outputOptions(cmd));
1418
1510
  });
1419
- metric.command("get <kn-id> <metric-id>").description("Get a metric").action(async (knId, id, _o, cmd) => {
1511
+ metric.command("get <kn-id> <metric-id>").description("Get a metric \u2192 {entries}, since the route takes a list of ids").action(async (knId, id, _o, cmd) => {
1420
1512
  printJson(await clientFrom(cmd).kn.metricGet(knId, id), outputOptions(cmd));
1421
1513
  });
1422
- metric.command("create <kn-id>").description("Create a metric (--body / --body-file)").option("--body <json>", "body JSON").option("--body-file <path>", "read body JSON from a file").action(async (knId, opts, cmd) => {
1514
+ metric.command("create <kn-id>").description("Create a metric (--body / --body-file)").option(
1515
+ "--body <json>",
1516
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1517
+ ).option("--body-file <path>", "read body JSON from a file").action(async (knId, opts, cmd) => {
1423
1518
  printJson(await clientFrom(cmd).kn.metricCreate(knId, readBody(opts)), outputOptions(cmd));
1424
1519
  });
1425
- metric.command("update <kn-id> <metric-id>").description("Update a metric (--body / --body-file)").option("--body <json>", "body JSON").option("--body-file <path>", "read body JSON from a file").action(async (knId, id, opts, cmd) => {
1520
+ metric.command("update <kn-id> <metric-id>").description("Update a metric (--body / --body-file)").option(
1521
+ "--body <json>",
1522
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1523
+ ).option("--body-file <path>", "read body JSON from a file").action(async (knId, id, opts, cmd) => {
1426
1524
  printJson(
1427
1525
  await clientFrom(cmd).kn.metricUpdate(knId, id, readBody(opts)),
1428
1526
  outputOptions(cmd)
@@ -1431,10 +1529,10 @@ function bknCommand() {
1431
1529
  metric.command("delete <kn-id> <metric-id>").description("Delete a metric").action(async (knId, id, _o, cmd) => {
1432
1530
  printJson(await clientFrom(cmd).kn.metricDelete(knId, id), outputOptions(cmd));
1433
1531
  });
1434
- metric.command("search <kn-id>").description("Search metrics (--body / --body-file)").option("--body <json>", "body JSON").option("--body-file <path>", "read body JSON from a file").action(async (knId, opts, cmd) => {
1435
- printJson(await clientFrom(cmd).kn.metricSearch(knId, readBody(opts)), outputOptions(cmd));
1436
- });
1437
- metric.command("validate <kn-id>").description("Validate a metric definition (--body / --body-file)").option("--body <json>", "body JSON").option("--body-file <path>", "read body JSON from a file").action(async (knId, opts, cmd) => {
1532
+ metric.command("validate <kn-id>").description("Validate a metric definition (--body / --body-file)").option(
1533
+ "--body <json>",
1534
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1535
+ ).option("--body-file <path>", "read body JSON from a file").action(async (knId, opts, cmd) => {
1438
1536
  printJson(await clientFrom(cmd).kn.metricValidate(knId, readBody(opts)), outputOptions(cmd));
1439
1537
  });
1440
1538
  const cg = bkn.command("concept-group").description("Concept groups \u2014 list/get");
@@ -1444,13 +1542,19 @@ function bknCommand() {
1444
1542
  cg.command("get <kn-id> <cg-id>").description("Get a concept group").action(async (knId, cgId, _o, cmd) => {
1445
1543
  printJson(await clientFrom(cmd).kn.conceptGroup(knId, cgId), outputOptions(cmd));
1446
1544
  });
1447
- cg.command("create <kn-id>").description("Create a concept group (--body / --body-file)").option("--body <json>", "body JSON").option("--body-file <path>", "read body JSON from a file").action(async (knId, opts, cmd) => {
1545
+ cg.command("create <kn-id>").description("Create a concept group (--body / --body-file)").option(
1546
+ "--body <json>",
1547
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1548
+ ).option("--body-file <path>", "read body JSON from a file").action(async (knId, opts, cmd) => {
1448
1549
  printJson(
1449
1550
  await clientFrom(cmd).kn.conceptGroupCreate(knId, readBody(opts)),
1450
1551
  outputOptions(cmd)
1451
1552
  );
1452
1553
  });
1453
- cg.command("update <kn-id> <cg-id>").description("Update a concept group (--body / --body-file)").option("--body <json>", "body JSON").option("--body-file <path>", "read body JSON from a file").action(async (knId, cgId, opts, cmd) => {
1554
+ cg.command("update <kn-id> <cg-id>").description("Update a concept group (--body / --body-file)").option(
1555
+ "--body <json>",
1556
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1557
+ ).option("--body-file <path>", "read body JSON from a file").action(async (knId, cgId, opts, cmd) => {
1454
1558
  printJson(
1455
1559
  await clientFrom(cmd).kn.conceptGroupUpdate(knId, cgId, readBody(opts)),
1456
1560
  outputOptions(cmd)
@@ -1459,7 +1563,10 @@ function bknCommand() {
1459
1563
  cg.command("delete <kn-id> <cg-id>").description("Delete a concept group").action(async (knId, cgId, _o, cmd) => {
1460
1564
  printJson(await clientFrom(cmd).kn.conceptGroupDelete(knId, cgId), outputOptions(cmd));
1461
1565
  });
1462
- cg.command("add-members <kn-id> <cg-id>").description("Add object types to a concept group (--body / --body-file)").option("--body <json>", "body JSON").option("--body-file <path>", "read body JSON from a file").action(async (knId, cgId, opts, cmd) => {
1566
+ cg.command("add-members <kn-id> <cg-id>").description("Add object types to a concept group (--body / --body-file)").option(
1567
+ "--body <json>",
1568
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1569
+ ).option("--body-file <path>", "read body JSON from a file").action(async (knId, cgId, opts, cmd) => {
1463
1570
  printJson(
1464
1571
  await clientFrom(cmd).kn.conceptGroupAddMembers(knId, cgId, readBody(opts)),
1465
1572
  outputOptions(cmd)
@@ -1478,19 +1585,28 @@ function bknCommand() {
1478
1585
  sched.command("get <kn-id> <schedule-id>").description("Get an action schedule").action(async (knId, sId, _o, cmd) => {
1479
1586
  printJson(await clientFrom(cmd).kn.actionSchedule(knId, sId), outputOptions(cmd));
1480
1587
  });
1481
- sched.command("create <kn-id>").description("Create an action schedule (--body / --body-file)").option("--body <json>", "body JSON").option("--body-file <path>", "read body JSON from a file").action(async (knId, opts, cmd) => {
1588
+ sched.command("create <kn-id>").description("Create an action schedule (--body / --body-file)").option(
1589
+ "--body <json>",
1590
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1591
+ ).option("--body-file <path>", "read body JSON from a file").action(async (knId, opts, cmd) => {
1482
1592
  printJson(
1483
1593
  await clientFrom(cmd).kn.actionScheduleCreate(knId, readBody(opts)),
1484
1594
  outputOptions(cmd)
1485
1595
  );
1486
1596
  });
1487
- sched.command("update <kn-id> <schedule-id>").description("Update an action schedule (--body / --body-file)").option("--body <json>", "body JSON").option("--body-file <path>", "read body JSON from a file").action(async (knId, sId, opts, cmd) => {
1597
+ sched.command("update <kn-id> <schedule-id>").description("Update an action schedule (--body / --body-file)").option(
1598
+ "--body <json>",
1599
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1600
+ ).option("--body-file <path>", "read body JSON from a file").action(async (knId, sId, opts, cmd) => {
1488
1601
  printJson(
1489
1602
  await clientFrom(cmd).kn.actionScheduleUpdate(knId, sId, readBody(opts)),
1490
1603
  outputOptions(cmd)
1491
1604
  );
1492
1605
  });
1493
- sched.command("set-status <kn-id> <schedule-id>").description("Set an action schedule's status (--body / --body-file)").option("--body <json>", "body JSON").option("--body-file <path>", "read body JSON from a file").action(async (knId, sId, opts, cmd) => {
1606
+ sched.command("set-status <kn-id> <schedule-id>").description("Set an action schedule's status (--body / --body-file)").option(
1607
+ "--body <json>",
1608
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1609
+ ).option("--body-file <path>", "read body JSON from a file").action(async (knId, sId, opts, cmd) => {
1494
1610
  printJson(
1495
1611
  await clientFrom(cmd).kn.actionScheduleSetStatus(knId, sId, readBody(opts)),
1496
1612
  outputOptions(cmd)
@@ -1500,8 +1616,8 @@ function bknCommand() {
1500
1616
  printJson(await clientFrom(cmd).kn.actionScheduleDelete(knId, ids), outputOptions(cmd));
1501
1617
  });
1502
1618
  bkn.command("push <directory>").description("Pack a BKN directory into a tar and import it as a knowledge network").option("--branch <name>", "target branch", "main").option("--build", "submit a Vega build task for each object type declaring a vector index").option(
1503
- "--embedding-model <id>",
1504
- "embedding model id for declared vector indexes (with --build)"
1619
+ "--embedding-model <name-or-id>",
1620
+ "small-model name (or numeric ID, resolved to its name) for declared vector indexes (with --build)"
1505
1621
  ).action(async (dir, opts, cmd) => {
1506
1622
  printJson(
1507
1623
  await clientFrom(cmd).kn.push(dir, {
@@ -1518,7 +1634,10 @@ function bknCommand() {
1518
1634
  outputOptions(cmd)
1519
1635
  );
1520
1636
  });
1521
- bkn.command("relation-type-paths <kn-id>").description("Query relation-type paths between object types (--body / --body-file JSON)").option("--body <json>", "request JSON").option("--body-file <path>", "read request JSON from a file").action(async (knId, opts, cmd) => {
1637
+ bkn.command("relation-type-paths <kn-id>").description("Query relation-type paths between object types (--body / --body-file JSON)").option(
1638
+ "--body <json>",
1639
+ "request JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1640
+ ).option("--body-file <path>", "read request JSON from a file").action(async (knId, opts, cmd) => {
1522
1641
  printJson(
1523
1642
  await clientFrom(cmd).kn.relationTypePaths(knId, readBody(opts)),
1524
1643
  outputOptions(cmd)
@@ -1530,7 +1649,10 @@ function bknCommand() {
1530
1649
  bkn.command("create-from-catalog <catalog-id>").description("Build a knowledge network from a Vega catalog's tables").requiredOption("--name <name>", "knowledge network name").option("--tables <list>", "comma-separated table names (default: all)").option("--pk-map <map>", "explicit primary keys: '<table>:<col>[,<table>:<col>...]'").option("--build", "submit a Vega build task per resource after creation").option(
1531
1650
  "--embedding-fields <map>",
1532
1651
  "columns to vectorize per table (with --build): '<table>:<col>[+<col>...][,...]'"
1533
- ).option("--embedding-model <id>", "embedding model id for the vector index (with --build)").option("--no-rollback", "keep a partially-created KN on failure").action(async (catalogId, opts, cmd) => {
1652
+ ).option(
1653
+ "--embedding-model <name-or-id>",
1654
+ "small-model name (or numeric ID, resolved to its name) for the vector index (with --build)"
1655
+ ).option("--no-rollback", "keep a partially-created KN on failure").action(async (catalogId, opts, cmd) => {
1534
1656
  printJson(
1535
1657
  await clientFrom(cmd).kn.createFromCatalog({
1536
1658
  catalogId,
@@ -1551,39 +1673,73 @@ function bknCommand() {
1551
1673
  printJson(result, outputOptions(cmd));
1552
1674
  if (!result.valid) process.exitCode = 1;
1553
1675
  });
1554
- bkn.command("create-from-csv <catalog-id>").description("Import CSV files into a Vega catalog, then build a KN from them").requiredOption("--files <glob>", "CSV paths (comma-separated or glob)").requiredOption("--name <name>", "knowledge network name").option("--table-prefix <s>", "prefix for derived table names", "").option("--batch-size <n>", "rows per insert batch", int4, 500).option("--tables <list>", "subset of imported tables to include in the KN").option("--pk-map <map>", "explicit primary keys: '<table>:<col>[,...]'").option("--build", "submit a Vega build task per resource after creation").option(
1555
- "--embedding-fields <map>",
1556
- "columns to vectorize per table (with --build): '<table>:<col>[+<col>...][,...]'"
1557
- ).option("--embedding-model <id>", "embedding model id for the vector index (with --build)").option("--no-rollback", "keep a partially-created KN on failure").action(async (catalogId, opts, cmd) => {
1558
- printJson(
1559
- await clientFrom(cmd).kn.createFromCsv({
1560
- catalogId,
1561
- name: opts.name,
1562
- files: opts.files,
1563
- tablePrefix: opts.tablePrefix,
1564
- batchSize: opts.batchSize,
1565
- tables: csv(opts.tables),
1566
- pkMap: opts.pkMap ? parsePkMap(opts.pkMap) : void 0,
1567
- build: Boolean(opts.build),
1568
- embeddingFields: opts.embeddingFields ? parseEmbeddingFields(opts.embeddingFields) : void 0,
1569
- embeddingModel: opts.embeddingModel,
1570
- noRollback: opts.rollback === false,
1571
- onProgress: (m) => console.error(m)
1572
- }),
1573
- outputOptions(cmd)
1574
- );
1575
- });
1576
- return group(bkn, "AI DATA PLATFORM");
1676
+ groupChildren(bkn, {
1677
+ GROUPS: [
1678
+ "object-type",
1679
+ "relation-type",
1680
+ "action-type",
1681
+ "metric",
1682
+ "concept-group",
1683
+ "action-log",
1684
+ "action-schedule"
1685
+ ],
1686
+ READ: [
1687
+ "pull",
1688
+ "list",
1689
+ "get",
1690
+ "stats",
1691
+ "export",
1692
+ "search",
1693
+ "subgraph",
1694
+ "relation-type-paths",
1695
+ "resources",
1696
+ "action-execution",
1697
+ "validate"
1698
+ ],
1699
+ WRITE: ["create", "update", "delete", "push", "create-from-catalog"]
1700
+ });
1701
+ groupChildren(metric, {
1702
+ READ: ["list", "get"],
1703
+ RUN: ["query", "dry-run", "validate"],
1704
+ WRITE: ["create", "update", "delete"]
1705
+ });
1706
+ guide(
1707
+ bkn,
1708
+ `WHERE IDS COME FROM
1709
+ \`list\` gives kn ids; \`object-type list <kn-id>\` and \`search <kn-id> "<q>"\` give the rest.
1710
+
1711
+ SEARCH VS THE MCP SIDE
1712
+ \`search\` recalls instance rows from one sentence and ships the object-type definitions
1713
+ needed to read them \u2014 start here when you do not know the schema yet. It only reaches
1714
+ properties indexed for match/knn, so an unindexed object type yields nothing, and no hits
1715
+ comes back as empty nodes with a message rather than an error. For schema alone use
1716
+ \`openbkn context search-schema\`; for a structured filter use \`object-type query\`.
1717
+
1718
+ EDITING SCHEMA AS FILES
1719
+ pull <kn-id> ./dir -> edit -> validate ./dir -> push ./dir
1720
+ \`validate\` is offline and catches structure errors before the upload.
1721
+
1722
+ REQUEST BODIES
1723
+ create/update take a definition; query/execute/dry-run take a query. Both are documented
1724
+ at https://openbkn-ai.github.io/bkn-foundry/ \u2014 definitions under bkn-backend, reads and
1725
+ executions under ontology-query. Each command's --body flag names its own module.
1726
+
1727
+ CREATING FROM DATA
1728
+ create-from-catalog <catalog-id> --name "<n>" builds a network from a Vega catalog,
1729
+ then \`openbkn vega dataset build <resource-id>\` produces the index. There is no
1730
+ whole-network build.`
1731
+ );
1732
+ return group(bkn, "DATA & KNOWLEDGE");
1577
1733
  }
1578
1734
 
1579
1735
  // src/commands/call.ts
1580
- import { Command as Command6 } from "commander";
1736
+ import { Command as Command5 } from "commander";
1581
1737
  function collect(value, prev) {
1582
1738
  prev.push(value);
1583
1739
  return prev;
1584
1740
  }
1585
1741
  function callCommand() {
1586
- const cmd = new Command6("call").alias("curl").description("Call an API with curl-style flags and auto-injected auth headers").argument("<url>", "API path (e.g. /api/...) or absolute URL").option("-X, --request <method>", "HTTP method").option("-H, --header <header>", 'extra header "Name: value" (repeatable)', collect, []).option("-d, --data <body>", "request body (sets JSON content-type if unset)").option("--data-raw <body>", "alias for --data").option(
1742
+ const cmd = new Command5("call").alias("curl").description("Call any platform API endpoint directly (auth added)").argument("<url>", "API path (e.g. /api/...) or absolute URL").option("-X, --request <method>", "HTTP method").option("-H, --header <header>", 'extra header "Name: value" (repeatable)', collect, []).option("-d, --data <body>", "request body (sets JSON content-type if unset)").option("--data-raw <body>", "alias for --data").option(
1587
1743
  "-F, --form <field>",
1588
1744
  "multipart field key=value or key=@file (repeatable)",
1589
1745
  collect,
@@ -1609,7 +1765,7 @@ function callCommand() {
1609
1765
  });
1610
1766
  const out = outputOptions(cmd2);
1611
1767
  try {
1612
- printJson(JSON.parse(res.body), out);
1768
+ printJson(parseBigIntJSON(res.body), out);
1613
1769
  } catch {
1614
1770
  process.stdout.write(res.body.endsWith("\n") ? res.body : `${res.body}
1615
1771
  `);
@@ -1620,18 +1776,30 @@ function callCommand() {
1620
1776
  process.exitCode = 1;
1621
1777
  }
1622
1778
  });
1623
- return group(cmd, "AUTHENTICATION & CONFIG");
1779
+ guide(
1780
+ cmd,
1781
+ `WHEN TO USE THIS
1782
+ Anything the named commands do not cover: services with no command group yet
1783
+ (bkn-agent, execution-factory operators and sandbox functions, MCP registration,
1784
+ skill index builds), and endpoints newer than this CLI.
1785
+
1786
+ FINDING THE PATH
1787
+ Every service's API is documented at https://openbkn-ai.github.io/bkn-foundry/ \u2014
1788
+ read the path and request body there rather than guessing. Auth, business domain
1789
+ and TLS flags are injected the same way as for any other command.`
1790
+ );
1791
+ return group(cmd, "RAW API");
1624
1792
  }
1625
1793
 
1626
1794
  // src/commands/config.ts
1627
- import { Command as Command7 } from "commander";
1795
+ import { Command as Command6 } from "commander";
1628
1796
  function requireActive() {
1629
1797
  const baseUrl = activePlatform();
1630
1798
  if (!baseUrl) throw new InputError("No active platform. Run `openbkn auth login <url>` first.");
1631
1799
  return baseUrl;
1632
1800
  }
1633
1801
  function configCommand() {
1634
- const config = new Command7("config").description("Per-platform CLI configuration");
1802
+ const config = new Command6("config").description("Remember a platform URL / business domain");
1635
1803
  config.command("show").description("Show the active platform and business domain").action((_opts, cmd) => {
1636
1804
  const baseUrl = activePlatform();
1637
1805
  printJson(
@@ -1644,9 +1812,9 @@ function configCommand() {
1644
1812
  });
1645
1813
  config.command("set <key> <value>").description("Set a config value (baseUrl | businessDomain)").action((key, value, _opts, cmd) => {
1646
1814
  if (key === "baseUrl") {
1647
- setActivePlatform(value.replace(/\/+$/, ""));
1815
+ setActivePlatform(trimTrailingSlashes(value));
1648
1816
  } else if (key === "businessDomain") {
1649
- writePlatformConfig(requireActive(), { businessDomain: value });
1817
+ updatePlatformConfig(requireActive(), { businessDomain: value });
1650
1818
  } else {
1651
1819
  throw new InputError(`Unknown config key: ${key} (expected baseUrl | businessDomain)`);
1652
1820
  }
@@ -1654,18 +1822,19 @@ function configCommand() {
1654
1822
  });
1655
1823
  config.command("set-bd <value>").description("Set the default business domain for the active platform").action((value, _opts, cmd) => {
1656
1824
  const baseUrl = requireActive();
1657
- writePlatformConfig(baseUrl, { businessDomain: value });
1825
+ updatePlatformConfig(baseUrl, { businessDomain: value });
1658
1826
  printJson({ baseUrl, businessDomain: value }, outputOptions(cmd));
1659
1827
  });
1660
1828
  config.command("list-bd").description("List business domains (requires login)").action(() => {
1661
1829
  throw new InputError("Not yet implemented \u2014 requires backend business-domains API.");
1662
1830
  });
1663
- return group(config, "AUTHENTICATION & CONFIG");
1831
+ groupChildren(config, { READ: ["show", "list-bd"], WRITE: ["set", "set-bd"] });
1832
+ return group(config, "SIGN IN & SETTINGS");
1664
1833
  }
1665
1834
 
1666
1835
  // src/commands/context.ts
1667
- import { Command as Command8 } from "commander";
1668
- var int5 = (v) => Number.parseInt(v, 10);
1836
+ import { Command as Command7 } from "commander";
1837
+ var int4 = (v) => Number.parseInt(v, 10);
1669
1838
  var collectArg = (v, prev) => {
1670
1839
  prev.push(v);
1671
1840
  return prev;
@@ -1674,7 +1843,7 @@ function buildArgs(opts) {
1674
1843
  let out = {};
1675
1844
  if (opts.args) {
1676
1845
  try {
1677
- out = JSON.parse(opts.args);
1846
+ out = parseBigIntJSON(opts.args);
1678
1847
  } catch {
1679
1848
  throw new InputError("--args must be valid JSON");
1680
1849
  }
@@ -1685,7 +1854,7 @@ function buildArgs(opts) {
1685
1854
  const key = pair.slice(0, idx);
1686
1855
  const raw = pair.slice(idx + 1);
1687
1856
  try {
1688
- out[key] = JSON.parse(raw);
1857
+ out[key] = parseBigIntJSON(raw);
1689
1858
  } catch {
1690
1859
  out[key] = raw;
1691
1860
  }
@@ -1703,33 +1872,44 @@ function printToolList(res, out) {
1703
1872
  printJson(res, out);
1704
1873
  return;
1705
1874
  }
1706
- const rows = arr.map((t) => ({
1875
+ const rows2 = arr.map((t) => ({
1707
1876
  name: t.name ?? t.tool_name ?? t.key ?? "",
1708
1877
  description: typeof t.description === "string" ? t.description : ""
1709
1878
  }));
1710
- printJson(rows, out);
1879
+ printJson(rows2, out);
1711
1880
  }
1712
1881
  function contextCommand() {
1713
- const cmd = new Command8("context").description(
1714
- "Context loader (MCP) \u2014 schema discovery, instance query, skill recall"
1882
+ const cmd = new Command7("context").description(
1883
+ "Ask a network questions (the MCP interface agents use)"
1715
1884
  );
1716
- cmd.command("search-schema <kn-id> <query>").description("Search object/relation/action/metric schemas").option("--scope <list>", "comma-separated scopes (object,relation,action,metric)").option("--max <n>", "max concepts", int5).action(async (knId, query, opts, cmd2) => {
1885
+ const jsonArgs = (raw) => {
1886
+ if (!raw) throw new InputError("--args is required (run with --schema to see its shape)");
1887
+ try {
1888
+ return parseBigIntJSON(raw);
1889
+ } catch {
1890
+ throw new InputError("--args must be valid JSON");
1891
+ }
1892
+ };
1893
+ cmd.command("search-schema <kn-id> <query>").description(
1894
+ "Search object/relation/action/metric schemas \u2192 {object_types, relation_types, action_types, metric_types}"
1895
+ ).option("--scope <list>", "comma-separated scopes (object,relation,action,metric)").option("--max <n>", "max concepts", int4).action(async (knId, query, opts, cmd2) => {
1717
1896
  const data = await clientFrom(cmd2).context.searchSchema(knId, query, {
1718
1897
  searchScope: opts.scope ? String(opts.scope).split(",") : void 0,
1719
1898
  maxConcepts: opts.max
1720
1899
  });
1721
1900
  printJson(data, outputOptions(cmd2));
1722
1901
  });
1723
- cmd.command("query-object-instance <kn-id>").description("Query object instances (provide --args as JSON)").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, opts, cmd2) => {
1724
- let args;
1725
- try {
1726
- args = JSON.parse(opts.args);
1727
- } catch {
1728
- throw new InputError("--args must be valid JSON");
1729
- }
1902
+ cmd.command("query-object-instance <kn-id>").description(
1903
+ `Query one object type's instances \u2014 \`--args '{"ot_id":"<id>","limit":10}'\` \u2192 {datas, total_count}`
1904
+ ).option(
1905
+ "--args <json>",
1906
+ "tool arguments as JSON; kn_id is filled from <kn-id>; --schema prints the shape"
1907
+ ).option("--schema", "print this tool's argument schema from the deploy instead of calling it").action(async (knId, opts, cmd2) => {
1908
+ if (opts.schema) return printToolSchema(cmd2, knId, "query_object_instance");
1909
+ const args = jsonArgs(opts.args);
1730
1910
  printJson(await clientFrom(cmd2).context.queryObjectInstance(knId, args), outputOptions(cmd2));
1731
1911
  });
1732
- cmd.command("find-skills <kn-id> <object-type-id>").description("Recall skills for an object type").option("--top-k <n>", "max skills (1-20)", int5).action(async (knId, otId, opts, cmd2) => {
1912
+ cmd.command("find-skills <kn-id> <object-type-id>").description("Recall skills for an object type").option("--top-k <n>", "max skills (1-20)", int4).action(async (knId, otId, opts, cmd2) => {
1733
1913
  printJson(
1734
1914
  await clientFrom(cmd2).context.findSkills(knId, otId, opts.topK),
1735
1915
  outputOptions(cmd2)
@@ -1745,18 +1925,139 @@ function contextCommand() {
1745
1925
  cmd.command("relation-types <kn-id> <ids...>").description("Full definitions for the given relation-type ids (unmatched \u2192 `missing`)").action(async (knId, ids, _opts, cmd2) => {
1746
1926
  printJson(await clientFrom(cmd2).context.relationTypes(knId, ids), outputOptions(cmd2));
1747
1927
  });
1928
+ cmd.command("conversation").description("Show the remembered conversation, or forget it with --forget").option(
1929
+ "--forget",
1930
+ "drop it, so the next command opens a fresh conversation (acts on this machine's store for the active user, whatever identity the request would use)"
1931
+ ).action((opts, cmd2) => {
1932
+ const o = cmd2.optsWithGlobals();
1933
+ const baseUrl = platformOf(o);
1934
+ if (!baseUrl) throw new InputError("No platform. Run `openbkn auth login` first.");
1935
+ const forgot = opts.forget ? readPlatformConfig(baseUrl).conversationId : void 0;
1936
+ if (opts.forget) {
1937
+ updatePlatformConfig(baseUrl, {
1938
+ conversationId: void 0,
1939
+ conversationOpenedAt: void 0
1940
+ });
1941
+ }
1942
+ const { id, source } = conversationSource(o);
1943
+ const stored = readPlatformConfig(baseUrl);
1944
+ printJson(
1945
+ {
1946
+ baseUrl,
1947
+ conversationId: id ?? null,
1948
+ source,
1949
+ ...stored.conversationOpenedAt ? { storedOpenedAt: stored.conversationOpenedAt } : {},
1950
+ // What is on disk, even when something outranks it — otherwise
1951
+ // `--forget` looks like a no-op to whoever just ran this.
1952
+ ...stored.conversationId && stored.conversationId !== id ? { storedConversationId: stored.conversationId } : {},
1953
+ ...opts.forget ? { forgot: forgot ?? null } : {}
1954
+ },
1955
+ outputOptions(cmd2)
1956
+ );
1957
+ });
1748
1958
  cmd.command("info").description("List the deploy's MCP tool catalog (global \u2014 no KN needed)").action(async (_opts, cmd2) => {
1749
1959
  printToolList(await clientFrom(cmd2).context.info(), outputOptions(cmd2));
1750
1960
  });
1751
- cmd.command("tools <kn-id>").description("List MCP tools advertised for a KN session").action(async (knId, _opts, cmd2) => {
1961
+ const printToolSchema = async (cmd2, knId, tool) => {
1962
+ const listed = await clientFrom(cmd2).context.tools(knId);
1963
+ const found = listed.tools?.find((t) => t.name === tool);
1964
+ if (!found) throw new InputError(`this deploy does not advertise the ${tool} tool`);
1965
+ printJson(
1966
+ { tool: found.name, description: found.description, inputSchema: found.inputSchema },
1967
+ { ...outputOptions(cmd2), json: true }
1968
+ );
1969
+ };
1970
+ cmd.command("run-sql <kn-id>").description(
1971
+ "Aggregate, rank or join with read-only SQL \u2014 what query-object-instance cannot do"
1972
+ ).option("--sql <sql>", "read-only MySQL over data resources, tables named as {{<resource-id>}}").option("--timeout <sec>", "query timeout in seconds", (v) => Number.parseInt(v, 10)).option("--schema", "print this tool's argument schema from the deploy instead of calling it").addHelpText(
1973
+ "after",
1974
+ `
1975
+ A table is named by resource id, never by the name it carries on the source:
1976
+
1977
+ openbkn context search-schema <kn-id> "<what you are after>" --json
1978
+ \u2192 object_types[].data_source.id
1979
+
1980
+ openbkn context run-sql <kn-id> \\
1981
+ --sql "SELECT supplier_id, COUNT(*) c FROM {{d9g387peef0be1ifnurg}} GROUP BY supplier_id"
1982
+
1983
+ Joining two resources means two ids, one placeholder each. Column names are the
1984
+ physical ones. Answers {columns, entries, paging} plus a bkn_receipt recording
1985
+ the operation in BKN Trace. Row limits belong in the SQL \u2014 this tool takes no
1986
+ limit argument.
1987
+
1988
+ The same SQL runs without a knowledge network through \`openbkn vega sql\`, which
1989
+ adds paging and --need-total but records nothing in Trace.`
1990
+ ).action(async (knId, opts, cmd2) => {
1991
+ if (opts.schema) return printToolSchema(cmd2, knId, "run_sql");
1992
+ if (!opts.sql) throw new InputError("--sql is required (or use --schema to see the shape)");
1993
+ const args = { sql: opts.sql };
1994
+ if (opts.timeout !== void 0) args.query_timeout = opts.timeout;
1995
+ printJson(await clientFrom(cmd2).context.toolCall(knId, "run_sql", args), outputOptions(cmd2));
1996
+ });
1997
+ cmd.command("explore-subgraph <kn-id> <object-type-id>").description("Follow relations outward from one object type without naming a path first").option("--hops <n>", "how many hops to walk, 1-3", (v) => Number.parseInt(v, 10)).option(
1998
+ "--direction <d>",
1999
+ "forward | backward | bidirectional \u2014 pick bidirectional when unsure how the relation reads",
2000
+ "bidirectional"
2001
+ ).option(
2002
+ "--limit <n>",
2003
+ "instances of the STARTING type, not paths or total objects",
2004
+ (v) => Number.parseInt(v, 10)
2005
+ ).option("--args <json>", "extra tool arguments merged in (condition, sort, offset \u2026)").option("--schema", "print this tool's argument schema from the deploy instead of calling it").addHelpText(
2006
+ "after",
2007
+ `
2008
+ Use this when the topology is the question \u2014 "what does this supplier touch?" \u2014
2009
+ and \`query-instance-subgraph\` when you already know which relations to walk.
2010
+ Paths multiply with each hop, so start at 1 or 2.`
2011
+ ).action(async (knId, objectTypeId, opts, cmd2) => {
2012
+ if (opts.schema) return printToolSchema(cmd2, knId, "explore_subgraph");
2013
+ if (opts.hops === void 0) {
2014
+ throw new InputError("--hops is required (or use --schema to see the shape)");
2015
+ }
2016
+ const args = {
2017
+ ...opts.args ? jsonArgs(opts.args) : {},
2018
+ source_object_type_id: objectTypeId,
2019
+ direction: opts.direction,
2020
+ path_length: opts.hops
2021
+ };
2022
+ if (opts.limit !== void 0) args.limit = opts.limit;
2023
+ printJson(
2024
+ await clientFrom(cmd2).context.toolCall(knId, "explore_subgraph", args),
2025
+ outputOptions(cmd2)
2026
+ );
2027
+ });
2028
+ cmd.command("query-metric <kn-id> <metric-id>").description("Read a modelled metric through its own definition \u2014 do not restate it in SQL").option(
2029
+ "--args <json>",
2030
+ "tool arguments: analysis_dimensions, time, condition, having, order_by"
2031
+ ).option("--schema", "print this tool's argument schema from the deploy instead of calling it").addHelpText(
2032
+ "after",
2033
+ `
2034
+ Metric ids come from an object type: \`context object-types <kn-id> <ot-id>\`
2035
+ lists them under related_metrics. The definition owns the arithmetic, so
2036
+ rewriting it with \`run-sql\` produces a number the platform will not agree with.`
2037
+ ).action(async (knId, metricId, opts, cmd2) => {
2038
+ if (opts.schema) return printToolSchema(cmd2, knId, "query_metric");
2039
+ const args = { ...opts.args ? jsonArgs(opts.args) : {}, metric_id: metricId };
2040
+ printJson(
2041
+ await clientFrom(cmd2).context.toolCall(knId, "query_metric", args),
2042
+ outputOptions(cmd2)
2043
+ );
2044
+ });
2045
+ cmd.command("tools <kn-id>").description("List MCP tools advertised for a KN session \u2192 {tools} with each inputSchema").action(async (knId, _opts, cmd2) => {
1752
2046
  printToolList(await clientFrom(cmd2).context.tools(knId), outputOptions(cmd2));
1753
2047
  });
1754
- cmd.command("tool-call <kn-id> <name>").description("Call any MCP tool by name \u2014 current or future (use `tools` to discover)").option("--args <json>", "tool arguments as JSON").option(
2048
+ cmd.command("tool-call <kn-id> <name>").description("Call any MCP tool by name \u2014 current or future (use `tools` to discover)").option(
2049
+ "--args <json>",
2050
+ "tool arguments as JSON; kn_id is filled from <kn-id> \u2014 input schema comes from `context tools <kn-id>`"
2051
+ ).option(
1755
2052
  "--arg <key=value>",
1756
2053
  "one argument (repeatable; value parsed as JSON, else string)",
1757
2054
  collectArg,
1758
2055
  []
2056
+ ).option(
2057
+ "--schema",
2058
+ "print the named tool's argument schema from the deploy instead of calling it"
1759
2059
  ).action(async (knId, name, opts, cmd2) => {
2060
+ if (opts.schema) return printToolSchema(cmd2, knId, name);
1760
2061
  printJson(
1761
2062
  await clientFrom(cmd2).context.toolCall(knId, name, buildArgs(opts)),
1762
2063
  outputOptions(cmd2)
@@ -1764,7 +2065,7 @@ function contextCommand() {
1764
2065
  });
1765
2066
  cmd.command("call-method <kn-id> <method>").description(
1766
2067
  "Call any MCP method by name (e.g. tools/list, resources/read) \u2014 current or future"
1767
- ).option("--args <json>", "method params as JSON").option(
2068
+ ).option("--args <json>", "method params as JSON \u2014 see `context call-method <kn-id> tools/list`").option(
1768
2069
  "--arg <key=value>",
1769
2070
  "one param (repeatable; value parsed as JSON, else string)",
1770
2071
  collectArg,
@@ -1787,239 +2088,1027 @@ function contextCommand() {
1787
2088
  cmd.command("prompts <kn-id>").description("List MCP prompts").action(async (knId, _opts, cmd2) => {
1788
2089
  printJson(await clientFrom(cmd2).context.prompts(knId), outputOptions(cmd2));
1789
2090
  });
1790
- cmd.command("prompt <kn-id> <name>").description("Get one MCP prompt (--args JSON for prompt arguments)").option("--args <json>", "prompt arguments as JSON").action(async (knId, name, opts, cmd2) => {
2091
+ cmd.command("prompt <kn-id> <name>").description("Get one MCP prompt (--args JSON for prompt arguments)").option(
2092
+ "--args <json>",
2093
+ "prompt arguments as JSON \u2014 argument names come from `context prompts <kn-id>`"
2094
+ ).action(async (knId, name, opts, cmd2) => {
1791
2095
  let args;
1792
2096
  if (opts.args) {
1793
2097
  try {
1794
- args = JSON.parse(opts.args);
2098
+ args = parseBigIntJSON(opts.args);
1795
2099
  } catch {
1796
2100
  throw new InputError("--args must be valid JSON");
1797
2101
  }
1798
2102
  }
1799
2103
  printJson(await clientFrom(cmd2).context.prompt(knId, name, args), outputOptions(cmd2));
1800
2104
  });
1801
- const jsonArgs = (raw) => {
1802
- try {
1803
- return JSON.parse(raw);
1804
- } catch {
1805
- throw new InputError("--args must be valid JSON");
1806
- }
1807
- };
1808
- cmd.command("query-instance-subgraph <kn-id>").description("Query an instance subgraph across relation-type paths").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, opts, cmd2) => {
2105
+ cmd.command("query-instance-subgraph <kn-id>").description("Query an instance subgraph across relation-type paths").option(
2106
+ "--args <json>",
2107
+ "tool arguments as JSON; kn_id is filled from <kn-id>; --schema prints the shape"
2108
+ ).option("--schema", "print this tool's argument schema from the deploy instead of calling it").action(async (knId, opts, cmd2) => {
2109
+ if (opts.schema) return printToolSchema(cmd2, knId, "query_instance_subgraph");
1809
2110
  printJson(
1810
2111
  await clientFrom(cmd2).context.queryInstanceSubgraph(knId, jsonArgs(opts.args)),
1811
2112
  outputOptions(cmd2)
1812
2113
  );
1813
2114
  });
1814
- cmd.command("get-logic-properties <kn-id>").description("Compute logic-property values for instances").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, opts, cmd2) => {
2115
+ cmd.command("get-logic-properties <kn-id>").description("Compute logic-property values for instances").option(
2116
+ "--args <json>",
2117
+ "tool arguments as JSON; kn_id is filled from <kn-id>; --schema prints the shape"
2118
+ ).option("--schema", "print this tool's argument schema from the deploy instead of calling it").action(async (knId, opts, cmd2) => {
2119
+ if (opts.schema) return printToolSchema(cmd2, knId, "get_logic_properties_values");
1815
2120
  printJson(
1816
2121
  await clientFrom(cmd2).context.logicProperties(knId, jsonArgs(opts.args)),
1817
2122
  outputOptions(cmd2)
1818
2123
  );
1819
2124
  });
1820
- cmd.command("get-action-info <kn-id>").description("Fetch action info / dynamic tools for an instance").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, opts, cmd2) => {
2125
+ cmd.command("get-action-info <kn-id>").description("Fetch action info / dynamic tools for an instance").option(
2126
+ "--args <json>",
2127
+ "tool arguments as JSON; kn_id is filled from <kn-id>; --schema prints the shape"
2128
+ ).option("--schema", "print this tool's argument schema from the deploy instead of calling it").action(async (knId, opts, cmd2) => {
2129
+ if (opts.schema) return printToolSchema(cmd2, knId, "get_action_info");
1821
2130
  printJson(
1822
2131
  await clientFrom(cmd2).context.actionInfo(knId, jsonArgs(opts.args)),
1823
2132
  outputOptions(cmd2)
1824
2133
  );
1825
2134
  });
1826
- return group(cmd, "AI DATA PLATFORM");
1827
- }
2135
+ groupChildren(cmd, {
2136
+ GROUPS: ["conversation"],
2137
+ READ: [
2138
+ "search-schema",
2139
+ "kn-detail",
2140
+ "object-types",
2141
+ "relation-types",
2142
+ "info",
2143
+ "tools",
2144
+ "resources",
2145
+ "resource",
2146
+ "templates",
2147
+ "prompts",
2148
+ "prompt",
2149
+ "find-skills"
2150
+ ],
2151
+ RUN: [
2152
+ "query-object-instance",
2153
+ "run-sql",
2154
+ "explore-subgraph",
2155
+ "query-metric",
2156
+ "query-instance-subgraph",
2157
+ "get-logic-properties",
2158
+ "get-action-info",
2159
+ "tool-call",
2160
+ "call-method"
2161
+ ]
2162
+ });
2163
+ guide(
2164
+ cmd,
2165
+ `ORDER OF WORK
2166
+ 1. search-schema <kn-id> "<question>" find the object/relation/action ids that matter
2167
+ 2. kn-detail / object-types drill into the ones you picked
2168
+ 3. query-object-instance filter + sort + page one object type
2169
+ query-instance-subgraph follow a known relation path
2170
+ get-logic-properties / get-action-info computed values, runnable actions
1828
2171
 
1829
- // src/commands/dataflow.ts
1830
- import { Command as Command9 } from "commander";
1831
- var int6 = (v) => Number.parseInt(v, 10);
1832
- function dataflowCommand() {
1833
- const cmd = new Command9("dataflow").description("Dataflow document workflows \u2014 list, runs, logs");
1834
- cmd.command("list").description("List all dataflows").action(async (_opts, cmd2) => {
1835
- printJson(await clientFrom(cmd2).dataflows.list(), outputOptions(cmd2));
1836
- });
1837
- cmd.command("runs <dagId>").description("List run records for one dataflow").option("--since <date>", "filter runs since a date").option("--limit <n>", "page size (backend default 20)", int6).option("--page <n>", "page (0-based; backend default 0)", int6).action(async (dagId, opts, cmd2) => {
1838
- printJson(
1839
- await clientFrom(cmd2).dataflows.runs(dagId, {
1840
- since: opts.since,
1841
- page: opts.page,
1842
- limit: opts.limit
1843
- }),
1844
- outputOptions(cmd2)
1845
- );
1846
- });
1847
- cmd.command("logs <dagId> <instanceId>").description("Show logs for one run").option("--page <n>", "page", int6, 0).option("--limit <n>", "page size", int6, DEFAULT_LIST_LIMIT).action(async (dagId, instanceId, opts, cmd2) => {
1848
- printJson(
1849
- await clientFrom(cmd2).dataflows.logs(dagId, instanceId, {
1850
- page: opts.page,
1851
- limit: opts.limit
1852
- }),
1853
- outputOptions(cmd2)
1854
- );
1855
- });
1856
- cmd.command("run <dagId>").description("Trigger a dataflow run from a remote file URL").requiredOption("--url <url>", "remote file URL").requiredOption("--name <name>", "file name").action(async (dagId, opts, cmd2) => {
1857
- printJson(
1858
- await clientFrom(cmd2).dataflows.run(dagId, opts.url, opts.name),
1859
- outputOptions(cmd2)
1860
- );
1861
- });
1862
- cmd.command("create").description("Create a dataflow (DAG) from a full document body (--body / --body-file)").option("--body <json>", "dataflow document JSON").option("--body-file <path>", "read the dataflow document JSON from a file").action(async (opts, cmd2) => {
1863
- printJson(await clientFrom(cmd2).dataflows.create(readBody(opts)), outputOptions(cmd2));
1864
- });
1865
- const parseSet = (pairs) => {
1866
- const out = {};
1867
- for (const item of pairs ?? []) {
1868
- const i = item.indexOf("=");
1869
- if (i > 0) out[item.slice(0, i)] = item.slice(i + 1);
1870
- }
1871
- return out;
1872
- };
1873
- cmd.command("templates").description("List available dataset/bkn/dataflow templates").action(async (_opts, cmd2) => {
1874
- printJson(clientFrom(cmd2).dataflows.templates(), outputOptions(cmd2));
1875
- });
1876
- cmd.command("create-dataset").description("Create a dataset from a template (--template <name> --set k=v ...)").requiredOption("--template <name>", "template name").option(
1877
- "--set <kv...>",
1878
- "set a template argument (key=value); repeatable",
1879
- (v, acc) => {
1880
- acc.push(v);
1881
- return acc;
1882
- },
1883
- []
1884
- ).action(async (opts, cmd2) => {
1885
- printJson(
1886
- await clientFrom(cmd2).dataflows.createDataset(opts.template, parseSet(opts.set)),
1887
- outputOptions(cmd2)
1888
- );
1889
- });
1890
- cmd.command("create-bkn").description("Create a knowledge network from a template (--template <name> --set k=v ...)").requiredOption("--template <name>", "template name").option(
1891
- "--set <kv...>",
1892
- "set a template argument (key=value); repeatable",
1893
- (v, acc) => {
1894
- acc.push(v);
1895
- return acc;
1896
- },
1897
- []
1898
- ).action(async (opts, cmd2) => {
1899
- printJson(
1900
- await clientFrom(cmd2).dataflows.createBkn(opts.template, parseSet(opts.set)),
1901
- outputOptions(cmd2)
1902
- );
1903
- });
1904
- return group(cmd, "AI DATA PLATFORM");
1905
- }
2172
+ PICKING THE RIGHT QUERY
2173
+ Aggregation, ranking, GROUP BY or joins are not query-object-instance \u2014 send SQL through
2174
+ \`tool-call <kn-id> run_sql\`. Unknown topology is \`tool-call <kn-id> explore_subgraph\`,
2175
+ not a hand-built path.
1906
2176
 
1907
- // src/commands/explore.ts
1908
- import { createServer } from "http";
1909
- import { Command as Command10 } from "commander";
1910
- var int7 = (v) => Number.parseInt(v, 10);
1911
- var ROUTES = {
1912
- "GET /api/bkn/meta": (c, q) => c.kn.get(req(q, "knId")),
1913
- "POST /api/bkn/search": (c, _q, b) => c.kn.search(str(b.knId), str(b.query), {
1914
- maxConcepts: typeof b.maxConcepts === "number" ? b.maxConcepts : void 0
1915
- }),
1916
- "POST /api/bkn/instances": (c, _q, b) => c.kn.objectTypeQuery(str(b.knId), str(b.objectTypeId), b.body ?? {}),
1917
- "POST /api/bkn/subgraph": (c, _q, b) => c.kn.subgraph(str(b.knId), b.body ?? b),
1918
- "GET /api/vega/catalogs": (c) => c.vega.catalogs(),
1919
- "GET /api/vega/catalog": (c, q) => c.vega.getCatalog(req(q, "catalogId")),
1920
- "GET /api/vega/catalog-resources": (c, q) => c.vega.catalogResources(req(q, "catalogId"), q.get("category") ?? void 0),
1921
- "GET /api/vega/connector-types": (c) => c.vega.connectorTypes(),
1922
- "POST /api/vega/query": (c, _q, b) => c.resource.query(str(b.resourceId), b.options ?? {})
1923
- };
1924
- function str(v) {
1925
- return typeof v === "string" ? v : String(v ?? "");
1926
- }
1927
- function req(q, key) {
1928
- const v = q.get(key);
1929
- if (!v) throw new Error(`missing query param: ${key}`);
1930
- return v;
1931
- }
1932
- function readBody2(reqMsg) {
1933
- return new Promise((resolve2, reject) => {
1934
- let data = "";
1935
- reqMsg.on("data", (chunk) => {
1936
- data += chunk;
1937
- });
1938
- reqMsg.on("end", () => {
1939
- if (!data.trim()) return resolve2({});
1940
- try {
1941
- resolve2(JSON.parse(data));
1942
- } catch {
1943
- reject(new Error("invalid JSON body"));
1944
- }
1945
- });
1946
- reqMsg.on("error", reject);
1947
- });
1948
- }
1949
- var INDEX = `<!doctype html><meta charset="utf-8"><title>openbkn explore</title>
1950
- <h1>openbkn explore</h1>
1951
- <p>Read-only JSON endpoints for bkn + vega:</p>
1952
- <ul>${Object.keys(ROUTES).map((r) => `<li><code>${r}</code></li>`).join("")}</ul>`;
1953
- function exploreCommand() {
1954
- const cmd = new Command10("explore").description(
1955
- "Start a local web server with read-only bkn + vega JSON endpoints"
2177
+ THE SAME ID, FOUR NAMES
2178
+ An object type's id is \`concept_id\` in search-schema output, \`id\` in kn-detail and
2179
+ get_object_types, \`ot_id\` in query-object-instance arguments, and \`object_type_id\` on
2180
+ an instance row. Same value throughout \u2014 carry it across, do not look it up again.
2181
+
2182
+ RAW MCP
2183
+ tools <kn-id> lists what this deploy advertises, with each tool's input schema.
2184
+ tool-call / call-method reach anything the named commands above do not cover.`
1956
2185
  );
1957
- cmd.option("--port <n>", "port to listen on", int7, 7777).option("--host <h>", "host to bind", "127.0.0.1").action(async (opts, command) => {
1958
- const client = clientFrom(command);
1959
- const server = createServer((reqMsg, res) => {
1960
- void handle(client, reqMsg, res);
1961
- });
1962
- server.listen(opts.port, opts.host, () => {
1963
- console.error(`openbkn explore running at http://${opts.host}:${opts.port}/`);
1964
- console.error("bkn + vega read endpoints only. Press Ctrl+C to stop.");
1965
- });
1966
- });
1967
- return group(cmd, "FOUNDATION");
2186
+ return group(cmd, "DATA & KNOWLEDGE");
1968
2187
  }
1969
- async function handle(client, reqMsg, res) {
1970
- const url = new URL(reqMsg.url ?? "/", "http://localhost");
1971
- const method = reqMsg.method ?? "GET";
1972
- if (method === "GET" && url.pathname === "/") {
1973
- res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
1974
- res.end(INDEX);
1975
- return;
2188
+
2189
+ // src/commands/describe.ts
2190
+ import { Command as Command8 } from "commander";
2191
+
2192
+ // src/help/returns.json
2193
+ var returns_default = {
2194
+ note: "Top-level keys observed on a live deploy, not a contract. Regenerate with scripts/capture-returns.mjs.",
2195
+ observedAt: "2026-08-29",
2196
+ commands: {
2197
+ "auth status": ["baseUrl", "expired", "hasToken", "userId", "username"],
2198
+ "auth whoami": [
2199
+ "at_hash",
2200
+ "aud",
2201
+ "auth_time",
2202
+ "baseUrl",
2203
+ "exp",
2204
+ "iat",
2205
+ "iss",
2206
+ "jti",
2207
+ "rat",
2208
+ "sid",
2209
+ "sub",
2210
+ "userId",
2211
+ "username"
2212
+ ],
2213
+ "auth list": "array",
2214
+ "config show": ["baseUrl"],
2215
+ "appkey list": ["keys"],
2216
+ "appkey admin list": ["keys"],
2217
+ "bkn list": ["entries", "total_count"],
2218
+ "bkn get": [
2219
+ "branch",
2220
+ "business_domain",
2221
+ "color",
2222
+ "comment",
2223
+ "create_time",
2224
+ "creator",
2225
+ "icon",
2226
+ "id",
2227
+ "module_type",
2228
+ "name",
2229
+ "operations",
2230
+ "tags",
2231
+ "update_time",
2232
+ "updater"
2233
+ ],
2234
+ "bkn object-type list": ["entries", "total_count"],
2235
+ "bkn object-type get": ["entries"],
2236
+ "bkn relation-type list": ["entries", "total_count"],
2237
+ "bkn relation-type get": ["entries"],
2238
+ "bkn action-type list": ["entries", "total_count"],
2239
+ "bkn action-type get": ["entries"],
2240
+ "bkn stats": [
2241
+ "branch",
2242
+ "business_domain",
2243
+ "color",
2244
+ "comment",
2245
+ "create_time",
2246
+ "creator",
2247
+ "icon",
2248
+ "id",
2249
+ "module_type",
2250
+ "name",
2251
+ "operations",
2252
+ "statistics",
2253
+ "tags",
2254
+ "update_time",
2255
+ "updater"
2256
+ ],
2257
+ "bkn action-log list": ["entries"],
2258
+ "bkn metric list": ["entries", "total_count"],
2259
+ "bkn concept-group list": ["entries", "total_count"],
2260
+ "bkn concept-group get": [
2261
+ "branch",
2262
+ "color",
2263
+ "comment",
2264
+ "create_time",
2265
+ "creator",
2266
+ "icon",
2267
+ "id",
2268
+ "kn_id",
2269
+ "module_type",
2270
+ "name",
2271
+ "object_types",
2272
+ "relation_types",
2273
+ "tags",
2274
+ "update_time",
2275
+ "updater"
2276
+ ],
2277
+ "bkn action-schedule list": ["entries", "total_count"],
2278
+ "vega catalog list": ["entries", "total_count"],
2279
+ "vega catalog resources": ["entries", "total_count"],
2280
+ "vega catalog health": ["health_check_result", "health_check_status", "id", "last_check_time"],
2281
+ "vega discover-schedule list": ["entries", "total_count"],
2282
+ "vega discover-schedule get": [
2283
+ "catalog_id",
2284
+ "create_time",
2285
+ "creator",
2286
+ "cron_expr",
2287
+ "enabled",
2288
+ "end_time",
2289
+ "id",
2290
+ "last_run",
2291
+ "name",
2292
+ "next_run",
2293
+ "start_time",
2294
+ "strategy",
2295
+ "update_time",
2296
+ "updater"
2297
+ ],
2298
+ "vega discover-task list": ["entries", "total_count"],
2299
+ "vega discover-task get": [
2300
+ "catalog_id",
2301
+ "catalog_name",
2302
+ "create_time",
2303
+ "creator",
2304
+ "finish_time",
2305
+ "id",
2306
+ "last_progress_time",
2307
+ "message",
2308
+ "progress",
2309
+ "queue_priority",
2310
+ "result",
2311
+ "schedule_id",
2312
+ "start_time",
2313
+ "status",
2314
+ "strategy",
2315
+ "trigger_type"
2316
+ ],
2317
+ "vega semantic-task list": ["entries", "total_count"],
2318
+ "vega semantic-task get": [
2319
+ "agent_id",
2320
+ "agent_task_id",
2321
+ "applied",
2322
+ "apply_detail_json",
2323
+ "apply_mode",
2324
+ "catalog_id",
2325
+ "catalog_name",
2326
+ "confidence",
2327
+ "confidence_detail_json",
2328
+ "confidence_threshold",
2329
+ "create_time",
2330
+ "creator",
2331
+ "id",
2332
+ "input",
2333
+ "input_hash",
2334
+ "resource_id",
2335
+ "resource_name",
2336
+ "result_json",
2337
+ "scope",
2338
+ "status"
2339
+ ],
2340
+ "vega connector-type list": ["entries", "total_count"],
2341
+ "vega resource list": ["entries", "total_count"],
2342
+ "vega resource get": ["entries"],
2343
+ "resource list": ["entries", "total_count"],
2344
+ "resource get": ["entries"],
2345
+ "context kn-detail": [
2346
+ "action_types",
2347
+ "comment",
2348
+ "concept_groups",
2349
+ "id",
2350
+ "name",
2351
+ "object_types",
2352
+ "relation_types"
2353
+ ],
2354
+ "context info": [
2355
+ "auth",
2356
+ "client_config_example",
2357
+ "endpoint",
2358
+ "language",
2359
+ "protocol",
2360
+ "service",
2361
+ "supported_languages",
2362
+ "tool_count",
2363
+ "tools",
2364
+ "transport"
2365
+ ],
2366
+ "context tools": ["tools"],
2367
+ "model llm list": ["count", "data"],
2368
+ "model llm get": [
2369
+ "max_model_len",
2370
+ "model_config",
2371
+ "model_id",
2372
+ "model_name",
2373
+ "model_series",
2374
+ "model_type"
2375
+ ],
2376
+ "model small list": ["count", "data"],
2377
+ "model small get": [
2378
+ "adapter",
2379
+ "adapter_code",
2380
+ "batch_size",
2381
+ "create_time",
2382
+ "default",
2383
+ "embedding_dim",
2384
+ "max_tokens",
2385
+ "model_config",
2386
+ "model_id",
2387
+ "model_name",
2388
+ "model_type",
2389
+ "update_time"
2390
+ ],
2391
+ "model small get-default": [
2392
+ "batch_size",
2393
+ "default",
2394
+ "embedding_dim",
2395
+ "max_tokens",
2396
+ "model_config",
2397
+ "model_id",
2398
+ "model_name",
2399
+ "model_type"
2400
+ ],
2401
+ "skill list": ["data", "has_next", "has_prev", "page", "page_size", "total", "total_pages"],
2402
+ "skill get": [
2403
+ "business_domain_id",
2404
+ "category",
2405
+ "category_name",
2406
+ "create_time",
2407
+ "create_user",
2408
+ "description",
2409
+ "name",
2410
+ "skill_id",
2411
+ "source",
2412
+ "status",
2413
+ "update_time",
2414
+ "update_user",
2415
+ "version"
2416
+ ],
2417
+ "skill market": ["data", "has_next", "has_prev", "page", "page_size", "total", "total_pages"],
2418
+ "skill market-get": [
2419
+ "business_domain_id",
2420
+ "category",
2421
+ "category_name",
2422
+ "create_time",
2423
+ "create_user",
2424
+ "description",
2425
+ "name",
2426
+ "release_time",
2427
+ "release_user",
2428
+ "skill_id",
2429
+ "source",
2430
+ "status",
2431
+ "update_time",
2432
+ "update_user",
2433
+ "version"
2434
+ ],
2435
+ "skill content": ["files", "skill_id", "status", "url"],
2436
+ "skill files": ["entries", "path", "skillId", "totalFiles", "totalSize"],
2437
+ "skill names": ["entries"],
2438
+ "skill history": "array",
2439
+ "toolbox list": ["data", "has_next", "has_prev", "page", "page_size", "total", "total_pages"],
2440
+ "function deps": ["dependencies", "session_id"],
2441
+ "function template": ["code_template", "template_type"],
2442
+ "trace graph": [
2443
+ "data",
2444
+ "duration_nano",
2445
+ "page",
2446
+ "partial",
2447
+ "partial_reason",
2448
+ "status",
2449
+ "trace_id"
2450
+ ],
2451
+ "trace conversations list": ["entries"],
2452
+ "trace conversations get": [
2453
+ "agent_name",
2454
+ "conversation_id",
2455
+ "created_at",
2456
+ "creation_auth_method",
2457
+ "external_conversation_key",
2458
+ "generation",
2459
+ "one_shot",
2460
+ "owner",
2461
+ "row_version",
2462
+ "status",
2463
+ "updated_at"
2464
+ ],
2465
+ "trace interactions get": [
2466
+ "closure_manifest",
2467
+ "conversation_id",
2468
+ "created_at",
2469
+ "evidence_status",
2470
+ "execution_status",
2471
+ "interaction_id",
2472
+ "lease_epoch",
2473
+ "lease_expires_at",
2474
+ "lease_token",
2475
+ "lease_version",
2476
+ "ordinal",
2477
+ "row_version",
2478
+ "terminal_at",
2479
+ "updated_at"
2480
+ ],
2481
+ "trace operations get": [
2482
+ "attempt",
2483
+ "attempt_status",
2484
+ "conversation_id",
2485
+ "created_at",
2486
+ "interaction_id",
2487
+ "operation_id",
2488
+ "operation_key",
2489
+ "retryable",
2490
+ "row_version",
2491
+ "tool_name",
2492
+ "updated_at"
2493
+ ],
2494
+ "trace receipts get": [
2495
+ "artifact_refs",
2496
+ "attempt",
2497
+ "business_refs",
2498
+ "causation_event_ids",
2499
+ "conversation_id",
2500
+ "evidence_durability",
2501
+ "interaction_id",
2502
+ "issued_at",
2503
+ "observed_evidence_refs",
2504
+ "operation_id",
2505
+ "operation_key",
2506
+ "owner",
2507
+ "partial_reasons",
2508
+ "receipt_id",
2509
+ "receipt_status",
2510
+ "request_id",
2511
+ "required",
2512
+ "row_version",
2513
+ "schema_version",
2514
+ "terminal_at",
2515
+ "tool_name",
2516
+ "trace_id"
2517
+ ],
2518
+ "trace get": "array",
2519
+ "trace detail": ["graph", "operations", "partial", "summary"],
2520
+ "trace spans": "array",
2521
+ "trace search": [
2522
+ "entries",
2523
+ "next_cursor",
2524
+ "page",
2525
+ "page_size",
2526
+ "partial",
2527
+ "total",
2528
+ "truncated"
2529
+ ],
2530
+ "admin auth status": ["baseUrl", "expired", "hasToken", "userId", "username"],
2531
+ "admin auth whoami": [
2532
+ "at_hash",
2533
+ "aud",
2534
+ "auth_time",
2535
+ "baseUrl",
2536
+ "exp",
2537
+ "iat",
2538
+ "iss",
2539
+ "jti",
2540
+ "rat",
2541
+ "sid",
2542
+ "sub",
2543
+ "userId",
2544
+ "username"
2545
+ ],
2546
+ "admin auth list": "array",
2547
+ "admin auth export": ["accessToken", "baseUrl", "idToken", "refreshToken"],
2548
+ "admin org list": ["departments", "total"],
2549
+ "admin org get": [
2550
+ "code",
2551
+ "created_at",
2552
+ "id",
2553
+ "manager_id",
2554
+ "manager_name",
2555
+ "name",
2556
+ "parent_id",
2557
+ "type"
2558
+ ],
2559
+ "admin org members": ["total", "users"],
2560
+ "admin org tree": "array",
2561
+ "admin user list": ["total", "users"],
2562
+ "admin user get": [
2563
+ "account",
2564
+ "account_type",
2565
+ "departments",
2566
+ "email",
2567
+ "enabled",
2568
+ "id",
2569
+ "name",
2570
+ "roles",
2571
+ "telephone",
2572
+ "updated_at"
2573
+ ],
2574
+ "admin user roles": ["roles"],
2575
+ "admin role list": ["roles"],
2576
+ "admin role get": [
2577
+ "built_in",
2578
+ "created_at",
2579
+ "description",
2580
+ "id",
2581
+ "members",
2582
+ "name",
2583
+ "permissions",
2584
+ "source"
2585
+ ],
2586
+ "admin role members": ["members"],
2587
+ "admin llm list": ["count", "data"],
2588
+ "admin llm get": [
2589
+ "max_model_len",
2590
+ "model_config",
2591
+ "model_id",
2592
+ "model_name",
2593
+ "model_series",
2594
+ "model_type"
2595
+ ],
2596
+ "admin small-model list": ["count", "data"],
2597
+ "admin small-model get": [
2598
+ "adapter",
2599
+ "adapter_code",
2600
+ "batch_size",
2601
+ "create_time",
2602
+ "default",
2603
+ "embedding_dim",
2604
+ "max_tokens",
2605
+ "model_config",
2606
+ "model_id",
2607
+ "model_name",
2608
+ "model_type",
2609
+ "update_time"
2610
+ ],
2611
+ "admin license show": [
2612
+ "activated",
2613
+ "contract_expires_at",
2614
+ "customer",
2615
+ "edition",
2616
+ "expires_at",
2617
+ "features",
2618
+ "instance_fp",
2619
+ "issued_at",
2620
+ "lic_id",
2621
+ "limits",
2622
+ "state"
2623
+ ],
2624
+ "admin config show": ["baseUrl"]
1976
2625
  }
1977
- const handler = ROUTES[`${method} ${url.pathname}`];
1978
- if (!handler) {
1979
- res.writeHead(404, { "content-type": "application/json" });
1980
- res.end(JSON.stringify({ error: "not found" }));
1981
- return;
2626
+ };
2627
+
2628
+ // src/commands/probe.ts
2629
+ var SERVICE_PROBES = [
2630
+ { service: "bkn-backend", path: "/api/bkn-backend/v1/knowledge-networks?limit=1" },
2631
+ { service: "vega-backend", path: "/api/vega-backend/v1/catalogs?limit=1" },
2632
+ {
2633
+ service: "agent-operator-integration",
2634
+ path: "/api/agent-operator-integration/v1/tool-box/list?page=1&size=1"
2635
+ },
2636
+ { service: "agent-observability", path: "/api/agent-observability/v1/conversations?limit=1" },
2637
+ { service: "mf-model-manager", path: "/api/mf-model-manager/v1/llm/list" },
2638
+ { service: "safe", path: "/api/safe/v1/me/api-keys" },
2639
+ { service: "agent-retrieval", path: "/api/agent-retrieval/v1/mcp/info" }
2640
+ ];
2641
+ var COMMAND_SERVICE = [
2642
+ [/^bkn\b/, "bkn-backend"],
2643
+ [/^vega\b/, "vega-backend"],
2644
+ [/^resource\b/, "vega-backend"],
2645
+ [/^context\b/, "agent-retrieval"],
2646
+ [/^trace\b/, "agent-observability"],
2647
+ [/^(skill|toolbox|tool|function)\b/, "agent-operator-integration"],
2648
+ [/^model\b/, "mf-model-manager"],
2649
+ [/^(auth|appkey)\b/, "safe"],
2650
+ [/^admin (llm|small-model)\b/, "mf-model-manager"],
2651
+ [/^admin\b/, "safe"]
2652
+ ];
2653
+ var COMMAND_TOOL = {
2654
+ "context search-schema": "search_schema",
2655
+ "context query-object-instance": "query_object_instance",
2656
+ "context query-instance-subgraph": "query_instance_subgraph",
2657
+ "context explore-subgraph": "explore_subgraph",
2658
+ "context run-sql": "run_sql",
2659
+ "context query-metric": "query_metric",
2660
+ "context get-logic-properties": "get_logic_properties_values",
2661
+ "context get-action-info": "get_action_info",
2662
+ "context find-skills": "find_skills",
2663
+ "context kn-detail": "get_kn_detail",
2664
+ "context object-types": "get_object_types",
2665
+ "context relation-types": "get_relation_types"
2666
+ };
2667
+ async function probeDeploy(cmd, now) {
2668
+ const client = clientFrom(cmd);
2669
+ const services = {};
2670
+ for (const { service, path } of SERVICE_PROBES) {
2671
+ try {
2672
+ const res = await client.call(path);
2673
+ services[service] = res.status === 404 || res.status === 501 || res.status >= 502 ? { available: false, reason: `HTTP ${res.status} ${res.statusText}`.trim() } : { available: true };
2674
+ } catch (err) {
2675
+ services[service] = {
2676
+ available: false,
2677
+ reason: err instanceof Error ? firstLine(err.message) : "unreachable"
2678
+ };
2679
+ }
1982
2680
  }
1983
- try {
1984
- const body = method === "GET" ? {} : await readBody2(reqMsg);
1985
- const data = await handler(client, url.searchParams, body);
1986
- res.writeHead(200, { "content-type": "application/json" });
1987
- res.end(JSON.stringify(data ?? null));
1988
- } catch (err) {
1989
- res.writeHead(500, { "content-type": "application/json" });
1990
- res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
2681
+ let mcpTools = [];
2682
+ if (services["agent-retrieval"]?.available) {
2683
+ try {
2684
+ const info = await client.context.info();
2685
+ mcpTools = (info.tools ?? []).map((t) => typeof t === "string" ? t : t.name ?? "").filter(Boolean);
2686
+ } catch {
2687
+ }
1991
2688
  }
2689
+ return { baseUrl: client.ctx.baseUrl, checkedAt: now, services, mcpTools };
1992
2690
  }
1993
-
1994
- // src/commands/model.ts
1995
- import { Command as Command11 } from "commander";
1996
- var int8 = (v) => Number.parseInt(v, 10);
1997
- async function resolveLlmModelName(client, model) {
1998
- if (!/^\d+$/.test(model)) return model;
1999
- const detail = await client.models.llm.get(model);
2000
- if (!detail?.model_name) throw new InputError(`No LLM found with id ${model}.`);
2001
- return detail.model_name;
2691
+ function firstLine(message) {
2692
+ return (message.split("\n")[0] ?? message).slice(0, 160);
2002
2693
  }
2003
- function addManagementCommands(parent, kind) {
2004
- parent.command("add").description("Register a model (definition JSON via --body / --body-file)").option("--body <json>", "model definition JSON").option("--body-file <path>", "read model definition JSON from a file").action(async (opts, cmd) => {
2694
+ function availabilityOf(path, probe) {
2695
+ if (!probe) return { available: "unknown" };
2696
+ const tool = COMMAND_TOOL[path];
2697
+ if (tool && probe.mcpTools.length) {
2698
+ return probe.mcpTools.includes(tool) ? { available: true } : { available: false, reason: `this deploy's MCP server does not advertise ${tool}` };
2699
+ }
2700
+ const service = COMMAND_SERVICE.find(([re]) => re.test(path))?.[1];
2701
+ if (!service) return { available: "unknown", reason: "no single service to check" };
2702
+ const state = probe.services[service];
2703
+ if (!state) return { available: "unknown" };
2704
+ return state.available ? { available: true } : { available: false, reason: `${service}: ${state.reason ?? "unreachable"}` };
2705
+ }
2706
+
2707
+ // src/commands/describe.ts
2708
+ var DEFAULT_SECTION = "COMMANDS";
2709
+ var ID_SOURCES = {
2710
+ "kn-id": "openbkn bkn list",
2711
+ "catalog-id": "openbkn vega catalog list",
2712
+ "resource-id": "openbkn vega catalog resources <catalog-id>",
2713
+ "ot-id": 'openbkn context search-schema <kn-id> "<q>"',
2714
+ "at-id": "openbkn bkn action-type list <kn-id>",
2715
+ "metric-id": "openbkn bkn metric list <kn-id>",
2716
+ "skill-id": "openbkn skill list",
2717
+ "box-id": "openbkn toolbox list",
2718
+ "tool-id": "openbkn tool list --toolbox <box-id>",
2719
+ "conversation-id": "openbkn trace conversations list",
2720
+ "trace-id": "openbkn trace search",
2721
+ "interaction-id": "openbkn trace search",
2722
+ "operation-id": "openbkn trace interactions operations <interaction-id>",
2723
+ "ot-ids": 'openbkn context search-schema <kn-id> "<q>"',
2724
+ "object-type-id": 'openbkn context search-schema <kn-id> "<q>"',
2725
+ "execution-id": "openbkn bkn action-log list <kn-id>",
2726
+ "receipt-id": "openbkn trace interactions operations <interaction-id>",
2727
+ "conversation-ids": "openbkn trace conversations list",
2728
+ "model-ids": "openbkn model llm list",
2729
+ "tool-ids": "openbkn tool list --toolbox <box-id>",
2730
+ "document-id": "openbkn vega resource document-get <resource-id> <document-id>",
2731
+ "document-ids": "openbkn vega resource document-get <resource-id> <document-id>",
2732
+ // Named entities, wherever they appear; the overrides below cover the cases
2733
+ // where the same word means something else.
2734
+ role: "openbkn admin role list",
2735
+ user: "openbkn admin user list"
2736
+ };
2737
+ var ARGUMENT_OVERRIDES = {
2738
+ "admin role add-member|id": "openbkn admin user list",
2739
+ "admin role remove-member|id": "openbkn admin user list",
2740
+ "admin user assign-role|role": "openbkn admin role list",
2741
+ "admin user revoke-role|role": "openbkn admin role list",
2742
+ "admin user roles|user": "openbkn admin user list",
2743
+ "admin role members|role": "openbkn admin role list",
2744
+ "auth switch|user": "openbkn auth users <url>",
2745
+ "admin auth switch|user": "openbkn admin auth users <url>"
2746
+ };
2747
+ var GROUP_ID_SOURCES = [
2748
+ [/^vega catalog\b/, "openbkn vega catalog list"],
2749
+ [/^vega resource\b/, "openbkn vega catalog resources <catalog-id>"],
2750
+ [/^vega discover-schedule\b/, "openbkn vega discover-schedule list"],
2751
+ [/^vega discover-task\b/, "openbkn vega discover-task list"],
2752
+ [/^vega semantic-task\b/, "openbkn vega semantic-task list"],
2753
+ [/^vega dataset\b/, "openbkn vega dataset build-list"],
2754
+ [/^bkn action-schedule\b/, "openbkn bkn action-schedule list <kn-id>"],
2755
+ [/^vega connector-type\b/, "openbkn vega connector-type list"],
2756
+ [/^resource\b/, "openbkn resource list"],
2757
+ [/^skill\b/, "openbkn skill list"],
2758
+ [/^toolbox\b/, "openbkn toolbox list"],
2759
+ [/^tool\b/, "openbkn tool list --toolbox <box-id>"],
2760
+ [/^appkey\b/, "openbkn appkey list"],
2761
+ [/^admin org\b/, "openbkn admin org list"],
2762
+ [/^auth\b/, "openbkn auth list"],
2763
+ [/^admin user\b/, "openbkn admin user list"],
2764
+ [/^admin role\b/, "openbkn admin role list"],
2765
+ [/^admin llm\b/, "openbkn admin llm list"],
2766
+ [/^admin small-model\b/, "openbkn admin small-model list"],
2767
+ [/^model llm\b/, "openbkn model llm list"],
2768
+ [/^model small\b/, "openbkn model small list"],
2769
+ [/^bkn object-type\b/, "openbkn bkn object-type list <kn-id>"],
2770
+ [/^bkn relation-type\b/, "openbkn bkn relation-type list <kn-id>"],
2771
+ [/^bkn action-type\b/, "openbkn bkn action-type list <kn-id>"],
2772
+ [/^bkn concept-group\b/, "openbkn bkn concept-group list <kn-id>"],
2773
+ [/^bkn action-schedule\b/, "openbkn bkn action-schedule list <kn-id>"],
2774
+ [/^bkn action-log\b/, "openbkn bkn action-log list <kn-id>"]
2775
+ ];
2776
+ function sourceOf(argName, path, isLast) {
2777
+ const override = ARGUMENT_OVERRIDES[`${path.join(" ")}|${argName}`];
2778
+ if (override !== void 0) return override ?? void 0;
2779
+ const byName = ID_SOURCES[argName];
2780
+ if (byName) return byName;
2781
+ if (!isLast) return void 0;
2782
+ const generic = /^(id|ids|cg-id|modelid|role|user|schedule-id|schedule-ids|task-id|log-id)$/i.test(argName);
2783
+ if (!generic) return void 0;
2784
+ const parent = path.slice(0, -1).join(" ");
2785
+ return GROUP_ID_SOURCES.find(([re]) => re.test(parent))?.[1];
2786
+ }
2787
+ function describeOption(opt) {
2788
+ return {
2789
+ flags: opt.flags,
2790
+ description: opt.description,
2791
+ ...opt.mandatory ? { mandatory: true } : {},
2792
+ ...opt.required || opt.optional ? { takesValue: true } : {},
2793
+ ...opt.defaultValue === void 0 ? {} : { default: opt.defaultValue }
2794
+ };
2795
+ }
2796
+ var RETURN_SHAPES = returns_default.commands;
2797
+ function shapeOf(path) {
2798
+ return RETURN_SHAPES[path];
2799
+ }
2800
+ var activeProbe;
2801
+ function describeNode(cmd, parentPath, depth) {
2802
+ const path = [...parentPath, cmd.name()];
2803
+ const children = cmd.commands.filter((c) => !c.name().startsWith("help"));
2804
+ const state = activeProbe ? availabilityOf(path.join(" "), activeProbe) : void 0;
2805
+ const skeleton = {
2806
+ path: path.join(" "),
2807
+ name: cmd.name(),
2808
+ section: sectionOf(cmd),
2809
+ summary: cmd.description(),
2810
+ ...shapeOf(path.join(" ")) ? { returns: shapeOf(path.join(" ")) } : {},
2811
+ ...state ? { available: state.available } : {},
2812
+ ...state?.reason ? { unavailable: state.reason } : {}
2813
+ };
2814
+ if (depth <= 0) return children.length ? { ...skeleton, hasCommands: true } : skeleton;
2815
+ const aliases = cmd.aliases();
2816
+ const options = cmd.options.filter((o) => o.long !== "--help");
2817
+ return {
2818
+ ...skeleton,
2819
+ ...aliases.length ? { aliases } : {},
2820
+ ...cmd.registeredArguments.length ? {
2821
+ arguments: cmd.registeredArguments.map((arg, index, all) => {
2822
+ const from = sourceOf(arg.name(), path, index === all.length - 1);
2823
+ return {
2824
+ name: arg.name(),
2825
+ required: arg.required,
2826
+ variadic: arg.variadic,
2827
+ ...arg.description ? { description: arg.description } : {},
2828
+ ...from ? { from } : {}
2829
+ };
2830
+ })
2831
+ } : {},
2832
+ ...options.length ? { options: options.map(describeOption) } : {},
2833
+ ...guideOf(cmd) ? { guide: guideOf(cmd) } : {},
2834
+ ...children.length ? { commands: children.map((child) => describeNode(child, path, depth - 1)) } : {}
2835
+ };
2836
+ }
2837
+ var FIELD_MEANINGS = {
2838
+ returns: "top-level keys observed on a live deploy \u2014 evidence for parsing, not a contract; absent where nothing was recorded",
2839
+ available: "whether this deploy can answer the command (only after --probe). Checked per service and per MCP tool, so a command can still be refused for a capability the probe cannot see",
2840
+ unavailable: "why it cannot; absent when it can",
2841
+ probe: "what the probe asked and found: one read per service, plus the MCP tool catalog",
2842
+ path: "full command path \u2014 run it as `openbkn <path>`",
2843
+ section: "which section the command sits in; see `sections`",
2844
+ summary: "what the command does; often names the shape it answers with",
2845
+ hasCommands: "this walk stopped here \u2014 ask for this path to see deeper",
2846
+ arguments: "positional arguments, in order",
2847
+ "arguments[].from": "the command that hands out this argument's value",
2848
+ options: "flags; `mandatory` and `takesValue` appear only when true",
2849
+ guide: "prose the command's own --help prints under its command list",
2850
+ commands: "nested commands"
2851
+ };
2852
+ function resolve2(program2, path) {
2853
+ let node = program2;
2854
+ for (const name of path) {
2855
+ const child = node.commands.find((c) => c.name() === name || c.aliases().includes(name));
2856
+ if (!child) throw new InputError(`no such command: ${path.join(" ")}`);
2857
+ node = child;
2858
+ }
2859
+ return node;
2860
+ }
2861
+ function describeCommandTree(program2, opts = {}) {
2862
+ activeProbe = opts.probe;
2863
+ const depth = opts.depth === void 0 ? Number.MAX_SAFE_INTEGER : opts.depth - 1;
2864
+ if (opts.path?.length) {
2865
+ return {
2866
+ sections: SECTION_MEANINGS,
2867
+ fields: FIELD_MEANINGS,
2868
+ ...describeNode(resolve2(program2, opts.path), opts.path.slice(0, -1), depth + 1)
2869
+ };
2870
+ }
2871
+ const top = program2.commands.filter(
2872
+ (c) => !c.name().startsWith("help") && c.name() !== "describe"
2873
+ );
2874
+ return {
2875
+ name: program2.name(),
2876
+ version: program2.version(),
2877
+ summary: program2.description(),
2878
+ sections: SECTION_MEANINGS,
2879
+ fields: FIELD_MEANINGS,
2880
+ ...opts.probe ? { probe: opts.probe } : {},
2881
+ commands: top.map((cmd) => describeNode(cmd, [], depth)),
2882
+ globalOptions: program2.options.filter((o) => o.long !== "--help").map(describeOption),
2883
+ guide: guideOf(program2)
2884
+ };
2885
+ }
2886
+ function rows(node, indent = "") {
2887
+ const out = [
2888
+ {
2889
+ indent,
2890
+ name: `${node.path.split(" ").pop()}${node.hasCommands ? " \u2026" : ""}`,
2891
+ section: node.section === DEFAULT_SECTION ? "" : node.section,
2892
+ summary: node.available === false ? `[unavailable] ${node.unavailable ?? ""} \u2014 ${node.summary}` : node.summary
2893
+ }
2894
+ ];
2895
+ for (const child of node.commands ?? []) out.push(...rows(child, `${indent} `));
2896
+ return out;
2897
+ }
2898
+ function renderText(tree) {
2899
+ const root = tree;
2900
+ const out = [];
2901
+ if (root.sections) {
2902
+ const width = Math.max(...Object.keys(root.sections).map((k) => k.length));
2903
+ out.push("SECTIONS");
2904
+ for (const [name, meaning] of Object.entries(root.sections)) {
2905
+ out.push(` ${name.padEnd(width)} ${meaning}`);
2906
+ }
2907
+ out.push("");
2908
+ }
2909
+ const nodes = root.path ? [tree] : root.commands ?? [];
2910
+ const truncated = nodes.some(function deeper(n) {
2911
+ return Boolean(n.hasCommands) || (n.commands ?? []).some(deeper);
2912
+ });
2913
+ const table = nodes.flatMap((node) => rows(node));
2914
+ const nameWidth = Math.max(...table.map((r) => r.indent.length + r.name.length));
2915
+ const sectionWidth = Math.max(...table.map((r) => r.section.length));
2916
+ for (const r of table) {
2917
+ const name = `${r.indent}${r.name}`.padEnd(nameWidth);
2918
+ out.push(`${name} ${r.section.padEnd(sectionWidth)} ${r.summary}`.trimEnd());
2919
+ }
2920
+ if (truncated) {
2921
+ out.push("", "\u2026 has subcommands \u2014 `openbkn describe <command>`, or raise --depth");
2922
+ }
2923
+ return out.join("\n");
2924
+ }
2925
+ function describeCommand(program2) {
2926
+ const cmd = new Command8("describe").description("Print the command tree \u2014 paths, sections, flags (--json for the data)").argument("[command...]", "only this subtree, e.g. `describe bkn metric`").option(
2927
+ "--depth <n>",
2928
+ "how many levels to walk (1 = this level only)",
2929
+ (v) => Number.parseInt(v, 10)
2930
+ ).option("--pretty", "indent the JSON instead of packing it onto one line").option("--probe", "ask this deploy which commands it can answer (read-only, ~7 requests)").action(async (path, opts, self) => {
2931
+ const probe = opts.probe ? await probeDeploy(self, (/* @__PURE__ */ new Date()).toISOString()) : void 0;
2932
+ const tree = describeCommandTree(program2, { path, depth: opts.depth, probe });
2933
+ const out = outputOptions(self);
2934
+ if (!out.json && !out.compact && !opts.pretty) {
2935
+ process.stdout.write(`${renderText(tree)}
2936
+ `);
2937
+ return;
2938
+ }
2939
+ printJson(tree, { ...out, json: Boolean(opts.pretty), compact: !opts.pretty });
2940
+ });
2941
+ return group(cmd, "COMMANDS");
2942
+ }
2943
+
2944
+ // src/commands/function.ts
2945
+ import { readFileSync as readFileSync4 } from "fs";
2946
+ import { Command as Command9 } from "commander";
2947
+ var int5 = (v) => Number.parseInt(v, 10);
2948
+ function readCode(file) {
2949
+ try {
2950
+ return readFileSync4(file === "-" ? 0 : file, "utf8");
2951
+ } catch (err) {
2952
+ throw new InputError(
2953
+ `Cannot read ${file === "-" ? "stdin" : file}: ${err instanceof Error ? err.message : err}`
2954
+ );
2955
+ }
2956
+ }
2957
+ function collectDep(value, previous = []) {
2958
+ const at = value.lastIndexOf("@");
2959
+ const name = at > 0 ? value.slice(0, at) : value;
2960
+ const version = at > 0 ? value.slice(at + 1) : void 0;
2961
+ if (!name) throw new InputError("--dep takes <name> or <name>@<version>");
2962
+ return [...previous, version ? { name, version } : { name }];
2963
+ }
2964
+ function parseJsonOption(raw, label) {
2965
+ if (raw === void 0) return void 0;
2966
+ try {
2967
+ return parseBigIntJSON(raw);
2968
+ } catch {
2969
+ throw new InputError(`--${label} must be valid JSON`);
2970
+ }
2971
+ }
2972
+ function definitionFlags(c) {
2973
+ return c.option("--name <n>", "name; required when the definition is a function").option("--description <d>", "what it does \u2014 the model reads this to decide when to call it").option("--type <t>", "function | openapi", "function").option("--inputs <json>", "input parameters: [{name,type,required,description}]").option("--outputs <json>", "output parameters, same shape as --inputs").option("--dep <name@version>", "package to install before running (repeatable)", collectDep).option("--index-url <url>", "package index to install from");
2974
+ }
2975
+ function parameterList(raw, label) {
2976
+ const parsed = parseJsonOption(raw, label);
2977
+ if (parsed === void 0) return void 0;
2978
+ if (!Array.isArray(parsed)) throw new InputError(`--${label} must be a JSON array of parameters`);
2979
+ return parsed;
2980
+ }
2981
+ function functionDefinitionFrom(file, opts) {
2982
+ if (!opts.name) throw new InputError("--name is required for a function");
2983
+ return {
2984
+ name: opts.name,
2985
+ description: opts.description,
2986
+ code: readCode(file),
2987
+ inputs: parameterList(opts.inputs, "inputs"),
2988
+ outputs: parameterList(opts.outputs, "outputs"),
2989
+ dependencies: opts.dep,
2990
+ dependenciesUrl: opts.indexUrl
2991
+ };
2992
+ }
2993
+ function functionCommand() {
2994
+ const cmd = new Command9("function").description(
2995
+ "Sandbox functions: run Python on the platform without registering anything"
2996
+ );
2997
+ cmd.command("run <file>").description("Run a file (or `-` for stdin) in the sandbox; exits non-zero when the code does").option("--event <json>", "the single argument handler() receives", "{}").option("--timeout <s>", "sandbox timeout in seconds", int5).option("--dep <name@version>", "install a package first (repeatable)", collectDep).option("--index-url <url>", "package index to install from (default PyPI)").option(
2998
+ "--pass-token",
2999
+ "put your credential in the sandbox's BKN_TOKEN so `sandbox_sdk.bkn` calls BKN as you"
3000
+ ).action(async (file, opts, cmd2) => {
3001
+ const client = clientFrom(cmd2);
3002
+ const result = await client.functions.run({
3003
+ code: readCode(file),
3004
+ event: parseJsonOption(opts.event, "event") ?? {},
3005
+ timeout: opts.timeout,
3006
+ dependencies: opts.dep,
3007
+ dependenciesUrl: opts.indexUrl,
3008
+ source: "openbkn_cli",
3009
+ // The request headers carry these too, but they stop at the service:
3010
+ // the sandbox reads its own environment, which only these fields fill.
3011
+ conversationId: client.ctx.trace?.conversationId,
3012
+ interactionId: client.ctx.trace?.interactionId,
3013
+ ...opts.passToken ? { bknToken: client.ctx.token } : {}
3014
+ });
3015
+ printJson(result, outputOptions(cmd2));
3016
+ if (result.exit_code !== void 0 && result.exit_code !== 0) process.exitCode = 1;
3017
+ });
3018
+ cmd.command("infer-schema <file>").description(
3019
+ "Derive a tool contract from @tool-decorated code; runs it, answers supported:false when it cannot"
3020
+ ).action(async (file, _opts, cmd2) => {
3021
+ printJson(await clientFrom(cmd2).functions.inferSchema(readCode(file)), outputOptions(cmd2));
3022
+ });
3023
+ cmd.command("deps").description("Libraries already installed in the sandbox \u2014 import these without --dep").action(async (_opts, cmd2) => {
3024
+ printJson(await clientFrom(cmd2).functions.dependencies(), outputOptions(cmd2));
3025
+ });
3026
+ cmd.command("versions <package>").description("Versions of one package, asked of the package index live").option("--python <v>", "keep only versions compatible with this Python").option("--index-url <url>", "package index to ask (default PyPI)").action(async (pkg, opts, cmd2) => {
3027
+ printJson(
3028
+ await clientFrom(cmd2).functions.dependencyVersions(pkg, {
3029
+ pythonVersion: opts.python,
3030
+ pypiRepoUrl: opts.indexUrl
3031
+ }),
3032
+ outputOptions(cmd2)
3033
+ );
3034
+ });
3035
+ cmd.command("template").description("The handler() skeleton to start from").option("--type <t>", "template type (python)", "python").action(async (opts, cmd2) => {
3036
+ printJson(await clientFrom(cmd2).functions.template(opts.type), outputOptions(cmd2));
3037
+ });
3038
+ groupChildren(cmd, {
3039
+ READ: ["deps", "versions", "template"],
3040
+ RUN: ["run", "infer-schema"]
3041
+ });
3042
+ guide(
3043
+ cmd,
3044
+ `THE ONE HARD RULE
3045
+ The entry point must be a function named \`handler\`, taking one argument:
3046
+
3047
+ def handler(event: Dict[str, Any]) -> Any:
3048
+ return {"sum": event.get("a", 0) + event.get("b", 0)}
3049
+
3050
+ \`--event\` is that argument, the return value comes back as \`result\`, and
3051
+ \`print\` output as \`stdout\`. \`function template\` prints the skeleton.
3052
+
3053
+ READING THE ANSWER
3054
+ Code that raises still answers HTTP 200 \u2014 \`exit_code\` is the verdict and the
3055
+ traceback is in \`stderr\`. This command exits non-zero to match, so \`&&\` works.
3056
+
3057
+ CONTEXT INSIDE THE SANDBOX
3058
+ --conversation-id / --interaction-id reach the sandbox as BKN_CONVERSATION_ID
3059
+ and BKN_INTERACTION_ID, which is how \`sandbox_sdk.bkn\` hangs its own BKN calls
3060
+ under your interaction. The credential does not travel unless you say so:
3061
+ --pass-token puts it in BKN_TOKEN so that code runs as you.
3062
+
3063
+ ORDER OF WORK
3064
+ function deps what is already importable
3065
+ function run ./add.py --event ... iterate here; nothing is kept
3066
+ toolbox create --type function a box to keep it in
3067
+ tool create ./add.py --toolbox the same code, now a tool
3068
+ tool enable <tool-id> --toolbox a tool is off until enabled, then agents
3069
+ can call it`
3070
+ );
3071
+ return group(cmd, "TOOLS & SKILLS");
3072
+ }
3073
+
3074
+ // src/commands/model.ts
3075
+ import { Command as Command10 } from "commander";
3076
+ var int6 = (v) => Number.parseInt(v, 10);
3077
+ async function resolveLlmModelName(client, model) {
3078
+ if (!/^\d+$/.test(model)) return model;
3079
+ const detail = await client.models.llm.get(model);
3080
+ if (!detail?.model_name) throw new InputError(`No LLM found with id ${model}.`);
3081
+ return detail.model_name;
3082
+ }
3083
+ function addManagementCommands(parent, kind) {
3084
+ parent.command("add").description("Register a model (definition JSON via --body / --body-file)").option(
3085
+ "--body <json>",
3086
+ "model definition JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (mf-model-manager)"
3087
+ ).option("--body-file <path>", "read model definition JSON from a file").action(async (opts, cmd) => {
2005
3088
  printJson(await clientFrom(cmd).models[kind].add(readBody(opts)), outputOptions(cmd));
2006
3089
  });
2007
- parent.command("edit").description("Update a model definition (JSON via --body / --body-file)").option("--body <json>", "model definition JSON").option("--body-file <path>", "read model definition JSON from a file").action(async (opts, cmd) => {
3090
+ parent.command("edit").description("Update a model definition (JSON via --body / --body-file)").option(
3091
+ "--body <json>",
3092
+ "model definition JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (mf-model-manager)"
3093
+ ).option("--body-file <path>", "read model definition JSON from a file").action(async (opts, cmd) => {
2008
3094
  printJson(await clientFrom(cmd).models[kind].edit(readBody(opts)), outputOptions(cmd));
2009
3095
  });
2010
3096
  parent.command("delete <model-ids>").description("Delete model(s) (comma-joined ids)").action(async (ids, _o, cmd) => {
2011
3097
  printJson(await clientFrom(cmd).models[kind].delete(csv(ids) ?? []), outputOptions(cmd));
2012
3098
  });
2013
- parent.command("test").description("Test a model's connectivity / inference (JSON via --body / --body-file)").option("--body <json>", "test request JSON").option("--body-file <path>", "read test request JSON from a file").action(async (opts, cmd) => {
3099
+ parent.command("test").description("Test a model's connectivity / inference (JSON via --body / --body-file)").option(
3100
+ "--body <json>",
3101
+ "test request JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (mf-model-manager)"
3102
+ ).option("--body-file <path>", "read test request JSON from a file").action(async (opts, cmd) => {
2014
3103
  printJson(await clientFrom(cmd).models[kind].test(readBody(opts)), outputOptions(cmd));
2015
3104
  });
2016
3105
  }
2017
3106
  function modelCommand() {
2018
- const model = new Command11("model").description(
2019
- "Model factory \u2014 LLM / small-model CRUD, chat / embeddings / rerank, default selection"
3107
+ const model = new Command10("model").description(
3108
+ "Large and small models: chat, embeddings, rerank, defaults"
2020
3109
  );
2021
3110
  const llm = model.command("llm").description("Large language models");
2022
- llm.command("list").description("List LLM models").option("--name <s>", "filter by name").option("--type <t>", "model type filter").option("--limit <n>", "page size", int8, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int8, 1).action(async (opts, cmd) => {
3111
+ llm.command("list").description("List LLM models \u2192 {data, count}").option("--name <s>", "filter by name").option("--type <t>", "model type filter").option("--limit <n>", "page size", int6, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int6, 1).action(async (opts, cmd) => {
2023
3112
  printJson(
2024
3113
  await clientFrom(cmd).models.llm.list({
2025
3114
  name: opts.name,
@@ -2052,7 +3141,7 @@ function modelCommand() {
2052
3141
  });
2053
3142
  addManagementCommands(llm, "llm");
2054
3143
  const small = model.command("small").description("Small models (embedding / reranker)");
2055
- small.command("list").description("List small models").option("--name <s>", "filter by name").option("--type <t>", "model type filter").option("--limit <n>", "page size", int8, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int8, 1).action(async (opts, cmd) => {
3144
+ small.command("list").description("List small models \u2192 {data, count}").option("--name <s>", "filter by name").option("--type <t>", "model type filter").option("--limit <n>", "page size", int6, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int6, 1).action(async (opts, cmd) => {
2056
3145
  printJson(
2057
3146
  await clientFrom(cmd).models.small.list({
2058
3147
  name: opts.name,
@@ -2066,13 +3155,13 @@ function modelCommand() {
2066
3155
  small.command("get <modelId>").description("Get a small model").action(async (id, _opts, cmd) => {
2067
3156
  printJson(await clientFrom(cmd).models.small.get(id), outputOptions(cmd));
2068
3157
  });
2069
- small.command("embeddings <modelId>").description("Compute embeddings").requiredOption("-i, --input <text>", "comma-separated input texts").action(async (id, opts, cmd) => {
3158
+ small.command("embeddings <model>").description("Compute embeddings (<model> = model name or numeric id)").requiredOption("-i, --input <text>", "comma-separated input texts").action(async (id, opts, cmd) => {
2070
3159
  printJson(
2071
3160
  await clientFrom(cmd).models.small.embeddings(id, csv(opts.input) ?? []),
2072
3161
  outputOptions(cmd)
2073
3162
  );
2074
3163
  });
2075
- small.command("rerank <modelId>").description("Rerank documents against a query").requiredOption("-q, --query <text>", "query").requiredOption("-d, --documents <list>", "comma-separated documents").action(async (id, opts, cmd) => {
3164
+ small.command("rerank <model>").description("Rerank documents against a query (<model> = model name or numeric id)").requiredOption("-q, --query <text>", "query").requiredOption("-d, --documents <list>", "comma-separated documents").action(async (id, opts, cmd) => {
2076
3165
  printJson(
2077
3166
  await clientFrom(cmd).models.small.rerank(id, opts.query, csv(opts.documents) ?? []),
2078
3167
  outputOptions(cmd)
@@ -2103,42 +3192,38 @@ Examples:
2103
3192
  $ openbkn model small get-default --type embedding # current default
2104
3193
  $ openbkn model small set-default <id> # default embedding/reranker`
2105
3194
  );
2106
- return group(model, "MODELS & SKILLS");
3195
+ for (const kind of [llm, small]) {
3196
+ groupChildren(kind, {
3197
+ READ: ["list", "get", "get-default"],
3198
+ RUN: ["chat", "embeddings", "rerank", "test"],
3199
+ WRITE: ["add", "edit", "delete", "set-default", "unset-default"]
3200
+ });
3201
+ }
3202
+ return group(model, "MODELS");
2107
3203
  }
2108
3204
 
2109
3205
  // src/commands/resource.ts
2110
- import { Command as Command12 } from "commander";
2111
- var int9 = (v) => Number.parseInt(v, 10);
2112
- var parsePairs = (raw) => {
2113
- if (!raw) return void 0;
2114
- return raw.split(",").map((part) => {
2115
- const idx = part.indexOf("=");
2116
- if (idx < 1) throw new Error("--extension must be key=value[,key=value]");
2117
- return { key: part.slice(0, idx).trim(), value: part.slice(idx + 1).trim() };
2118
- }).filter((p) => p.key.length > 0);
2119
- };
3206
+ import { Command as Command11 } from "commander";
3207
+ var int7 = (v) => Number.parseInt(v, 10);
2120
3208
  function resourceCommand() {
2121
- const cmd = new Command12("resource").alias("res").description("Resources \u2014 list, find, get, query, delete");
2122
- cmd.command("list").description("List resources under a catalog").option("--catalog-id <id>", "filter by catalog id").option("--datasource-id <id>", "alias of --catalog-id").option("--category <c>", "resource category (table | logicview | dataset)").option("--type <c>", "alias of --category").option("--status <status>", "filter by status").option("--database <name>", "filter by database").option("--limit <n>", "page size", int9, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int9, 0).option("--include-extensions", "include all extension key/value pairs").option("--include-extension-keys <keys>", "include selected extension keys").option("--extension <k=v,...>", "filter by extension key/value pairs").option("--sort <field>", "sort field: name | create_time | update_time").option("--direction <dir>", "sort direction: asc | desc").action(async (opts, cmd2) => {
3209
+ const cmd = new Command11("resource").alias("res").description("Tables and views behind a network: find, inspect, sample, enable, disable");
3210
+ cmd.command("list").description("List resources under a catalog").option("--catalog-id <id>", "filter by catalog id").option("--category <c>", "resource category (table | logicview | dataset)").option("--type <c>", "alias of --category").option("--status <status>", "filter by status").option("--schema <name>", "filter by source schema").option("--limit <n>", "page size", int7, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int7, 0).option("--sort <field>", "sort field: name | create_time | update_time").option("--direction <dir>", "sort direction: asc | desc").action(async (opts, cmd2) => {
2123
3211
  const data = await clientFrom(cmd2).resource.list({
2124
- datasourceId: opts.catalogId ?? opts.datasourceId,
3212
+ catalogId: opts.catalogId,
2125
3213
  category: opts.category ?? opts.type,
2126
3214
  status: opts.status,
2127
- database: opts.database,
3215
+ schema: opts.schema,
2128
3216
  limit: opts.limit,
2129
3217
  offset: opts.offset,
2130
- includeExtensions: opts.includeExtensions,
2131
- includeExtensionKeys: opts.includeExtensionKeys,
2132
- extensionPairs: parsePairs(opts.extension),
2133
3218
  sort: opts.sort,
2134
3219
  direction: opts.direction
2135
3220
  });
2136
3221
  printJson(data, outputOptions(cmd2));
2137
3222
  });
2138
- cmd.command("find").description("Search resources by name (fuzzy; --exact for strict)").requiredOption("--name <name>", "resource name to search").option("--exact", "exact name match").option("--catalog-id <id>", "limit to a catalog").option("--datasource-id <id>", "alias of --catalog-id").option("--limit <n>", "rows to scan before filtering", int9, DEFAULT_LIST_LIMIT).action(async (opts, cmd2) => {
3223
+ cmd.command("find").description("Search resources by name, fuzzy unless --exact \u2192 a bare array, not an envelope").requiredOption("--name <name>", "resource name to search").option("--exact", "exact name match").option("--catalog-id <id>", "limit to a catalog").option("--limit <n>", "rows to scan before filtering", int7, DEFAULT_LIST_LIMIT).action(async (opts, cmd2) => {
2139
3224
  const data = await clientFrom(cmd2).resource.find(opts.name, {
2140
3225
  exact: opts.exact,
2141
- datasourceId: opts.catalogId ?? opts.datasourceId,
3226
+ catalogId: opts.catalogId,
2142
3227
  limit: opts.limit
2143
3228
  });
2144
3229
  printJson(data, outputOptions(cmd2));
@@ -2146,7 +3231,14 @@ function resourceCommand() {
2146
3231
  cmd.command("get <id>").description("Get resource details").action(async (id, _opts, cmd2) => {
2147
3232
  printJson(await clientFrom(cmd2).resource.get(id), outputOptions(cmd2));
2148
3233
  });
2149
- cmd.command("query <id>").description("Fetch data rows from a resource").option("--limit <n>", "row limit", int9, DEFAULT_QUERY_LIMIT).option("--offset <n>", "row offset", int9, 0).option("--paging-mode <mode>", "paging mode: single | cursor").option("--keep-alive-sec <s>", "cursor keep-alive in seconds (60\u20133600)", int9).option("--cursor <cursor>", "opaque cursor returned by the previous page").option("--need-total", "include total count").action(async (id, opts, cmd2) => {
3234
+ for (const action of ["enable", "disable"]) {
3235
+ cmd.command(`${action} <id>`).description(`${action[0]?.toUpperCase()}${action.slice(1)} a resource`).action(async (id, _opts, cmd2) => {
3236
+ const api = clientFrom(cmd2).resource;
3237
+ const result = action === "enable" ? await api.enable(id) : await api.disable(id);
3238
+ printJson(result, outputOptions(cmd2));
3239
+ });
3240
+ }
3241
+ cmd.command("query <id>").description("Fetch data rows from a resource").option("--limit <n>", "row limit", int7, DEFAULT_QUERY_LIMIT).option("--offset <n>", "row offset", int7, 0).option("--paging-mode <mode>", "paging mode: single | cursor").option("--keep-alive-sec <s>", "cursor keep-alive in seconds (60\u20133600)", int7).option("--cursor <cursor>", "opaque cursor returned by the previous page").option("--need-total", "include total count").action(async (id, opts, cmd2) => {
2150
3242
  const data = await clientFrom(cmd2).resource.query(id, {
2151
3243
  limit: opts.limit,
2152
3244
  offset: opts.offset,
@@ -2160,12 +3252,17 @@ function resourceCommand() {
2160
3252
  cmd.command("delete <id>").description("Delete a resource").option("-y, --yes", "skip confirmation").action(async (id, _opts, cmd2) => {
2161
3253
  printJson(await clientFrom(cmd2).resource.delete(id), outputOptions(cmd2));
2162
3254
  });
2163
- return group(cmd, "AI DATA PLATFORM");
3255
+ groupChildren(cmd, {
3256
+ READ: ["list", "find", "get"],
3257
+ RUN: ["query"],
3258
+ WRITE: ["enable", "disable", "delete"]
3259
+ });
3260
+ return group(cmd, "DATA & KNOWLEDGE");
2164
3261
  }
2165
3262
 
2166
3263
  // src/commands/skill.ts
2167
- import { Command as Command13 } from "commander";
2168
- var int10 = (v) => Number.parseInt(v, 10);
3264
+ import { Command as Command12 } from "commander";
3265
+ var int8 = (v) => Number.parseInt(v, 10);
2169
3266
  var positiveInt = (flag) => (v) => {
2170
3267
  if (!/^\d+$/.test(v)) {
2171
3268
  throw new InputError(`${flag} must be a positive integer (got '${v}')`);
@@ -2190,9 +3287,15 @@ function checkSource(source) {
2190
3287
  }
2191
3288
  var draftOption = (c) => c.option("--draft", "read the draft (management) version instead of the published one");
2192
3289
  function skillCommand() {
2193
- const cmd = new Command13("skill").description("Skill registry and market");
2194
- const listOpts = (c) => c.option("--name <s>", "filter by name").option("--source <s>", "filter by source").option("--status <s>", "filter by status").option("--limit <n>", "page size", int10, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int10, 1);
2195
- listOpts(cmd.command("list").description("List skills")).option("--create-user <s>", "filter by creator").action(async (opts, cmd2) => {
3290
+ const cmd = new Command12("skill").description(
3291
+ "Skill packages (SKILL.md + files) agents load on demand"
3292
+ );
3293
+ const listOpts = (c) => c.option("--name <s>", "filter by name").option("--source <s>", "filter by source").option("--status <s>", "filter by status").option("--limit <n>", "page size", int8, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int8, 1);
3294
+ listOpts(
3295
+ cmd.command("list").description(
3296
+ "List skills \u2192 {data, total, page, page_size, has_next}; the id to reuse is `skill_id`"
3297
+ )
3298
+ ).option("--create-user <s>", "filter by creator").action(async (opts, cmd2) => {
2196
3299
  printJson(
2197
3300
  await clientFrom(cmd2).skills.list({
2198
3301
  name: opts.name,
@@ -2208,19 +3311,19 @@ function skillCommand() {
2208
3311
  cmd.command("get <skill-id>").description("Get a skill by id").action(async (id, _opts, cmd2) => {
2209
3312
  printJson(await clientFrom(cmd2).skills.get(id), outputOptions(cmd2));
2210
3313
  });
2211
- listOpts(cmd.command("market").description("Browse the skill market")).action(
2212
- async (opts, cmd2) => {
2213
- printJson(
2214
- await clientFrom(cmd2).skills.market({
2215
- name: opts.name,
2216
- source: opts.source,
2217
- pageSize: opts.limit,
2218
- page: opts.page
2219
- }),
2220
- outputOptions(cmd2)
2221
- );
2222
- }
2223
- );
3314
+ listOpts(
3315
+ cmd.command("market").description("Browse the skill market \u2192 {data, total, page, has_next}")
3316
+ ).action(async (opts, cmd2) => {
3317
+ printJson(
3318
+ await clientFrom(cmd2).skills.market({
3319
+ name: opts.name,
3320
+ source: opts.source,
3321
+ pageSize: opts.limit,
3322
+ page: opts.page
3323
+ }),
3324
+ outputOptions(cmd2)
3325
+ );
3326
+ });
2224
3327
  cmd.command("market-get <skill-id>").description("Get a market skill by id").action(async (id, _opts, cmd2) => {
2225
3328
  printJson(await clientFrom(cmd2).skills.marketGet(id), outputOptions(cmd2));
2226
3329
  });
@@ -2329,7 +3432,7 @@ ${files.length} files, ${bytes} B
2329
3432
  );
2330
3433
  });
2331
3434
  cmd.command("register <directory>").description("Zip a local skill directory and register it").option("--source <s>", `source tag: ${SKILL_SOURCES.join(" | ")}`, "custom").option("--extend-info <json>", "extra metadata as JSON").action(async (dir, opts, cmd2) => {
2332
- const extendInfo = opts.extendInfo ? JSON.parse(opts.extendInfo) : void 0;
3435
+ const extendInfo = opts.extendInfo ? parseBigIntJSON(opts.extendInfo) : void 0;
2333
3436
  printJson(
2334
3437
  await clientFrom(cmd2).skills.register(dir, {
2335
3438
  source: checkSource(opts.source),
@@ -2347,7 +3450,10 @@ ${files.length} files, ${bytes} B
2347
3450
  cmd.command("install <skill-id> [directory]").description("Download a skill archive and extract it locally").action(async (skillId, dir, _o, cmd2) => {
2348
3451
  printJson(await clientFrom(cmd2).skills.install(skillId, dir), outputOptions(cmd2));
2349
3452
  });
2350
- cmd.command("update-metadata <skill-id>").description("Update a skill's metadata (--body / --body-file JSON)").option("--body <json>", "metadata JSON").option("--body-file <path>", "read metadata JSON from a file").action(async (skillId, opts, cmd2) => {
3453
+ cmd.command("update-metadata <skill-id>").description("Update a skill's metadata (--body / --body-file JSON)").option(
3454
+ "--body <json>",
3455
+ "metadata JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (execution-factory)"
3456
+ ).option("--body-file <path>", "read metadata JSON from a file").action(async (skillId, opts, cmd2) => {
2351
3457
  printJson(
2352
3458
  await clientFrom(cmd2).skills.updateMetadata(skillId, readBody(opts)),
2353
3459
  outputOptions(cmd2)
@@ -2365,15 +3471,53 @@ ${files.length} files, ${bytes} B
2365
3471
  outputOptions(cmd2)
2366
3472
  );
2367
3473
  });
2368
- return group(cmd, "MODELS & SKILLS");
3474
+ groupChildren(cmd, {
3475
+ READ: [
3476
+ "list",
3477
+ "market",
3478
+ "get",
3479
+ "market-get",
3480
+ "names",
3481
+ "content",
3482
+ "read-file",
3483
+ "files",
3484
+ "history"
3485
+ ],
3486
+ RUN: ["execute", "download", "install"],
3487
+ WRITE: [
3488
+ "register",
3489
+ "update-metadata",
3490
+ "update-package",
3491
+ "set-status",
3492
+ "republish",
3493
+ "publish-history",
3494
+ "delete"
3495
+ ]
3496
+ });
3497
+ guide(
3498
+ cmd,
3499
+ `READING A SKILL
3500
+ content <id> gives the SKILL.md index; files <id> [path] walks the package; read-file
3501
+ pulls one file. Read progressively \u2014 do not download the whole archive to answer a question.
3502
+
3503
+ PUBLISHED VS DRAFT
3504
+ Read commands return the published version. --draft reads the editing copy instead, which
3505
+ is what a Studio user sees. The two differ whenever changes are unpublished.
3506
+
3507
+ AUTHORING
3508
+ register <dir> zips and registers; update-package replaces the files; update-metadata
3509
+ changes only the metadata. set-status and republish move versions around.`
3510
+ );
3511
+ return group(cmd, "TOOLS & SKILLS");
2369
3512
  }
2370
3513
 
2371
3514
  // src/commands/toolbox.ts
2372
- import { Command as Command14 } from "commander";
2373
- var int11 = (v) => Number.parseInt(v, 10);
3515
+ import { Command as Command13 } from "commander";
3516
+ import yaml from "js-yaml";
3517
+ var int9 = (v) => Number.parseInt(v, 10);
2374
3518
  function toolboxCommand() {
2375
- const cmd = new Command14("toolbox").description("Agent toolbox lifecycle");
2376
- cmd.command("list").description("List toolboxes").option("--keyword <s>", "filter by keyword").option("--limit <n>", "page size", int11, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int11, 0).action(async (opts, cmd2) => {
3519
+ const cmd = new Command13("toolbox").description("Toolboxes: group tools into one publishable box");
3520
+ cmd.command("list").description("List toolboxes").option("--keyword <s>", "filter by keyword").option("--limit <n>", "page size", int9, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int9, 0).action(async (opts, cmd2) => {
2377
3521
  printJson(
2378
3522
  await clientFrom(cmd2).toolboxes.list({
2379
3523
  keyword: opts.keyword,
@@ -2383,12 +3527,21 @@ function toolboxCommand() {
2383
3527
  outputOptions(cmd2)
2384
3528
  );
2385
3529
  });
2386
- cmd.command("create").description("Create a toolbox").requiredOption("--name <name>", "toolbox name").requiredOption("--service-url <url>", "tool service URL").option("--description <d>", "description").action(async (opts, cmd2) => {
3530
+ cmd.command("create").description(
3531
+ "Create a toolbox \u2014 openapi proxies to a service, function holds platform functions"
3532
+ ).requiredOption("--name <name>", "toolbox name").option("--service-url <url>", "where an openapi box proxies its tools; required for that type").option("--type <t>", "openapi | function", "openapi").option("--description <d>", "description").action(async (opts, cmd2) => {
3533
+ if (opts.type !== "openapi" && opts.type !== "function") {
3534
+ throw new InputError("--type must be openapi or function");
3535
+ }
3536
+ if (opts.type === "openapi" && !opts.serviceUrl) {
3537
+ throw new InputError("--service-url is required for an openapi toolbox");
3538
+ }
2387
3539
  printJson(
2388
3540
  await clientFrom(cmd2).toolboxes.create({
2389
3541
  name: opts.name,
2390
3542
  serviceUrl: opts.serviceUrl,
2391
- description: opts.description
3543
+ description: opts.description,
3544
+ metadataType: opts.type
2392
3545
  }),
2393
3546
  outputOptions(cmd2)
2394
3547
  );
@@ -2411,11 +3564,36 @@ function toolboxCommand() {
2411
3564
  cmd.command("import <file>").description("Import a toolbox config from a local .adp file").option("--type <t>", "impex type: toolbox | mcp | operator", "toolbox").action(async (file, opts, cmd2) => {
2412
3565
  printJson(await clientFrom(cmd2).toolboxes.import(file, opts.type), outputOptions(cmd2));
2413
3566
  });
2414
- return group(cmd, "DECISION AGENT");
3567
+ groupChildren(cmd, {
3568
+ READ: ["list", "export"],
3569
+ WRITE: ["create", "publish", "unpublish", "delete", "import"]
3570
+ });
3571
+ guide(
3572
+ cmd,
3573
+ `TWO KINDS OF BOX
3574
+ --type openapi its tools proxy to --service-url; they come from a spec
3575
+ --type function its tools are platform functions, no service URL to give
3576
+
3577
+ ORDER OF WORK
3578
+ toolbox create --name "<n>" an empty box, in draft
3579
+ tool create ./add.py --toolbox a function tool, or --type openapi for a spec
3580
+ tool enable <tool-ids...> a tool is off until enabled
3581
+ toolbox publish <box-id> the box becomes visible in the market
3582
+ tool execute <tool-id> call an enabled tool
3583
+ tool debug <tool-id> call one that is not, while building it
3584
+
3585
+ Publishing the box is about the market, not about calling: an enabled tool in
3586
+ an unpublished box executes. \`tool enable\` is the gate.
3587
+
3588
+ export / import move a whole box between deploys as an .adp file.`
3589
+ );
3590
+ return group(cmd, "TOOLS & SKILLS");
2415
3591
  }
2416
3592
  function toolCommand() {
2417
- const cmd = new Command14("tool").description("Tools inside a toolbox");
2418
- cmd.command("list").description("List tools in a toolbox").requiredOption("--toolbox <box-id>", "toolbox id").option("--limit <n>", "page size (backend default 10, max 100)", int11).option("--page <n>", "page (1-based; backend default 1)", int11).option("--all", "return every tool, ignoring page size").action(async (opts, cmd2) => {
3593
+ const cmd = new Command13("tool").description(
3594
+ "Tools in a box: add one from code or a spec, enable it, call it"
3595
+ );
3596
+ cmd.command("list").description("List tools in a toolbox").requiredOption("--toolbox <box-id>", "toolbox id").option("--limit <n>", "page size (backend default 10, max 100)", int9).option("--page <n>", "page (1-based; backend default 1)", int9).option("--all", "return every tool, ignoring page size").action(async (opts, cmd2) => {
2419
3597
  printJson(
2420
3598
  await clientFrom(cmd2).toolboxes.tools(opts.toolbox, {
2421
3599
  page: opts.page,
@@ -2437,55 +3615,120 @@ function toolCommand() {
2437
3615
  outputOptions(cmd2)
2438
3616
  );
2439
3617
  });
2440
- const invokeOpts = (c) => c.requiredOption("--toolbox <box-id>", "toolbox id").option("--body <json>", "request body JSON").option("--header <json>", "headers map JSON").option("--query <json>", "query params JSON").option("--path <json>", "path params JSON").option("--timeout <s>", "per-call timeout seconds", int11);
3618
+ const invokeOpts = (c) => c.requiredOption("--toolbox <box-id>", "toolbox id").option(
3619
+ "--body <json>",
3620
+ "request body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (execution-factory)"
3621
+ ).option("--header <json>", "headers map JSON").option("--query <json>", "query params JSON").option("--path <json>", "path params JSON").option("--timeout <s>", "per-call timeout seconds", int9);
2441
3622
  const parseJson = (s, label) => {
2442
3623
  if (!s) return void 0;
2443
3624
  try {
2444
- return JSON.parse(s);
3625
+ return parseBigIntJSON(s);
2445
3626
  } catch {
2446
3627
  throw new InputError(`--${label} must be valid JSON`);
2447
3628
  }
2448
3629
  };
2449
3630
  const buildEnvelope = (opts) => ({
2450
- body: opts.body ? JSON.parse(opts.body) : void 0,
3631
+ body: opts.body ? parseBigIntJSON(opts.body) : void 0,
2451
3632
  header: parseJson(opts.header, "header"),
2452
3633
  query: parseJson(opts.query, "query"),
2453
3634
  path: parseJson(opts.path, "path"),
2454
3635
  timeout: opts.timeout ? Number(opts.timeout) : void 0
2455
3636
  });
3637
+ invokeOpts(cmd.command("execute <tool-id>").description("Invoke an enabled tool")).action(
3638
+ async (toolId, opts, cmd2) => {
3639
+ printJson(
3640
+ await clientFrom(cmd2).toolboxes.execute(opts.toolbox, toolId, buildEnvelope(opts)),
3641
+ outputOptions(cmd2)
3642
+ );
3643
+ }
3644
+ );
2456
3645
  invokeOpts(
2457
- cmd.command("execute <tool-id>").description("Invoke a published+enabled tool")
3646
+ cmd.command("debug <tool-id>").description("Invoke a tool that is not enabled yet")
2458
3647
  ).action(async (toolId, opts, cmd2) => {
2459
3648
  printJson(
2460
- await clientFrom(cmd2).toolboxes.execute(opts.toolbox, toolId, buildEnvelope(opts)),
3649
+ await clientFrom(cmd2).toolboxes.debug(opts.toolbox, toolId, buildEnvelope(opts)),
2461
3650
  outputOptions(cmd2)
2462
3651
  );
2463
3652
  });
2464
- invokeOpts(
2465
- cmd.command("debug <tool-id>").description("Invoke a tool (draft/disabled too)")
2466
- ).action(async (toolId, opts, cmd2) => {
3653
+ const toolFrom = (file, opts) => {
3654
+ if (opts.type === "openapi") {
3655
+ let data;
3656
+ try {
3657
+ data = yaml.load(readCode(file));
3658
+ } catch (err) {
3659
+ throw new InputError(
3660
+ `${file} is not valid JSON or YAML: ${err instanceof Error ? err.message : err}`
3661
+ );
3662
+ }
3663
+ return { metadataType: "openapi", data, useRule: opts.useRule };
3664
+ }
3665
+ if (opts.type !== "function") throw new InputError("--type must be function or openapi");
3666
+ return {
3667
+ metadataType: "function",
3668
+ function: functionDefinitionFrom(file, opts),
3669
+ useRule: opts.useRule
3670
+ };
3671
+ };
3672
+ definitionFlags(
3673
+ cmd.command("create <file>").description(
3674
+ "Create a tool from code (or a spec) \u2014 the only way to add a function tool to a box"
3675
+ ).requiredOption("--toolbox <box-id>", "target toolbox id").option("--use-rule <s>", "usage rule carried onto the tool")
3676
+ ).action(async (file, opts, cmd2) => {
3677
+ const result = await clientFrom(cmd2).toolboxes.createTool(
3678
+ opts.toolbox,
3679
+ toolFrom(file, opts)
3680
+ );
3681
+ printJson(result, outputOptions(cmd2));
3682
+ if (result?.failure_count) process.exitCode = 1;
3683
+ });
3684
+ cmd.command("get <tool-id>").description("One tool in full: metadata, parameters, usage rule").requiredOption("--toolbox <box-id>", "toolbox id").action(async (toolId, opts, cmd2) => {
3685
+ printJson(await clientFrom(cmd2).toolboxes.getTool(opts.toolbox, toolId), outputOptions(cmd2));
3686
+ });
3687
+ definitionFlags(
3688
+ cmd.command("update <tool-id> <file>").description("Replace a tool's definition; the id survives and an enabled tool stays enabled").requiredOption("--toolbox <box-id>", "toolbox id").option("--use-rule <s>", "usage rule carried onto the tool")
3689
+ ).action(async (toolId, file, opts, cmd2) => {
3690
+ if (!opts.name || !opts.description) {
3691
+ throw new InputError(
3692
+ "--name and --description are required: update replaces the tool, it does not patch it"
3693
+ );
3694
+ }
2467
3695
  printJson(
2468
- await clientFrom(cmd2).toolboxes.debug(opts.toolbox, toolId, buildEnvelope(opts)),
3696
+ await clientFrom(cmd2).toolboxes.updateTool(opts.toolbox, toolId, {
3697
+ ...toolFrom(file, opts),
3698
+ name: opts.name,
3699
+ description: opts.description
3700
+ }),
3701
+ outputOptions(cmd2)
3702
+ );
3703
+ });
3704
+ cmd.command("delete <tool-ids...>").description("Delete tools from a toolbox").requiredOption("--toolbox <box-id>", "toolbox id").option("-y, --yes", "skip confirmation").action(async (toolIds, opts, cmd2) => {
3705
+ printJson(
3706
+ await clientFrom(cmd2).toolboxes.deleteTools(opts.toolbox, toolIds),
2469
3707
  outputOptions(cmd2)
2470
3708
  );
2471
3709
  });
2472
- cmd.command("upload <file>").description("Upload a tool definition file (OpenAPI spec) into a toolbox").requiredOption("--toolbox <id>", "target toolbox id").option("--metadata-type <t>", "metadata type", "openapi").action(async (file, opts, cmd2) => {
3710
+ cmd.command("upload <file>").description("Add tools from an OpenAPI file \u2014 `tool create` is the same endpoint, as JSON").requiredOption("--toolbox <id>", "target toolbox id").option("--metadata-type <t>", "metadata type", "openapi").action(async (file, opts, cmd2) => {
2473
3711
  printJson(
2474
3712
  await clientFrom(cmd2).toolboxes.upload(opts.toolbox, file, opts.metadataType),
2475
3713
  outputOptions(cmd2)
2476
3714
  );
2477
3715
  });
2478
- return group(cmd, "DECISION AGENT");
3716
+ groupChildren(cmd, {
3717
+ READ: ["list", "get"],
3718
+ RUN: ["execute", "debug"],
3719
+ WRITE: ["create", "update", "delete", "enable", "disable", "upload"]
3720
+ });
3721
+ return group(cmd, "TOOLS & SKILLS");
2479
3722
  }
2480
3723
 
2481
3724
  // src/commands/trace.ts
2482
- import { readFileSync as readFileSync5, writeFileSync } from "fs";
2483
- import { Command as Command15 } from "commander";
3725
+ import { readFileSync as readFileSync6, writeFileSync } from "fs";
3726
+ import { Command as Command14 } from "commander";
2484
3727
 
2485
3728
  // src/bkn-trace/schema-validate.ts
2486
- import { readFileSync as readFileSync4 } from "fs";
3729
+ import { readFileSync as readFileSync5 } from "fs";
2487
3730
  import { extname } from "path";
2488
- import yaml from "js-yaml";
3731
+ import yaml2 from "js-yaml";
2489
3732
  import { z } from "zod";
2490
3733
  var Assertion = z.object({
2491
3734
  type: z.enum([
@@ -2520,10 +3763,10 @@ var DiagnosisRule = z.object({
2520
3763
  params: z.record(z.string(), z.unknown()).optional()
2521
3764
  });
2522
3765
  function parseFile(file) {
2523
- const text = readFileSync4(file, "utf8");
3766
+ const text = readFileSync5(file, "utf8");
2524
3767
  const ext = extname(file).toLowerCase();
2525
- if (ext === ".yaml" || ext === ".yml") return yaml.load(text);
2526
- return JSON.parse(text);
3768
+ if (ext === ".yaml" || ext === ".yml") return yaml2.load(text);
3769
+ return parseBigIntJSON(text);
2527
3770
  }
2528
3771
  function inferKind(data) {
2529
3772
  if (data && typeof data === "object" && !Array.isArray(data)) {
@@ -2557,9 +3800,44 @@ function validateSchemaFile(file, kind) {
2557
3800
  }
2558
3801
 
2559
3802
  // src/commands/trace.ts
3803
+ function renderPayload(payload) {
3804
+ if (!payload) return "-";
3805
+ if (payload.mode === "inline") return stringifyBigIntJSON(payload.inline);
3806
+ if (payload.mode === "referenced") return `[referenced] ${payload.ref ?? "-"}`;
3807
+ return `[omitted] ${payload.omitted_reason ?? "unknown"}`;
3808
+ }
3809
+ function renderTechnicalTraceDetail(detail) {
3810
+ const lines = [
3811
+ `Trace: ${detail.summary.trace_id}`,
3812
+ `Status: ${detail.summary.status}`,
3813
+ `Request: ${detail.summary.request_id || "-"}`,
3814
+ `Question: ${detail.summary.question_preview || "-"}`,
3815
+ `Result: ${detail.summary.result_preview || "-"}`,
3816
+ `Service: ${detail.summary.root_service || "-"}`,
3817
+ `Spans: ${detail.graph?.data.nodes.length ?? 0}`
3818
+ ];
3819
+ if (detail.partial) {
3820
+ lines.push(`Partial: ${(detail.partial_reasons ?? []).join(", ") || "yes"}`);
3821
+ }
3822
+ for (const operation of detail.operations) {
3823
+ lines.push(
3824
+ "",
3825
+ `${operation.fact.tool_name} \xB7 ${operation.fact.operation_id} \xB7 attempt ${operation.fact.attempt} \xB7 ${operation.state}`,
3826
+ `Source: ${operation.fact.protocol}/${operation.fact.source_module}`,
3827
+ `Input: ${renderPayload(operation.fact.input)}`
3828
+ );
3829
+ if (operation.fact.output) lines.push(`Output: ${renderPayload(operation.fact.output)}`);
3830
+ if (operation.fact.error) lines.push(`Error: ${renderPayload(operation.fact.error)}`);
3831
+ if (operation.partial_reasons?.length) {
3832
+ lines.push(`Partial: ${operation.partial_reasons.join(", ")}`);
3833
+ }
3834
+ }
3835
+ return `${lines.join("\n")}
3836
+ `;
3837
+ }
2560
3838
  function traceCommand() {
2561
- const cmd = new Command15("trace").description(
2562
- "BKN Trace \u2014 fetch spans, diagnose (symbolic + LLM rubric), scan, eval-set, schema validate"
3839
+ const cmd = new Command14("trace").description(
3840
+ "Inspect what an agent actually did, and diagnose bad answers"
2563
3841
  );
2564
3842
  cmd.command("graph <trace-id>").description("Fetch normalized trace graph by trace id").action(async (traceId, _opts, cmd2) => {
2565
3843
  printJson(await clientFrom(cmd2).trace.graph(traceId), outputOptions(cmd2));
@@ -2633,8 +3911,17 @@ function traceCommand() {
2633
3911
  outputOptions(cmd2)
2634
3912
  );
2635
3913
  });
3914
+ interactions.command("operations <interaction-id>").description("List the exact Operation call facts for one interaction").action(async (interactionId, _opts, cmd2) => {
3915
+ printJson(
3916
+ await clientFrom(cmd2).trace.lifecycle.listInteractionOperations(interactionId),
3917
+ outputOptions(cmd2)
3918
+ );
3919
+ });
2636
3920
  for (const action of ["complete", "fail", "cancel", "handoff"]) {
2637
- interactions.command(`${action} <interaction-id>`).description(`${action} a managed interaction using a 3.0 completion manifest`).requiredOption("--body-file <path>", "read completion manifest JSON from a protected file").action(async (interactionId, opts, cmd2) => {
3921
+ interactions.command(`${action} <interaction-id>`).description(`${action} a managed interaction using a 3.0 completion manifest`).requiredOption(
3922
+ "--body-file <path>",
3923
+ "read completion manifest JSON from a protected file \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (agent-observability)"
3924
+ ).action(async (interactionId, opts, cmd2) => {
2638
3925
  const input = readBody(opts);
2639
3926
  const lifecycle = clientFrom(cmd2).trace.lifecycle;
2640
3927
  const terminal = {
@@ -2653,7 +3940,20 @@ function traceCommand() {
2653
3940
  outputOptions(cmd2)
2654
3941
  );
2655
3942
  });
2656
- operations.command("retry <operation-id>").description("Create the next retry attempt for an eligible failed operation").requiredOption("--body-file <path>", "read retry request JSON from a protected file").action(async (operationId, opts, cmd2) => {
3943
+ operations.command("attempt <operation-id> <attempt>").description("Get one exact Operation attempt call fact").action(async (operationId, attempt, _opts, cmd2) => {
3944
+ if (!/^[1-9]\d*$/.test(attempt)) {
3945
+ throw new InputError("attempt must be a positive integer");
3946
+ }
3947
+ const ordinal = Number.parseInt(attempt, 10);
3948
+ printJson(
3949
+ await clientFrom(cmd2).trace.lifecycle.getOperationAttempt(operationId, ordinal),
3950
+ outputOptions(cmd2)
3951
+ );
3952
+ });
3953
+ operations.command("retry <operation-id>").description("Create the next retry attempt for an eligible failed operation").requiredOption(
3954
+ "--body-file <path>",
3955
+ "read retry request JSON from a protected file \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (agent-observability)"
3956
+ ).action(async (operationId, opts, cmd2) => {
2657
3957
  printJson(
2658
3958
  await clientFrom(cmd2).trace.lifecycle.retryOperationAttempt(
2659
3959
  operationId,
@@ -2666,14 +3966,41 @@ function traceCommand() {
2666
3966
  receipts.command("get <receipt-id>").description("Get an authorized operation receipt").action(async (receiptId, _opts, cmd2) => {
2667
3967
  printJson(await clientFrom(cmd2).trace.lifecycle.getReceipt(receiptId), outputOptions(cmd2));
2668
3968
  });
2669
- cmd.command("get <conversation-id>").description("Fetch all trace spans for a conversation").option("--max-spans <n>", "max spans", (v) => Number.parseInt(v, 10)).action(async (conversationId, opts, cmd2) => {
3969
+ cmd.command("get <conversation-id>").description("Fetch normalized spans for a conversation").option("--max-spans <n>", "max spans", (v) => Number.parseInt(v, 10)).action(async (conversationId, opts, cmd2) => {
3970
+ printJson(
3971
+ await clientFrom(cmd2).trace.spans(conversationId, { maxSpans: opts.maxSpans }),
3972
+ outputOptions(cmd2)
3973
+ );
3974
+ });
3975
+ cmd.command("detail <trace-id>").description("Get one typed technical trace with Span and Operation facts").action(async (traceId, _opts, cmd2) => {
3976
+ const detail = await clientFrom(cmd2).trace.get(traceId);
3977
+ const output = outputOptions(cmd2);
3978
+ if (output.json || output.compact) printJson(detail, output);
3979
+ else process.stdout.write(renderTechnicalTraceDetail(detail));
3980
+ });
3981
+ cmd.command("spans <conversation-id>").description("Fetch normalized spans for a conversation").option("--max-spans <n>", "max spans", (v) => Number.parseInt(v, 10)).action(async (conversationId, opts, cmd2) => {
2670
3982
  printJson(
2671
3983
  await clientFrom(cmd2).trace.spans(conversationId, { maxSpans: opts.maxSpans }),
2672
3984
  outputOptions(cmd2)
2673
3985
  );
2674
3986
  });
2675
- cmd.command("search").description("Raw trace search (--body / --body-file OpenSearch JSON)").option("--body <json>", "search body JSON").option("--body-file <path>", "read search body JSON from a file").action(async (opts, cmd2) => {
2676
- printJson(await clientFrom(cmd2).trace.search(readBody(opts)), outputOptions(cmd2));
3987
+ cmd.command("search").description("List authorized technical traces \u2192 {entries, total, next_cursor, partial}").option("--limit <n>", "page size, 1..200", (value) => Number.parseInt(value, 10)).option("--cursor <cursor>", "opaque pagination cursor").option("--from <time>", "started at or after this RFC3339 timestamp").option("--to <time>", "started at or before this RFC3339 timestamp").option("--status <status>", "execution status").option("--service <service>", "exact producing service").option("--tool <tool>", "exact root tool").option("--trace-id <id>", "exact Trace ID").option("--error-keyword <text>", "case-insensitive error text").option("--conversation-id <id>", "exact conversation ID").option("--interaction-id <id>", "exact interaction ID").action(async (opts, cmd2) => {
3988
+ printJson(
3989
+ await clientFrom(cmd2).trace.search({
3990
+ limit: opts.limit,
3991
+ cursor: opts.cursor,
3992
+ from: opts.from,
3993
+ to: opts.to,
3994
+ status: opts.status,
3995
+ service: opts.service,
3996
+ tool: opts.tool,
3997
+ traceId: opts.traceId,
3998
+ errorKeyword: opts.errorKeyword,
3999
+ conversationId: opts.conversationId,
4000
+ interactionId: opts.interactionId
4001
+ }),
4002
+ outputOptions(cmd2)
4003
+ );
2677
4004
  });
2678
4005
  cmd.command("diagnose <conversation-id>").description("Diagnose a conversation's trace (symbolic rules; --llm adds rubric judging)").option("--llm", "also run LLM-judged rubric rules via the local `claude` CLI").action(async (conversationId, opts, cmd2) => {
2679
4006
  const report = await clientFrom(cmd2).trace.diagnose(conversationId, {
@@ -2690,27 +4017,17 @@ function traceCommand() {
2690
4017
  outputOptions(cmd2)
2691
4018
  );
2692
4019
  });
2693
- const evalSet = cmd.command("eval-set").description("Build + run trace eval sets");
4020
+ const evalSet = cmd.command("eval-set").description("Build trace eval sets");
2694
4021
  evalSet.command("build <queries-file>").description("Build eval cases from a queries JSON file").option("--out <file>", "write the cases JSON here (default: stdout)").action(async (queriesFile, opts, cmd2) => {
2695
- const raw = JSON.parse(readFileSync5(queriesFile, "utf8"));
4022
+ const raw = parseBigIntJSON(readFileSync6(queriesFile, "utf8"));
2696
4023
  const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
2697
4024
  if (opts.out) {
2698
- writeFileSync(opts.out, JSON.stringify({ cases }, null, 2));
4025
+ writeFileSync(opts.out, stringifyBigIntJSON({ cases }, 2));
2699
4026
  printJson({ out: opts.out, cases: cases.length }, outputOptions(cmd2));
2700
4027
  } else {
2701
4028
  printJson({ cases }, outputOptions(cmd2));
2702
4029
  }
2703
4030
  });
2704
- evalSet.command("test <cases-file>").description("Run an eval set against an agent (--llm enables semantic_match)").requiredOption("--agent <id>", "agent id to run the queries against").option("--version <v>", "agent version", "v0").option("--llm", "enable semantic_match assertions via the local `claude` CLI").action(async (casesFile, opts, cmd2) => {
2705
- const raw = JSON.parse(readFileSync5(casesFile, "utf8"));
2706
- const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
2707
- const result = await clientFrom(cmd2).trace.evalSetTest(opts.agent, cases, {
2708
- version: opts.version,
2709
- llm: Boolean(opts.llm)
2710
- });
2711
- printJson(result, outputOptions(cmd2));
2712
- if (result.failed > 0) process.exitCode = 1;
2713
- });
2714
4031
  const schema = cmd.command("schema").description("Validate eval-set / diagnosis-rule files");
2715
4032
  schema.command("validate <file>").description("Validate an eval-set or diagnosis-rule file (JSON/YAML) against its schema").option("--kind <k>", "force schema kind: eval-set | rule (default: auto-detect)").action(async (file, opts, cmd2) => {
2716
4033
  const result = validateSchemaFile(file, opts.kind);
@@ -2722,12 +4039,53 @@ function traceCommand() {
2722
4039
  printJson(result, outputOptions(cmd2));
2723
4040
  if (!result.ok) process.exitCode = 1;
2724
4041
  });
2725
- return group(cmd, "TRACE AI");
4042
+ groupChildren(cmd, {
4043
+ GROUPS: ["conversations", "interactions", "operations", "receipts", "eval-set", "schema"],
4044
+ READ: ["graph", "get", "search", "detail", "spans"],
4045
+ RUN: ["diagnose", "scan", "validate-fixture"]
4046
+ });
4047
+ guide(
4048
+ cmd,
4049
+ `FINDING A CONVERSATION
4050
+ conversations list gives conversation ids; get <conversation-id> pulls its spans.
4051
+
4052
+ DIAGNOSING
4053
+ diagnose <conversation-id> symbolic rules only, no model needed
4054
+ diagnose <conversation-id> --llm adds rubric judging + a synthesized summary; needs a
4055
+ local \`claude\` CLI on PATH, silently degrades without it
4056
+ scan <id,id,...> the same over several conversations, aggregated
4057
+
4058
+ MANAGED LIFECYCLE
4059
+ conversations / interactions / operations / receipts are the write side: an agent opens an
4060
+ interaction, reports operations, then completes it. Those bodies are 3.0 manifests,
4061
+ documented at https://openbkn-ai.github.io/bkn-foundry/ (agent-observability).`
4062
+ );
4063
+ return group(cmd, "TRACING");
2726
4064
  }
2727
4065
 
2728
4066
  // src/commands/vega.ts
2729
- import { Command as Command16 } from "commander";
2730
- var int12 = (v) => Number.parseInt(v, 10);
4067
+ import { Command as Command15 } from "commander";
4068
+ var int10 = (value) => {
4069
+ const parsed = Number(value);
4070
+ if (!Number.isSafeInteger(parsed)) {
4071
+ throw new InputError(`expected an integer, received "${value}"`);
4072
+ }
4073
+ return parsed;
4074
+ };
4075
+ var expectedUpdateTime = (value) => {
4076
+ const parsed = Number(value);
4077
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
4078
+ throw new InputError("--expected-update-time must be a positive integer timestamp");
4079
+ }
4080
+ return parsed;
4081
+ };
4082
+ var confidenceThreshold = (value) => {
4083
+ const parsed = Number(value);
4084
+ if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
4085
+ throw new InputError("--confidence-threshold must be a number between 0 and 1");
4086
+ }
4087
+ return parsed;
4088
+ };
2731
4089
  var bool = (value) => {
2732
4090
  if (value === "true") return true;
2733
4091
  if (value === "false") return false;
@@ -2736,7 +4094,7 @@ var bool = (value) => {
2736
4094
  var parseJsonObject = (value, flag) => {
2737
4095
  let parsed;
2738
4096
  try {
2739
- parsed = JSON.parse(value);
4097
+ parsed = parseBigIntJSON(value);
2740
4098
  } catch {
2741
4099
  throw new InputError(`${flag} must be valid JSON`);
2742
4100
  }
@@ -2745,10 +4103,15 @@ var parseJsonObject = (value, flag) => {
2745
4103
  }
2746
4104
  return parsed;
2747
4105
  };
2748
- var parseStringRecord = (value, flag) => {
2749
- const parsed = parseJsonObject(value, flag);
2750
- if (Object.values(parsed).some((item) => typeof item !== "string")) {
2751
- throw new InputError(`${flag} values must be strings`);
4106
+ var parseJsonArray = (value, flag) => {
4107
+ let parsed;
4108
+ try {
4109
+ parsed = parseBigIntJSON(value);
4110
+ } catch {
4111
+ throw new InputError(`${flag} must be valid JSON`);
4112
+ }
4113
+ if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== "object" || item === null || Array.isArray(item))) {
4114
+ throw new InputError(`${flag} must be a JSON array of objects`);
2752
4115
  }
2753
4116
  return parsed;
2754
4117
  };
@@ -2771,20 +4134,143 @@ var healthCheckSchedule = (mode, cronExpr) => {
2771
4134
  }
2772
4135
  throw new InputError("health-check mode must be inherit, enabled, or disabled");
2773
4136
  };
2774
- var parsePairs2 = (raw) => {
2775
- if (!raw) return void 0;
2776
- return raw.split(",").map((part) => {
2777
- const idx = part.indexOf("=");
2778
- if (idx < 1) throw new InputError("--extension must be key=value[,key=value]");
2779
- return { key: part.slice(0, idx).trim(), value: part.slice(idx + 1).trim() };
2780
- }).filter((p) => p.key.length > 0);
4137
+ var buildTaskStatuses = (raw) => {
4138
+ if (raw === void 0) return void 0;
4139
+ const statuses = csv(raw);
4140
+ if (!statuses?.length) {
4141
+ throw new InputError("--status must include at least one build status");
4142
+ }
4143
+ return statuses.map((status2) => {
4144
+ const parsed = BuildTaskStatus.safeParse(status2);
4145
+ if (!parsed.success) {
4146
+ throw new InputError(
4147
+ `invalid build status "${status2}"; expected one of ${BuildTaskStatus.options.join(", ")}`
4148
+ );
4149
+ }
4150
+ return parsed.data;
4151
+ });
4152
+ };
4153
+ var buildTaskSort = (raw) => {
4154
+ if (raw === void 0) return void 0;
4155
+ const parsed = BuildTaskSort.safeParse(raw);
4156
+ if (!parsed.success) {
4157
+ throw new InputError(
4158
+ `invalid build task sort "${raw}"; expected one of ${BuildTaskSort.options.join(", ")}`
4159
+ );
4160
+ }
4161
+ return parsed.data;
4162
+ };
4163
+ var buildTaskExecuteType = (raw) => {
4164
+ if (raw === void 0) return void 0;
4165
+ const parsed = BuildTaskExecuteType.safeParse(raw);
4166
+ if (!parsed.success) {
4167
+ throw new InputError(
4168
+ `invalid build task execute type "${raw}"; expected one of ${BuildTaskExecuteType.options.join(", ")}`
4169
+ );
4170
+ }
4171
+ return parsed.data;
4172
+ };
4173
+ var discoverStrategy = (raw) => {
4174
+ if (raw === void 0) return void 0;
4175
+ const parsed = DiscoverStrategy.safeParse(raw);
4176
+ if (!parsed.success) {
4177
+ throw new InputError(
4178
+ `invalid discover strategy "${raw}"; expected one of ${DiscoverStrategy.options.join(", ")}`
4179
+ );
4180
+ }
4181
+ return parsed.data;
4182
+ };
4183
+ var requiredDiscoverStrategy = (raw) => discoverStrategy(raw);
4184
+ var taskStatuses = (raw) => {
4185
+ if (raw === void 0) return void 0;
4186
+ const values = csv(raw);
4187
+ if (!values?.length) throw new InputError("--status must include at least one task status");
4188
+ return values.map((value) => {
4189
+ const parsed = VegaTaskStatus.safeParse(value);
4190
+ if (!parsed.success) {
4191
+ throw new InputError(
4192
+ `invalid task status "${value}"; expected one of ${VegaTaskStatus.options.join(", ")}`
4193
+ );
4194
+ }
4195
+ return parsed.data;
4196
+ });
4197
+ };
4198
+ var semanticScope = (raw) => {
4199
+ if (raw === void 0) return void 0;
4200
+ const parsed = SemanticUnderstandingScope.safeParse(raw);
4201
+ if (!parsed.success) {
4202
+ throw new InputError(
4203
+ `invalid semantic task scope "${raw}"; expected one of ${SemanticUnderstandingScope.options.join(", ")}`
4204
+ );
4205
+ }
4206
+ return parsed.data;
4207
+ };
4208
+ var semanticApplyMode = (raw) => {
4209
+ if (raw === void 0) return void 0;
4210
+ const parsed = SemanticUnderstandingApplyMode.safeParse(raw);
4211
+ if (!parsed.success) {
4212
+ throw new InputError(
4213
+ `invalid semantic apply mode "${raw}"; expected one of ${SemanticUnderstandingApplyMode.options.join(", ")}`
4214
+ );
4215
+ }
4216
+ return parsed.data;
4217
+ };
4218
+ var discoverScheduleSort = (raw) => {
4219
+ if (raw === void 0) return void 0;
4220
+ const parsed = DiscoverScheduleSort.safeParse(raw);
4221
+ if (!parsed.success) {
4222
+ throw new InputError(
4223
+ `invalid discover schedule sort "${raw}"; expected one of ${DiscoverScheduleSort.options.join(", ")}`
4224
+ );
4225
+ }
4226
+ return parsed.data;
4227
+ };
4228
+ var discoverTaskSort = (raw) => {
4229
+ if (raw === void 0) return void 0;
4230
+ const parsed = DiscoverTaskSort.safeParse(raw);
4231
+ if (!parsed.success) {
4232
+ throw new InputError(
4233
+ `invalid discover task sort "${raw}"; expected one of ${DiscoverTaskSort.options.join(", ")}`
4234
+ );
4235
+ }
4236
+ return parsed.data;
4237
+ };
4238
+ var discoverTaskTriggerType = (raw) => {
4239
+ if (raw === void 0) return void 0;
4240
+ const parsed = DiscoverTaskTriggerType.safeParse(raw);
4241
+ if (!parsed.success) {
4242
+ throw new InputError(
4243
+ `invalid discover task trigger type "${raw}"; expected one of ${DiscoverTaskTriggerType.options.join(", ")}`
4244
+ );
4245
+ }
4246
+ return parsed.data;
4247
+ };
4248
+ var semanticTaskSort = (raw) => {
4249
+ if (raw === void 0) return void 0;
4250
+ const parsed = SemanticUnderstandingTaskSort.safeParse(raw);
4251
+ if (!parsed.success) {
4252
+ throw new InputError(
4253
+ `invalid semantic task sort "${raw}"; expected one of ${SemanticUnderstandingTaskSort.options.join(", ")}`
4254
+ );
4255
+ }
4256
+ return parsed.data;
4257
+ };
4258
+ var sortDirection = (raw) => {
4259
+ if (raw === void 0) return void 0;
4260
+ const parsed = SortDirection.safeParse(raw);
4261
+ if (!parsed.success) {
4262
+ throw new InputError(
4263
+ `invalid sort direction "${raw}"; expected one of ${SortDirection.options.join(", ")}`
4264
+ );
4265
+ }
4266
+ return parsed.data;
2781
4267
  };
2782
4268
  function vegaCommand() {
2783
- const vega = new Command16("vega").description(
2784
- "Vega observability \u2014 catalog, resources, index build tasks"
4269
+ const vega = new Command15("vega").description(
4270
+ "Data sources: catalogs, connectors, SQL, index builds"
2785
4271
  );
2786
4272
  const catalog = vega.command("catalog").description("Catalog entries");
2787
- catalog.command("list").description("List catalog entries").option("--limit <n>", "page size", (v) => Number.parseInt(v, 10), DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", (v) => Number.parseInt(v, 10), 0).option("--name <s>", "filter by name").option("--tag <s>", "filter by tag").option("--type <type>", "filter by catalog type: physical | logical").option("--enabled <bool>", "filter by enabled state").option("--health-check-status <s>", "filter by health status").option("--include-extensions", "include all extension key/value pairs").option("--include-extension-keys <keys>", "include selected extension keys").option("--extension <k=v,...>", "filter by extension key/value pairs").option("--sort <field>", "sort field: name | create_time | update_time").option("--direction <dir>", "sort direction: asc | desc").action(async (_opts, cmd) => {
4273
+ catalog.command("list").description("List catalog entries").option("--limit <n>", "page size", int10, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int10, 0).option("--name <s>", "filter by name").option("--tag <s>", "filter by tag").option("--type <type>", "filter by catalog type: physical | logical").option("--connector-type <type>", "filter by connector type").option("--enabled <bool>", "filter by enabled state").option("--health-check-status <s>", "filter by health status").option("--sort <field>", "sort field: name | create_time | update_time").option("--direction <dir>", "sort direction: asc | desc").action(async (_opts, cmd) => {
2788
4274
  const o = cmd.optsWithGlobals();
2789
4275
  const data = await clientFrom(cmd).vega.catalogs({
2790
4276
  limit: o.limit,
@@ -2792,11 +4278,9 @@ function vegaCommand() {
2792
4278
  name: o.name,
2793
4279
  tag: o.tag,
2794
4280
  type: o.type,
4281
+ connectorType: o.connectorType,
2795
4282
  enabled: o.enabled === void 0 ? void 0 : o.enabled === "true",
2796
4283
  healthCheckStatus: o.healthCheckStatus,
2797
- includeExtensions: o.includeExtensions,
2798
- includeExtensionKeys: o.includeExtensionKeys,
2799
- extensionPairs: parsePairs2(o.extension),
2800
4284
  sort: o.sort,
2801
4285
  direction: o.direction
2802
4286
  });
@@ -2805,7 +4289,7 @@ function vegaCommand() {
2805
4289
  catalog.command("get <id>").description("Get a catalog by id").action(async (id, _opts, cmd) => {
2806
4290
  printJson(await clientFrom(cmd).vega.getCatalog(id), outputOptions(cmd));
2807
4291
  });
2808
- catalog.command("resources <id>").description("List resources under a catalog").option("--category <c>", "filter by category (e.g. table)").option("--limit <n>", "page size (backend default 20, max 1000; -1 = all)", int12).option("--offset <n>", "page offset", int12, 0).action(async (id, opts, cmd) => {
4292
+ catalog.command("resources <id>").description("List resources under a catalog").option("--category <c>", "filter by category (e.g. table)").option("--limit <n>", "page size (default 30, max 1000; -1 = all)", int10).option("--offset <n>", "page offset", int10, 0).action(async (id, opts, cmd) => {
2809
4293
  printJson(
2810
4294
  await clientFrom(cmd).vega.catalogResources(id, opts.category, opts.limit, opts.offset),
2811
4295
  outputOptions(cmd)
@@ -2814,9 +4298,8 @@ function vegaCommand() {
2814
4298
  catalog.command("health <id>").description("Health-status for a catalog").action(async (id, _opts, cmd) => {
2815
4299
  printJson(await clientFrom(cmd).vega.catalogHealth(id), outputOptions(cmd));
2816
4300
  });
2817
- catalog.command("create").description("Create a catalog (data source)").requiredOption("--name <s>", "catalog name").requiredOption("--connector-type <s>", "connector type (e.g. mysql)").requiredOption("--connector-config <json>", "connector config JSON").option("--id <id>", "explicit catalog id").option("--tags <t1,t2>", "comma-separated tags").option("--description <s>", "description").option("--enabled", "create enabled (default: disabled)").option("--internal", "create an internal catalog").option("--extensions <json>", "extension key/value JSON object").option("--allow-unhealthy", "save the catalog when its connection test fails").option("--health-check-mode <mode>", "health schedule: inherit | enabled | disabled").option("--health-check-cron <expr>", "cron expression for enabled health checks").action(async (opts, cmd) => {
4301
+ catalog.command("create").description("Create a catalog (data source)").requiredOption("--name <s>", "catalog name").requiredOption("--connector-type <s>", "connector type (e.g. mysql)").requiredOption("--connector-config <json>", "connector config JSON").option("--id <id>", "explicit catalog id").option("--tags <t1,t2>", "comma-separated tags").option("--description <s>", "description").option("--enabled", "create enabled (default: disabled)").option("--internal", "create an internal catalog").option("--allow-unhealthy", "save the catalog when its connection test fails").option("--health-check-mode <mode>", "health schedule: inherit | enabled | disabled").option("--health-check-cron <expr>", "cron expression for enabled health checks").action(async (opts, cmd) => {
2818
4302
  const connectorConfig = parseJsonObject(opts.connectorConfig, "--connector-config");
2819
- const extensions = opts.extensions ? parseStringRecord(opts.extensions, "--extensions") : void 0;
2820
4303
  printJson(
2821
4304
  await clientFrom(cmd).vega.createCatalog(
2822
4305
  {
@@ -2828,7 +4311,6 @@ function vegaCommand() {
2828
4311
  description: opts.description,
2829
4312
  enabled: opts.enabled ? true : void 0,
2830
4313
  internal: opts.internal ? true : void 0,
2831
- extensions,
2832
4314
  healthCheckSchedule: healthCheckSchedule(opts.healthCheckMode, opts.healthCheckCron)
2833
4315
  },
2834
4316
  { allowUnhealthy: opts.allowUnhealthy ? true : void 0 }
@@ -2836,9 +4318,12 @@ function vegaCommand() {
2836
4318
  outputOptions(cmd)
2837
4319
  );
2838
4320
  });
2839
- catalog.command("update <id>").description("Fully update a catalog").requiredOption("--name <s>", "catalog name").requiredOption("--connector-type <s>", "connector type").requiredOption("--enabled <bool>", "current enabled state", bool).option("--connector-config <json>", "connector config JSON").option("--tags <t1,t2>", "comma-separated tags").option("--description <s>", "description").option("--extensions <json>", "extension key/value JSON object").option("--allow-unhealthy", "save the update when its connection test fails").action(async (id, opts, cmd) => {
4321
+ catalog.command("update <id>").description("Fully update a catalog").requiredOption("--name <s>", "catalog name").requiredOption("--connector-type <s>", "connector type").requiredOption("--enabled <bool>", "current enabled state", bool).option("--connector-config <json>", "connector config JSON").option("--tags <t1,t2>", "comma-separated tags").option("--description <s>", "description").requiredOption(
4322
+ "--expected-update-time <ms>",
4323
+ "optimistic-lock update time",
4324
+ expectedUpdateTime
4325
+ ).option("--allow-unhealthy", "save the update when its connection test fails").action(async (id, opts, cmd) => {
2840
4326
  const connectorConfig = opts.connectorConfig ? parseJsonObject(opts.connectorConfig, "--connector-config") : void 0;
2841
- const extensions = opts.extensions ? parseStringRecord(opts.extensions, "--extensions") : void 0;
2842
4327
  printJson(
2843
4328
  await clientFrom(cmd).vega.updateCatalog(
2844
4329
  id,
@@ -2849,7 +4334,7 @@ function vegaCommand() {
2849
4334
  tags: opts.tags ? String(opts.tags).split(",").map((t) => t.trim()).filter(Boolean) : void 0,
2850
4335
  description: opts.description,
2851
4336
  enabled: opts.enabled,
2852
- extensions
4337
+ expectedUpdateTime: opts.expectedUpdateTime
2853
4338
  },
2854
4339
  { allowUnhealthy: opts.allowUnhealthy ? true : void 0 }
2855
4340
  ),
@@ -2862,8 +4347,9 @@ function vegaCommand() {
2862
4347
  catalog.command("disable <id>").description("Disable a catalog").action(async (id, _opts, cmd) => {
2863
4348
  printJson(await clientFrom(cmd).vega.disableCatalog(id), outputOptions(cmd));
2864
4349
  });
2865
- catalog.command("delete <id>").description("Delete a catalog").action(async (id, _opts, cmd) => {
2866
- printJson(await clientFrom(cmd).vega.deleteCatalog(id), outputOptions(cmd));
4350
+ catalog.command("delete <id>").description("Delete a catalog").option("--dry-run", "preview deletion impact without changing data").action(async (id, opts, cmd) => {
4351
+ const result = opts.dryRun ? await clientFrom(cmd).vega.deleteCatalog(id, { dryRun: true }) : await clientFrom(cmd).vega.deleteCatalog(id);
4352
+ printJson(result, outputOptions(cmd));
2867
4353
  });
2868
4354
  catalog.command("test-connection <id>").description("Test a catalog connection").action(async (id, _opts, cmd) => {
2869
4355
  printJson(await clientFrom(cmd).vega.testCatalogConnection(id), outputOptions(cmd));
@@ -2880,17 +4366,183 @@ function vegaCommand() {
2880
4366
  catalog.command("health-check-schedule <id>").description("Get a catalog health-check schedule").action(async (id, _opts, cmd) => {
2881
4367
  printJson(await clientFrom(cmd).vega.catalogHealthCheckSchedule(id), outputOptions(cmd));
2882
4368
  });
2883
- catalog.command("set-health-check-schedule <id>").description("Update a catalog health-check schedule").requiredOption("--mode <mode>", "health schedule: inherit | enabled | disabled").option("--cron <expr>", "cron expression for enabled health checks").action(async (id, opts, cmd) => {
4369
+ catalog.command("set-health-check-schedule <id>").description("Update a catalog health-check schedule").requiredOption("--mode <mode>", "health schedule: inherit | enabled | disabled").option("--cron <expr>", "cron expression for enabled health checks").requiredOption(
4370
+ "--expected-update-time <ms>",
4371
+ "optimistic-lock update time",
4372
+ expectedUpdateTime
4373
+ ).action(async (id, opts, cmd) => {
2884
4374
  const schedule = healthCheckSchedule(opts.mode, opts.cron);
2885
4375
  if (!schedule) throw new InputError("--mode is required");
2886
4376
  printJson(
2887
- await clientFrom(cmd).vega.updateCatalogHealthCheckSchedule(id, schedule),
4377
+ await clientFrom(cmd).vega.updateCatalogHealthCheckSchedule(id, {
4378
+ ...schedule,
4379
+ expectedUpdateTime: opts.expectedUpdateTime
4380
+ }),
4381
+ outputOptions(cmd)
4382
+ );
4383
+ });
4384
+ catalog.command("discover <id>").description("Trigger catalog resource discovery").option("--strategy <strategy>", `strategy: ${DiscoverStrategy.options.join(" | ")}`).action(async (id, opts, cmd) => {
4385
+ printJson(
4386
+ await clientFrom(cmd).vega.discoverCatalog(id, {
4387
+ strategy: discoverStrategy(opts.strategy)
4388
+ }),
4389
+ outputOptions(cmd)
4390
+ );
4391
+ });
4392
+ const discoverSchedule = vega.command("discover-schedule").description("Resource discovery schedules");
4393
+ discoverSchedule.command("list").description("List discovery schedules").option("--name <s>", "filter by name").option("--catalog-id <id>", "filter by catalog id").option("--enabled <bool>", "filter by enabled state", bool).option("--limit <n>", "page size", int10, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int10, 0).option("--sort <field>", "name | create_time | update_time | next_run").option("--direction <dir>", "asc | desc").action(async (opts, cmd) => {
4394
+ printJson(
4395
+ await clientFrom(cmd).vega.discoverSchedules({
4396
+ name: opts.name,
4397
+ catalogId: opts.catalogId,
4398
+ enabled: opts.enabled,
4399
+ limit: opts.limit,
4400
+ offset: opts.offset,
4401
+ sort: discoverScheduleSort(opts.sort),
4402
+ direction: sortDirection(opts.direction)
4403
+ }),
4404
+ outputOptions(cmd)
4405
+ );
4406
+ });
4407
+ discoverSchedule.command("get <id>").description("Get a discovery schedule").action(async (id, _opts, cmd) => {
4408
+ printJson(await clientFrom(cmd).vega.getDiscoverSchedule(id), outputOptions(cmd));
4409
+ });
4410
+ discoverSchedule.command("create").description("Create a discovery schedule").requiredOption("--name <s>", "schedule name").requiredOption("--catalog-id <id>", "catalog id").requiredOption("--cron <expr>", "five-field cron expression").option("--start-time <ms>", "start time", int10).option("--end-time <ms>", "end time", int10).option("--enabled", "create enabled").option("--strategy <strategy>", `strategy: ${DiscoverStrategy.options.join(" | ")}`).action(async (opts, cmd) => {
4411
+ printJson(
4412
+ await clientFrom(cmd).vega.createDiscoverSchedule({
4413
+ name: opts.name,
4414
+ catalogId: opts.catalogId,
4415
+ cronExpr: opts.cron,
4416
+ startTime: opts.startTime,
4417
+ endTime: opts.endTime,
4418
+ enabled: opts.enabled ? true : void 0,
4419
+ strategy: discoverStrategy(opts.strategy)
4420
+ }),
4421
+ outputOptions(cmd)
4422
+ );
4423
+ });
4424
+ discoverSchedule.command("update <id>").description("Fully update a discovery schedule").requiredOption("--name <s>", "schedule name").requiredOption("--catalog-id <id>", "current catalog id").requiredOption("--cron <expr>", "five-field cron expression").requiredOption("--enabled <bool>", "current enabled state", bool).requiredOption("--start-time <ms>", "start time (0 = no lower bound)", int10).requiredOption("--end-time <ms>", "end time (0 = no upper bound)", int10).requiredOption("--strategy <strategy>", `strategy: ${DiscoverStrategy.options.join(" | ")}`).requiredOption(
4425
+ "--expected-update-time <ms>",
4426
+ "optimistic-lock update time",
4427
+ expectedUpdateTime
4428
+ ).action(async (id, opts, cmd) => {
4429
+ printJson(
4430
+ await clientFrom(cmd).vega.updateDiscoverSchedule(id, {
4431
+ name: opts.name,
4432
+ catalogId: opts.catalogId,
4433
+ cronExpr: opts.cron,
4434
+ enabled: opts.enabled,
4435
+ startTime: opts.startTime,
4436
+ endTime: opts.endTime,
4437
+ strategy: requiredDiscoverStrategy(opts.strategy),
4438
+ expectedUpdateTime: opts.expectedUpdateTime
4439
+ }),
4440
+ outputOptions(cmd)
4441
+ );
4442
+ });
4443
+ for (const action of ["enable", "disable", "delete"]) {
4444
+ discoverSchedule.command(`${action} <id>`).description(`${action[0]?.toUpperCase()}${action.slice(1)} a discovery schedule`).action(async (id, _opts, cmd) => {
4445
+ const api = clientFrom(cmd).vega;
4446
+ const result = action === "enable" ? await api.enableDiscoverSchedule(id) : action === "disable" ? await api.disableDiscoverSchedule(id) : await api.deleteDiscoverSchedule(id);
4447
+ printJson(result, outputOptions(cmd));
4448
+ });
4449
+ }
4450
+ const discoverTask = vega.command("discover-task").description("Resource discovery tasks");
4451
+ discoverTask.command("list").description("List discovery tasks").option("--catalog-id <id>", "filter by catalog id").option("--resource-id <id>", "filter by resource id").option("--schedule-id <id>", "filter by schedule id").option("--status <status>", `comma-separated: ${VegaTaskStatus.options.join(" | ")}`).option("--strategy <strategy>", `strategy: ${DiscoverStrategy.options.join(" | ")}`).option("--trigger-type <type>", "manual | scheduled").option("--limit <n>", "page size", int10, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int10, 0).option("--sort <field>", "create_time | start_time | finish_time | last_progress_time").option("--direction <dir>", "asc | desc").action(async (opts, cmd) => {
4452
+ printJson(
4453
+ await clientFrom(cmd).vega.discoverTasks({
4454
+ catalogId: opts.catalogId,
4455
+ resourceId: opts.resourceId,
4456
+ scheduleId: opts.scheduleId,
4457
+ status: taskStatuses(opts.status),
4458
+ strategy: discoverStrategy(opts.strategy),
4459
+ triggerType: discoverTaskTriggerType(opts.triggerType),
4460
+ limit: opts.limit,
4461
+ offset: opts.offset,
4462
+ sort: discoverTaskSort(opts.sort),
4463
+ direction: sortDirection(opts.direction)
4464
+ }),
4465
+ outputOptions(cmd)
4466
+ );
4467
+ });
4468
+ discoverTask.command("get <id>").description("Get a discovery task").action(async (id, _opts, cmd) => {
4469
+ printJson(await clientFrom(cmd).vega.getDiscoverTask(id), outputOptions(cmd));
4470
+ });
4471
+ discoverTask.command("delete <ids...>").description("Delete completed discovery tasks").option("--ignore-missing", "ignore missing task ids").action(async (ids, opts, cmd) => {
4472
+ printJson(
4473
+ await clientFrom(cmd).vega.deleteDiscoverTasks(ids, {
4474
+ ignoreMissing: opts.ignoreMissing
4475
+ }),
4476
+ outputOptions(cmd)
4477
+ );
4478
+ });
4479
+ const semanticTask = vega.command("semantic-task").description("Semantic-understanding tasks");
4480
+ semanticTask.command("list").description("List semantic-understanding tasks").option("--scope <scope>", `scope: ${SemanticUnderstandingScope.options.join(" | ")}`).option("--catalog-id <id>", "filter by catalog id").option("--resource-id <id>", "filter by resource id").option("--status <status>", `comma-separated: ${VegaTaskStatus.options.join(" | ")}`).option(
4481
+ "--apply-mode <mode>",
4482
+ `apply mode: ${SemanticUnderstandingApplyMode.options.join(" | ")}`
4483
+ ).option("--applied <bool>", "filter by applied state", bool).option("--limit <n>", "page size", int10, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int10, 0).option("--sort <field>", "create_time | start_time | finish_time").option("--direction <dir>", "asc | desc").action(async (opts, cmd) => {
4484
+ printJson(
4485
+ await clientFrom(cmd).vega.semanticUnderstandingTasks({
4486
+ scope: semanticScope(opts.scope),
4487
+ catalogId: opts.catalogId,
4488
+ resourceId: opts.resourceId,
4489
+ status: taskStatuses(opts.status),
4490
+ applyMode: semanticApplyMode(opts.applyMode),
4491
+ applied: opts.applied,
4492
+ limit: opts.limit,
4493
+ offset: opts.offset,
4494
+ sort: semanticTaskSort(opts.sort),
4495
+ direction: sortDirection(opts.direction)
4496
+ }),
4497
+ outputOptions(cmd)
4498
+ );
4499
+ });
4500
+ semanticTask.command("create").description("Create a semantic-understanding task").requiredOption("--scope <scope>", "resource | catalog").option("--catalog-id <id>", "catalog id").option("--resource-id <id>", "resource id").option("--apply-mode <mode>", "dry_run | fill_empty | force").option("--confidence-threshold <n>", "minimum confidence (0..1)", confidenceThreshold).option("--include-sample-rows", "include resource sample rows").option("--sample-max-rows <n>", "sample row limit", int10).action(async (opts, cmd) => {
4501
+ const scope = semanticScope(opts.scope);
4502
+ if (!scope) throw new InputError("--scope is required");
4503
+ const applyMode = semanticApplyMode(opts.applyMode);
4504
+ if (scope === "catalog" && !opts.catalogId) {
4505
+ throw new InputError("--catalog-id is required for catalog scope");
4506
+ }
4507
+ if (scope === "resource" && !opts.resourceId) {
4508
+ throw new InputError("--resource-id is required for resource scope");
4509
+ }
4510
+ if (scope === "catalog" && (opts.includeSampleRows || opts.sampleMaxRows !== void 0)) {
4511
+ throw new InputError("sample row options are only valid for resource scope");
4512
+ }
4513
+ if (scope === "resource" && opts.includeSampleRows && opts.sampleMaxRows === void 0) {
4514
+ throw new InputError("--sample-max-rows is required with --include-sample-rows");
4515
+ }
4516
+ if (scope === "resource" && !opts.includeSampleRows && opts.sampleMaxRows !== void 0) {
4517
+ throw new InputError("--sample-max-rows requires --include-sample-rows");
4518
+ }
4519
+ if (opts.sampleMaxRows !== void 0 && (opts.sampleMaxRows < 1 || opts.sampleMaxRows > 20)) {
4520
+ throw new InputError("--sample-max-rows must be between 1 and 20");
4521
+ }
4522
+ const common = {
4523
+ applyMode,
4524
+ confidenceThreshold: opts.confidenceThreshold
4525
+ };
4526
+ const request = scope === "catalog" ? { scope: "catalog", catalogId: opts.catalogId, ...common } : {
4527
+ scope: "resource",
4528
+ resourceId: opts.resourceId,
4529
+ includeSampleRows: opts.includeSampleRows ? true : void 0,
4530
+ samplePolicy: opts.sampleMaxRows === void 0 ? void 0 : { masked: false, maxRows: opts.sampleMaxRows },
4531
+ ...common
4532
+ };
4533
+ printJson(
4534
+ await clientFrom(cmd).vega.createSemanticUnderstandingTask(request),
2888
4535
  outputOptions(cmd)
2889
4536
  );
2890
4537
  });
2891
- catalog.command("discover <id>").description("Trigger catalog resource discovery").option("--wait", "wait for discovery to complete").action(async (id, opts, cmd) => {
4538
+ semanticTask.command("get <id>").description("Get a semantic-understanding task").action(async (id, _opts, cmd) => {
4539
+ printJson(await clientFrom(cmd).vega.getSemanticUnderstandingTask(id), outputOptions(cmd));
4540
+ });
4541
+ semanticTask.command("delete <ids...>").description("Delete completed semantic-understanding tasks").option("--ignore-missing", "ignore missing task ids").action(async (ids, opts, cmd) => {
2892
4542
  printJson(
2893
- await clientFrom(cmd).vega.discoverCatalog(id, Boolean(opts.wait)),
4543
+ await clientFrom(cmd).vega.deleteSemanticUnderstandingTasks(ids, {
4544
+ ignoreMissing: opts.ignoreMissing
4545
+ }),
2894
4546
  outputOptions(cmd)
2895
4547
  );
2896
4548
  });
@@ -2901,17 +4553,19 @@ function vegaCommand() {
2901
4553
  connector.command("get <type>").description("Get a connector type").action(async (type, _opts, cmd) => {
2902
4554
  printJson(await clientFrom(cmd).vega.connectorType(type), outputOptions(cmd));
2903
4555
  });
2904
- vega.command("sql").description("Run SQL / OpenSearch DSL directly against a vega-backend data source").option(
4556
+ vega.command("sql").description(
4557
+ "Read-only SQL straight against a data source \u2014 no knowledge network, no Trace record"
4558
+ ).option(
2905
4559
  "--query <sql>",
2906
4560
  "SQL string; reference a resource with a {{<resource-id>}} placeholder"
2907
- ).option("--input-dialect <dialect>", "SQL input dialect: postgres | mysql | trino | duckdb").option("--paging-mode <mode>", "paging mode: single | cursor").option("--limit <n>", "page size (cursor mode requires it)", int12).option("--offset <n>", "first-page offset", int12).option("--keep-alive-sec <s>", "cursor keep-alive in seconds (60\u20133600)", int12).option("--cursor <cursor>", "opaque cursor returned by the previous page").option("--need-total", "include the complete total count").option("--query-timeout-sec <s>", "query timeout in seconds (1\u20133600)", int12).option(
4561
+ ).option("--input-dialect <dialect>", "SQL input dialect: postgres | mysql | trino | duckdb").option("--paging-mode <mode>", "paging mode: single | cursor").option("--limit <n>", "page size (cursor mode requires it)", int10).option("--offset <n>", "first-page offset", int10).option("--keep-alive-sec <s>", "cursor keep-alive in seconds (60\u20133600)", int10).option("--cursor <cursor>", "opaque cursor returned by the previous page").option("--need-total", "include the complete total count").option("--query-timeout-sec <s>", "query timeout in seconds (1\u20133600)", int10).option(
2908
4562
  "-d, --data <json>",
2909
4563
  "full request body as JSON (advanced; wins over individual query flags)"
2910
4564
  ).action(async (opts, cmd) => {
2911
4565
  let body;
2912
4566
  if (opts.data) {
2913
4567
  try {
2914
- body = JSON.parse(opts.data);
4568
+ body = parseBigIntJSON(opts.data);
2915
4569
  } catch {
2916
4570
  throw new InputError("--data must be valid JSON");
2917
4571
  }
@@ -2948,18 +4602,15 @@ function vegaCommand() {
2948
4602
  printJson(await clientFrom(cmd).vega.sql(body), outputOptions(cmd));
2949
4603
  });
2950
4604
  const resource = vega.command("resource").description("Vega-backend resources");
2951
- resource.command("list").description("List resources").option("--datasource-id <id>", "filter by catalog/datasource id").option("--catalog-id <id>", "alias of --datasource-id").option("--type <category>", "resource category").option("--category <category>", "alias of --type").option("--status <status>", "filter by status").option("--database <name>", "filter by database").option("--limit <n>", "page size", (v) => Number.parseInt(v, 10), DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int12, 0).option("--include-extensions", "include all extension key/value pairs").option("--include-extension-keys <keys>", "include selected extension keys").option("--extension <k=v,...>", "filter by extension key/value pairs").option("--sort <field>", "sort field: name | create_time | update_time").option("--direction <dir>", "sort direction: asc | desc").action(async (opts, cmd) => {
4605
+ resource.command("list").description("List resources").option("--catalog-id <id>", "filter by catalog id").option("--type <category>", "resource category").option("--category <category>", "alias of --type").option("--status <status>", "filter by status").option("--schema <name>", "filter by source schema").option("--limit <n>", "page size", int10, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int10, 0).option("--sort <field>", "sort field: name | create_time | update_time").option("--direction <dir>", "sort direction: asc | desc").action(async (opts, cmd) => {
2952
4606
  printJson(
2953
4607
  await clientFrom(cmd).resource.list({
2954
- datasourceId: opts.datasourceId ?? opts.catalogId,
4608
+ catalogId: opts.catalogId,
2955
4609
  category: opts.type ?? opts.category,
2956
4610
  status: opts.status,
2957
- database: opts.database,
4611
+ schema: opts.schema,
2958
4612
  limit: opts.limit,
2959
4613
  offset: opts.offset,
2960
- includeExtensions: opts.includeExtensions,
2961
- includeExtensionKeys: opts.includeExtensionKeys,
2962
- extensionPairs: parsePairs2(opts.extension),
2963
4614
  sort: opts.sort,
2964
4615
  direction: opts.direction
2965
4616
  }),
@@ -2969,17 +4620,75 @@ function vegaCommand() {
2969
4620
  resource.command("get <id>").description("Get a resource").action(async (id, _opts, cmd) => {
2970
4621
  printJson(await clientFrom(cmd).resource.get(id), outputOptions(cmd));
2971
4622
  });
2972
- resource.command("query <id>").description("Fetch data rows from a resource").option("--limit <n>", "row limit", (v) => Number.parseInt(v, 10), 50).option("--offset <n>", "row offset", (v) => Number.parseInt(v, 10), 0).action(async (id, opts, cmd) => {
4623
+ resource.command("discover <id>").description("Trigger metadata discovery for a resource").action(async (id, _opts, cmd) => {
4624
+ printJson(await clientFrom(cmd).vega.discoverResource(id), outputOptions(cmd));
4625
+ });
4626
+ for (const action of ["enable", "disable"]) {
4627
+ resource.command(`${action} <id>`).description(`${action[0]?.toUpperCase()}${action.slice(1)} a resource`).action(async (id, _opts, cmd) => {
4628
+ const api = clientFrom(cmd).resource;
4629
+ const result = action === "enable" ? await api.enable(id) : await api.disable(id);
4630
+ printJson(result, outputOptions(cmd));
4631
+ });
4632
+ }
4633
+ resource.command("query <id>").description("Fetch data rows from a resource").option("--limit <n>", "row limit", int10, 50).option("--offset <n>", "row offset", int10, 0).action(async (id, opts, cmd) => {
2973
4634
  printJson(
2974
4635
  await clientFrom(cmd).resource.query(id, { limit: opts.limit, offset: opts.offset }),
2975
4636
  outputOptions(cmd)
2976
4637
  );
2977
4638
  });
4639
+ resource.command("document-get <resource-id> <document-id>").description("Get one dataset document").action(async (resourceId, documentId, _opts, cmd) => {
4640
+ printJson(
4641
+ await clientFrom(cmd).resource.getDocument(resourceId, documentId),
4642
+ outputOptions(cmd)
4643
+ );
4644
+ });
4645
+ resource.command("document-create <resource-id>").description("Create dataset documents").requiredOption(
4646
+ "--data <json>",
4647
+ "JSON array of documents \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (vega-backend)"
4648
+ ).action(async (resourceId, opts, cmd) => {
4649
+ printJson(
4650
+ await clientFrom(cmd).resource.createDocuments(
4651
+ resourceId,
4652
+ parseJsonArray(opts.data, "--data")
4653
+ ),
4654
+ outputOptions(cmd)
4655
+ );
4656
+ });
4657
+ resource.command("document-upsert <resource-id>").description("Upsert dataset documents; every document must have an id").requiredOption(
4658
+ "--data <json>",
4659
+ "JSON array of documents \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (vega-backend)"
4660
+ ).action(async (resourceId, opts, cmd) => {
4661
+ const documents = parseJsonArray(opts.data, "--data");
4662
+ if (documents.some((document) => typeof document.id !== "string")) {
4663
+ throw new InputError("every document in --data must have a string id");
4664
+ }
4665
+ printJson(
4666
+ await clientFrom(cmd).resource.upsertDocuments(resourceId, documents),
4667
+ outputOptions(cmd)
4668
+ );
4669
+ });
4670
+ resource.command("document-delete <resource-id> <document-ids...>").description("Delete dataset documents by id").action(async (resourceId, documentIds, _opts, cmd) => {
4671
+ printJson(
4672
+ await clientFrom(cmd).resource.deleteDocuments(resourceId, documentIds),
4673
+ outputOptions(cmd)
4674
+ );
4675
+ });
4676
+ resource.command("document-delete-filter <resource-id>").description("Delete dataset documents by a non-empty filter").requiredOption("--filter <json>", "filter_condition JSON object").action(async (resourceId, opts, cmd) => {
4677
+ const filter = parseJsonObject(opts.filter, "--filter");
4678
+ if (!Object.keys(filter).length) throw new InputError("--filter must not be empty");
4679
+ printJson(
4680
+ await clientFrom(cmd).resource.deleteDocumentsByFilter(resourceId, filter),
4681
+ outputOptions(cmd)
4682
+ );
4683
+ });
2978
4684
  const dataset = vega.command("dataset").description("Dataset index build tasks");
2979
4685
  dataset.command("build <resource-id>").description("Build a resource's index (creates a BuildTask)").requiredOption("--mode <mode>", "build mode: batch | streaming").option("--embedding-fields <list>", "comma-separated fields to vectorize").option(
2980
4686
  "--build-key-fields <list>",
2981
4687
  "comma-separated key fields (batch: time; streaming: row id)"
2982
- ).option("--embedding-model <id>", "default embedding model name/id").option("--fulltext-fields <list>", "comma-separated fields for fulltext index").option("--fulltext-analyzer <name>", "fulltext analyzer").option("--execute-type <type>", "batch execution type: incremental | full").option("--wait", "poll until the build reaches a terminal state").option("--timeout <s>", "wait timeout in seconds", (v) => Number.parseInt(v, 10), 300).action(async (resourceId, _opts, cmd) => {
4688
+ ).option(
4689
+ "--embedding-model <name-or-id>",
4690
+ "default small-model name (a numeric ID is resolved to its name)"
4691
+ ).option("--fulltext-fields <list>", "comma-separated fields for fulltext index").option("--fulltext-analyzer <name>", "fulltext analyzer").option("--execute-type <type>", "batch execution type: incremental | full").option("--wait", "poll until the build reaches a terminal state").option("--timeout <s>", "wait timeout in seconds", (v) => Number.parseInt(v, 10), 300).action(async (resourceId, _opts, cmd) => {
2983
4692
  const o = cmd.optsWithGlobals();
2984
4693
  const embeddingFields = csv(o.embeddingFields);
2985
4694
  const buildKeyFields = csv(o.buildKeyFields);
@@ -3007,28 +4716,26 @@ function vegaCommand() {
3007
4716
  const task = await clientFrom(cmd).vega.buildStatus(taskId);
3008
4717
  printJson(task, outputOptions(cmd));
3009
4718
  });
3010
- dataset.command("build-list").description("List BuildTasks").option("--limit <n>", "page size", int12, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int12, 0).option("--resource-id <id>", "filter by resource id").option("--catalog-id <id>", "filter by catalog id").option("--status <status>", "comma-separated statuses").option("--active", "only running/init tasks").option("--mode <mode>", "filter by mode: batch | streaming").option("--order-by <field>", "created_at | updated_at").option("--order <dir>", "asc | desc").action(async (opts, cmd) => {
3011
- if (opts.orderBy === "default") {
3012
- throw new InputError(
3013
- '--order-by default is no longer supported; use "created_at" or "updated_at"'
3014
- );
3015
- }
4719
+ dataset.command("build-list").description("List BuildTasks").option("--limit <n>", "page size", int10, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int10, 0).option("--resource-id <id>", "filter by resource id").option("--catalog-id <id>", "filter by catalog id").option("--status <status>", `comma-separated statuses: ${BuildTaskStatus.options.join(" | ")}`).option("--mode <mode>", "filter by mode: batch | streaming").option(
4720
+ "--execute-type <type>",
4721
+ `filter by execution type: ${BuildTaskExecuteType.options.join(" | ")}`
4722
+ ).option("--sort <field>", `sort field: ${BuildTaskSort.options.join(" | ")}`).option("--direction <dir>", `sort direction: ${SortDirection.options.join(" | ")}`).action(async (opts, cmd) => {
3016
4723
  printJson(
3017
4724
  await clientFrom(cmd).vega.buildTasks({
3018
4725
  limit: opts.limit,
3019
4726
  offset: opts.offset,
3020
4727
  resourceId: opts.resourceId,
3021
4728
  catalogId: opts.catalogId,
3022
- status: opts.status,
3023
- active: opts.active,
4729
+ status: buildTaskStatuses(opts.status),
3024
4730
  mode: opts.mode,
3025
- orderBy: opts.orderBy,
3026
- order: opts.order
4731
+ executeType: buildTaskExecuteType(opts.executeType),
4732
+ sort: buildTaskSort(opts.sort),
4733
+ direction: sortDirection(opts.direction)
3027
4734
  }),
3028
4735
  outputOptions(cmd)
3029
4736
  );
3030
4737
  });
3031
- dataset.command("build-start <task-id>").description("Start a BuildTask").option("--reset", "restart a full task from the beginning (ignored for incremental tasks)").action(async (taskId, opts, cmd) => {
4738
+ dataset.command("build-start <task-id>").description("Start a BuildTask").option("--reset", "restart a full task from the beginning (rejected for incremental tasks)").action(async (taskId, opts, cmd) => {
3032
4739
  printJson(
3033
4740
  await clientFrom(cmd).vega.startBuildTask(taskId, { reset: opts.reset }),
3034
4741
  outputOptions(cmd)
@@ -3037,43 +4744,142 @@ function vegaCommand() {
3037
4744
  dataset.command("build-stop <task-id>").description("Stop a BuildTask").action(async (taskId, _opts, cmd) => {
3038
4745
  printJson(await clientFrom(cmd).vega.stopBuildTask(taskId), outputOptions(cmd));
3039
4746
  });
3040
- dataset.command("build-delete <ids...>").description("Delete one or more BuildTasks").option("--ignore-missing", "ignore missing task ids").option("--delete-active-index", "delete active indexes too").action(async (ids, opts, cmd) => {
4747
+ dataset.command("build-delete <ids...>").description("Delete one or more BuildTasks").option("--ignore-missing", "ignore missing task ids").action(async (ids, opts, cmd) => {
3041
4748
  printJson(
3042
4749
  await clientFrom(cmd).vega.deleteBuildTasks(ids, {
3043
- ignoreMissing: opts.ignoreMissing,
3044
- deleteActiveIndex: opts.deleteActiveIndex
4750
+ ignoreMissing: opts.ignoreMissing
3045
4751
  }),
3046
4752
  outputOptions(cmd)
3047
4753
  );
3048
4754
  });
3049
- return group(vega, "AI DATA PLATFORM");
4755
+ groupChildren(vega, {
4756
+ GROUPS: [
4757
+ "catalog",
4758
+ "resource",
4759
+ "connector-type",
4760
+ "dataset",
4761
+ "discover-schedule",
4762
+ "discover-task",
4763
+ "semantic-task"
4764
+ ],
4765
+ RUN: ["sql"]
4766
+ });
4767
+ groupChildren(resource, {
4768
+ READ: ["list", "get", "document-get"],
4769
+ RUN: ["query", "discover"],
4770
+ WRITE: [
4771
+ "enable",
4772
+ "disable",
4773
+ "document-create",
4774
+ "document-upsert",
4775
+ "document-delete",
4776
+ "document-delete-filter"
4777
+ ]
4778
+ });
4779
+ groupChildren(catalog, {
4780
+ READ: ["list", "get", "resources", "health", "health-check-schedule"],
4781
+ RUN: ["test-connection", "test-connection-config", "discover"],
4782
+ WRITE: ["create", "update", "enable", "disable", "delete", "set-health-check-schedule"]
4783
+ });
4784
+ guide(
4785
+ vega,
4786
+ `FINDING DATA
4787
+ catalog list -> catalog resources <catalog-id> -> resource get <id>. A physical catalog
4788
+ can be discovered and written; a logical one cannot.
4789
+
4790
+ QUERYING DIRECTLY
4791
+ sql --query "<sql>" runs against the source itself. Name a resource with a
4792
+ {{<resource-id>}} placeholder rather than the physical table it happens to have.
4793
+
4794
+ BUILDING AN INDEX
4795
+ dataset build <resource-id> creates a BuildTask; build-status / build-list follow it.
4796
+ Indexes are per resource \u2014 a knowledge network has no build of its own.`
4797
+ );
4798
+ return group(vega, "DATA & KNOWLEDGE");
4799
+ }
4800
+
4801
+ // src/cli-program.ts
4802
+ function buildProgram() {
4803
+ const program2 = new Command16();
4804
+ program2.name("openbkn").description(
4805
+ "openbkn \u2014 one CLI for the BKN platform: knowledge networks, the data behind them,\nthe tools and skills agents run on them, and the traces they leave."
4806
+ ).version(package_default.version, "-V, --version", "output the version number").option("--base-url <url>", "platform base URL (env: BKN_BASE_URL)").option("--token <value>", "access token (env: BKN_TOKEN)").option("--user <id|name>", "use specific user credentials (env: BKN_USER)").option("--json", "machine-readable JSON output").option("--compact", "single-line JSON output").option("--full", "human view: show all columns (default trims to the key ones)").option("--biz-domain <s>", "business domain (alias: -bd)").option("--conversation-id <id>", "BKN Trace conversation id (env: BKN_CONVERSATION_ID)").option("--interaction-id <id>", "BKN Trace interaction id (env: BKN_INTERACTION_ID)").option(
4807
+ "--new-conversation",
4808
+ "ignore the remembered conversation for this command (see `openbkn context conversation`)"
4809
+ ).option("-k, --insecure", "skip TLS verification (dev / self-signed only)").option("--dry-run", "print the request this command would send, and send nothing").showHelpAfterError();
4810
+ program2.addCommand(authCommand());
4811
+ program2.addCommand(configCommand());
4812
+ program2.addCommand(appkeyCommand());
4813
+ program2.addCommand(bknCommand());
4814
+ program2.addCommand(vegaCommand());
4815
+ program2.addCommand(resourceCommand());
4816
+ program2.addCommand(contextCommand());
4817
+ program2.addCommand(modelCommand());
4818
+ program2.addCommand(skillCommand());
4819
+ program2.addCommand(toolboxCommand());
4820
+ program2.addCommand(toolCommand());
4821
+ program2.addCommand(functionCommand());
4822
+ program2.addCommand(traceCommand());
4823
+ program2.addCommand(adminCommand());
4824
+ program2.addCommand(callCommand());
4825
+ program2.addCommand(describeCommand(program2));
4826
+ guide(
4827
+ program2,
4828
+ `FIRST STEPS
4829
+ openbkn auth login https://your-platform -u <user> -p <pass>
4830
+ openbkn bkn list # knowledge networks you can see
4831
+ openbkn bkn --help # every group has its own help
4832
+ openbkn describe --depth 1 # the whole map as one table; \`describe <command>\`
4833
+ # drills in, --json ships the same tree as data
4834
+
4835
+ COMMON TASKS
4836
+ Answer a question bkn search <kn-id> "<q>" -> context search-schema ->
4837
+ context query-object-instance --args '<json>'
4838
+ Look at the data vega catalog list -> resource find --name <t> -> resource query <id>
4839
+ Build from a catalog bkn create-from-catalog <catalog-id> --name "<n>" ->
4840
+ vega dataset build <resource-id>
4841
+ Edit as files bkn pull <kn-id> ./kn -> bkn validate ./kn -> bkn push ./kn
4842
+ Ship a capability skill register ./my-skill; toolbox create --name "<n>" ->
4843
+ tool upload ./api.yaml --toolbox <id> -> toolbox publish <id>
4844
+ Ship some code function run ./add.py -> toolbox create --name "<n>" --type function
4845
+ -> tool create ./add.py --toolbox <box-id> --name add ->
4846
+ tool enable <tool-id> --toolbox <box-id>
4847
+ Debug an answer trace conversations list -> trace diagnose <conversation-id> --llm
4848
+
4849
+ GOOD TO KNOW
4850
+ Every command group sorts its subcommands into the same four sections: GROUPS nests one
4851
+ level deeper, READ changes nothing, RUN acts without changing configuration (triggers a
4852
+ job, spends a model call, rotates a token), WRITE changes platform state \u2014 confirm those
4853
+ with a person first.
4854
+ Add --json to any command for machine-readable output (the default view trims columns,
4855
+ --full widens it). Most list commands answer {entries, total_count}; anything else says
4856
+ so in its own description. Ids come from list/search output \u2014 opaque, never guess one,
4857
+ and the key holding one is not always \`id\` (\`skill_id\`, \`conversation_id\`, \`ot_id\` \u2026).
4858
+ Multi-tenant deploys: --biz-domain picks the domain, --user switches saved logins.
4859
+ \`openbkn call\` reaches any endpoint a command does not cover, auth injected. Look the
4860
+ path up at https://openbkn-ai.github.io/bkn-foundry/ first \u2014 do not guess one.`
4861
+ );
4862
+ installGroupedHelp(program2);
4863
+ return program2;
3050
4864
  }
3051
4865
 
3052
4866
  // src/cli.ts
3053
- var program = new Command17();
3054
- program.name("openbkn").description("Operate the BKN platform from the CLI").version(package_default.version, "-V, --version", "output the version number").option("--base-url <url>", "platform base URL (env: BKN_BASE_URL)").option("--token <value>", "access token (env: BKN_TOKEN)").option("--user <id|name>", "use specific user credentials (env: BKN_USER)").option("--json", "machine-readable JSON output").option("--compact", "single-line JSON output").option("--full", "human view: show all columns (default trims to the key ones)").option("--biz-domain <s>", "business domain (alias: -bd)").option("--conversation-id <id>", "BKN Trace conversation id (env: BKN_CONVERSATION_ID)").option("--interaction-id <id>", "BKN Trace interaction id (env: BKN_INTERACTION_ID)").option("-k, --insecure", "skip TLS verification (dev / self-signed only)").showHelpAfterError();
3055
- program.addCommand(authCommand());
3056
- program.addCommand(callCommand());
3057
- program.addCommand(configCommand());
3058
- program.addCommand(appkeyCommand());
3059
- program.addCommand(vegaCommand());
3060
- program.addCommand(bknCommand());
3061
- program.addCommand(resourceCommand());
3062
- program.addCommand(dataflowCommand());
3063
- program.addCommand(contextCommand());
3064
- program.addCommand(agentCommand());
3065
- program.addCommand(modelCommand());
3066
- program.addCommand(skillCommand());
3067
- program.addCommand(toolboxCommand());
3068
- program.addCommand(toolCommand());
3069
- program.addCommand(traceCommand());
3070
- program.addCommand(adminCommand());
3071
- program.addCommand(exploreCommand());
3072
- installGroupedHelp(program);
4867
+ process.stdout.on("error", (err) => {
4868
+ if (err.code === "EPIPE") process.exit(0);
4869
+ throw err;
4870
+ });
4871
+ var program = buildProgram();
3073
4872
  var argv = process.argv.map((a) => a === "-bd" ? "--biz-domain" : a);
4873
+ if (argv.includes("--dry-run")) enableDryRun();
3074
4874
  try {
3075
4875
  await program.parseAsync(argv);
3076
4876
  } catch (err) {
4877
+ if (err instanceof DryRunSignal) {
4878
+ process.stdout.write(`${JSON.stringify(err.request, null, 2)}
4879
+ `);
4880
+ await releaseLifecycleSessions();
4881
+ process.exit(0);
4882
+ }
3077
4883
  console.error(formatError(err));
3078
4884
  await releaseLifecycleSessions();
3079
4885
  process.exit(toExitCode(err));