@openbkn/bkn-sdk 0.1.5-rc.1 → 0.1.5

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,9 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ AUDIT_LOG_MAX_LIMIT,
4
+ BuildMode,
3
5
  BuildTaskExecuteType,
4
6
  BuildTaskSort,
5
7
  BuildTaskStatus,
6
- DEFAULT_BUSINESS_DOMAIN,
8
+ CatalogHealthCheckStatus,
9
+ ConnectorCategory,
10
+ ConnectorMode,
7
11
  DEFAULT_LIST_LIMIT,
8
12
  DEFAULT_QUERY_LIMIT,
9
13
  DiscoverScheduleSort,
@@ -11,17 +15,28 @@ import {
11
15
  DiscoverTaskSort,
12
16
  DiscoverTaskTriggerType,
13
17
  DryRunSignal,
18
+ FUNCTION_TEMPLATE_TYPES,
19
+ FunctionAiGenerationType,
14
20
  HttpError,
21
+ IMPEX_TYPES,
15
22
  InputError,
23
+ ResourceCategory,
24
+ ResourceStatus,
25
+ SKILL_SOURCES,
26
+ SKILL_STATUSES,
16
27
  SemanticUnderstandingApplyMode,
17
28
  SemanticUnderstandingScope,
18
29
  SemanticUnderstandingTaskSort,
19
30
  SortDirection,
31
+ TOOL_METADATA_TYPES,
20
32
  VegaTaskStatus,
33
+ WaitTimeoutError,
21
34
  activePlatform,
22
35
  attachToken,
36
+ buildTaskState,
23
37
  changePasswordSafe,
24
38
  classifyPath,
39
+ configureVersionCheck,
25
40
  createClient,
26
41
  credentialDeviceLogin,
27
42
  currentToken,
@@ -30,17 +45,19 @@ import {
30
45
  deletePlatform,
31
46
  deviceLogin,
32
47
  enableDryRun,
48
+ ensureCompatible,
33
49
  exportCreds,
34
50
  filesUnder,
35
51
  formatError,
36
- getUserSafe,
52
+ getMeSafe,
53
+ isBuildTaskUnsuccessful,
37
54
  isHeadless,
38
55
  lifecycleHint,
39
56
  listPlatforms,
40
57
  logout,
41
58
  openBrowser,
59
+ package_default,
42
60
  parseBigIntJSON,
43
- parseEmbeddingFields,
44
61
  parsePkMap,
45
62
  rawCall,
46
63
  readPlatformConfig,
@@ -48,6 +65,8 @@ import {
48
65
  renderReportMarkdown,
49
66
  renderTree,
50
67
  resolveContext,
68
+ scopeFromKinds,
69
+ sessionInsecure,
51
70
  setActivePlatform,
52
71
  status,
53
72
  stringifyBigIntJSON,
@@ -56,83 +75,17 @@ import {
56
75
  trimTrailingSlashes,
57
76
  updatePlatformConfig,
58
77
  use,
78
+ validateBknDirectory,
59
79
  validateFixturePath,
60
80
  whoami
61
- } from "./chunk-WIYRGBAB.js";
81
+ } from "./chunk-ORVEVDGJ.js";
62
82
 
63
83
  // src/cli-program.ts
64
- import { Command as Command16 } from "commander";
65
-
66
- // package.json
67
- var package_default = {
68
- name: "@openbkn/bkn-sdk",
69
- version: "0.1.5-rc.1",
70
- description: "Unified TypeScript SDK + CLI for the BKN (Business Knowledge Network) platform.",
71
- type: "module",
72
- license: "Apache-2.0",
73
- engines: {
74
- node: ">=22.19.0"
75
- },
76
- bin: {
77
- openbkn: "./dist/cli.js"
78
- },
79
- main: "./dist/index.js",
80
- types: "./dist/index.d.ts",
81
- exports: {
82
- ".": {
83
- types: "./dist/index.d.ts",
84
- import: "./dist/index.js"
85
- }
86
- },
87
- files: ["dist", "!dist/**/*.map", "README.md", "README.zh.md", "LICENSE", "NOTICE"],
88
- publishConfig: {
89
- access: "public"
90
- },
91
- repository: {
92
- type: "git",
93
- url: "git+https://github.com/openbkn-ai/bkn-sdk.git"
94
- },
95
- keywords: ["bkn", "openbkn", "knowledge-network", "cli", "sdk", "typescript"],
96
- scripts: {
97
- build: "tsup",
98
- dev: "tsup --watch",
99
- typecheck: "tsc --noEmit",
100
- "check:deps": "node scripts/check-no-self-dep.mjs",
101
- "capture:returns": "node scripts/capture-returns.mjs",
102
- lint: "npm run check:deps && biome check . && tsc --noEmit",
103
- format: "biome format --write .",
104
- test: "vitest run",
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",
109
- "test:cover": "vitest run --coverage",
110
- ci: "npm run lint && npm test",
111
- prepublishOnly: "npm run ci && npm run build"
112
- },
113
- dependencies: {
114
- "@clack/prompts": "^0.9.1",
115
- chalk: "^5.4.1",
116
- commander: "^13.1.0",
117
- "csv-parse": "^6.2.1",
118
- "js-yaml": "^4.3.0",
119
- jszip: "^3.10.1",
120
- undici: "^8.9.0",
121
- zod: "^3.24.1"
122
- },
123
- devDependencies: {
124
- "@biomejs/biome": "^1.9.4",
125
- "@types/js-yaml": "^4.0.9",
126
- "@types/node": "^24.0.0",
127
- tsup: "^8.4.0",
128
- typescript: "^5.8.0",
129
- vitest: "^3.0.0"
130
- }
131
- };
84
+ import { Command as Command17 } from "commander";
132
85
 
133
86
  // src/commands/admin.ts
134
87
  import { readFileSync as readFileSync2 } from "fs";
135
- import { Command as Command2 } from "commander";
88
+ import { Command as Command2, Option } from "commander";
136
89
 
137
90
  // src/help/grouped-help.ts
138
91
  var GROUP = /* @__PURE__ */ Symbol("openbkn.group");
@@ -161,11 +114,11 @@ var VERB_SECTIONS = [
161
114
  "READ"
162
115
  ],
163
116
  [
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)$/,
117
+ /^(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|stop|resume|ensure-current|create-new-generation|close|complete|fail|cancel|handoff|operations)$/,
165
118
  "RUN"
166
119
  ],
167
120
  [
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)$/,
121
+ /^(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|publish-history|update-metadata|update-package|create-from-catalog|export-config)$/,
169
122
  "WRITE"
170
123
  ]
171
124
  ];
@@ -314,7 +267,8 @@ var ROW_ENVELOPES = [
314
267
  "roles",
315
268
  "departments",
316
269
  "members",
317
- "keys"
270
+ "keys",
271
+ "logs"
318
272
  ];
319
273
  function toRows(value) {
320
274
  const isRowArray = (v) => Array.isArray(v) && v.length > 0 && v.every((x) => x !== null && typeof x === "object" && !Array.isArray(x));
@@ -401,7 +355,7 @@ function stringifyCell(v) {
401
355
  // src/utils/prompt.ts
402
356
  import { createInterface } from "readline";
403
357
  function promptLine(query, hidden = false) {
404
- return new Promise((resolve3) => {
358
+ return new Promise((resolve2) => {
405
359
  const rl = createInterface({ input: process.stdin, output: process.stdout });
406
360
  if (hidden) {
407
361
  const mutable = rl;
@@ -412,7 +366,7 @@ function promptLine(query, hidden = false) {
412
366
  rl.question(query, (answer) => {
413
367
  rl.close();
414
368
  if (hidden) process.stdout.write("\n");
415
- resolve3(answer.trim());
369
+ resolve2(answer.trim());
416
370
  });
417
371
  });
418
372
  }
@@ -446,6 +400,17 @@ function traceOptionsFrom(o) {
446
400
  ...interactionId ? { interactionId } : {}
447
401
  };
448
402
  }
403
+ function retryOptionsFrom(o) {
404
+ return {
405
+ retry: o.retry !== false,
406
+ onRetry: (n) => {
407
+ process.stderr.write(
408
+ `openbkn: ${n.method} ${new URL(n.url).pathname} failed (${n.reason}); retry ${n.attempt}/${n.retries} in ${n.delayMs / 1e3}s
409
+ `
410
+ );
411
+ }
412
+ };
413
+ }
449
414
  function clientFrom(cmd) {
450
415
  const o = cmd.optsWithGlobals();
451
416
  const trace = traceOptionsFrom(o);
@@ -455,8 +420,9 @@ function clientFrom(cmd) {
455
420
  baseUrl: o.baseUrl,
456
421
  token: o.token,
457
422
  user: o.user,
458
- businessDomain: o.bizDomain,
459
423
  insecure: o.insecure,
424
+ versionCheckMode: "cli",
425
+ ...retryOptionsFrom(o),
460
426
  ...trace ? { trace } : {},
461
427
  ...remembered.source === "stored" && remembered.id ? { rememberedConversationId: remembered.id } : {},
462
428
  // Remember a conversation this run opens, so the next command continues the
@@ -484,10 +450,67 @@ function outputOptions(cmd) {
484
450
  const o = cmd.optsWithGlobals();
485
451
  return { json: Boolean(o.json), compact: Boolean(o.compact), full: Boolean(o.full) };
486
452
  }
453
+ var positiveInt = (flag, max) => (v) => {
454
+ const bound = max === void 0 ? "a positive integer" : `an integer from 1 to ${max}`;
455
+ if (!/^\d+$/.test(v)) throw new InputError(`${flag} must be ${bound} (got '${v}')`);
456
+ const n = Number.parseInt(v, 10);
457
+ if (!Number.isSafeInteger(n) || n <= 0 || max !== void 0 && n > max) {
458
+ throw new InputError(`${flag} must be ${bound} (got '${v}')`);
459
+ }
460
+ return n;
461
+ };
462
+ var MAX_PAGE_SIZE = 100;
463
+ var oneOf = (flag, values) => (v) => {
464
+ if (values.includes(v)) return v;
465
+ throw new InputError(`${flag} must be one of: ${values.join(" | ")} (got '${v}')`);
466
+ };
487
467
  function csv(value) {
488
468
  if (!value) return void 0;
489
469
  return value.split(",").map((s) => s.trim()).filter(Boolean);
490
470
  }
471
+ function readJsonArgs(opts, required = false) {
472
+ if (opts.args !== void 0 && opts.argsFile !== void 0) {
473
+ throw new InputError("--args and --args-file cannot be combined");
474
+ }
475
+ const file = opts.argsFile ?? (opts.args === "-" ? "-" : void 0);
476
+ let raw = opts.args;
477
+ if (file !== void 0) {
478
+ try {
479
+ raw = readFileSync(file === "-" ? 0 : file, "utf8");
480
+ } catch (error) {
481
+ throw new InputError(
482
+ `Cannot read ${file === "-" ? "stdin" : `--args-file '${file}'`}: ${error instanceof Error ? error.message : error}`
483
+ );
484
+ }
485
+ }
486
+ if (raw === void 0) {
487
+ if (required) throw new InputError("--args is required (run with --schema to see its shape)");
488
+ return void 0;
489
+ }
490
+ let parsed;
491
+ try {
492
+ parsed = parseBigIntJSON(raw);
493
+ } catch {
494
+ throw new InputError("--args must be valid JSON");
495
+ }
496
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
497
+ throw new InputError("--args must be a JSON object");
498
+ }
499
+ return parsed;
500
+ }
501
+ function cypherParams(raw) {
502
+ if (raw === void 0) return void 0;
503
+ let parsed;
504
+ try {
505
+ parsed = parseBigIntJSON(raw);
506
+ } catch {
507
+ throw new InputError("--params is not valid JSON.");
508
+ }
509
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
510
+ throw new InputError(`--params must be a JSON object, as in '{"floor": 100}'.`);
511
+ }
512
+ return parsed;
513
+ }
491
514
  function readBody(opts) {
492
515
  const raw = opts.bodyFile ? readFileSync(opts.bodyFile, "utf8") : opts.body;
493
516
  if (!raw) throw new InputError("Provide --body '<json>' or --body-file <path>.");
@@ -500,19 +523,50 @@ function readBody(opts) {
500
523
 
501
524
  // src/commands/auth.ts
502
525
  import { Command } from "commander";
503
- async function resolveAccount(baseUrl, accessToken, insecure, idToken) {
504
- const sub = decodeJwt(idToken ?? accessToken)?.sub;
505
- if (!sub) return void 0;
526
+ async function resolveAccount(ctx, idToken) {
527
+ const claims = decodeJwt(idToken ?? ctx.token);
528
+ if (claims?.preferred_username) return claims.preferred_username;
529
+ if (!claims?.sub) return void 0;
506
530
  try {
507
- const u = await getUserSafe(
508
- { baseUrl, token: accessToken, businessDomain: DEFAULT_BUSINESS_DOMAIN, insecure },
509
- sub
510
- );
511
- return u.account;
531
+ const u = await getMeSafe(ctx);
532
+ return u?.account;
512
533
  } catch {
513
534
  return void 0;
514
535
  }
515
536
  }
537
+ async function verifyLoginPlatform(ctx) {
538
+ await ensureCompatible(ctx, new URL(ctx.baseUrl));
539
+ }
540
+ function loginContext(baseUrl, token, insecure, options) {
541
+ const ctx = {
542
+ baseUrl: trimTrailingSlashes(baseUrl),
543
+ token,
544
+ insecure: Boolean(insecure),
545
+ ...retryOptionsFrom(options)
546
+ };
547
+ configureVersionCheck(ctx, "login");
548
+ return ctx;
549
+ }
550
+ function warnEnvShadow(baseUrl) {
551
+ const notes = [];
552
+ const vars = [];
553
+ if (process.env.BKN_TOKEN) {
554
+ notes.push("BKN_TOKEN is set \u2014 other commands send it instead of this session's token.");
555
+ vars.push("BKN_TOKEN");
556
+ }
557
+ const envBase = process.env.BKN_BASE_URL;
558
+ if (envBase && baseUrl && trimTrailingSlashes(envBase) !== trimTrailingSlashes(baseUrl)) {
559
+ notes.push(
560
+ "BKN_BASE_URL is set to a different platform \u2014 other commands use it instead of this saved session."
561
+ );
562
+ vars.push("BKN_BASE_URL");
563
+ }
564
+ if (vars.length === 0) return;
565
+ process.stderr.write(
566
+ `warning: ${notes.join(" ")} To use the saved session: \`unset ${vars.join(" ")}\`
567
+ `
568
+ );
569
+ }
516
570
  function renderSessions(items) {
517
571
  const byPlatform = /* @__PURE__ */ new Map();
518
572
  for (const it of items) {
@@ -543,9 +597,11 @@ function registerAuthLeaves(cmd) {
543
597
  process.stdout.write(`Logged in to ${r.baseUrl ?? url} as ${r.username ?? r.userId}
544
598
  `);
545
599
  }
600
+ warnEnvShadow(r.baseUrl ?? url);
546
601
  };
547
602
  const token = opts.token ?? g.token;
548
603
  if (token) {
604
+ await verifyLoginPlatform(loginContext(url, token, g.insecure, g));
549
605
  report(attachToken(url, token, { insecure: g.insecure }));
550
606
  return;
551
607
  }
@@ -594,18 +650,16 @@ User code: ${userCode}
594
650
  }
595
651
  throw e;
596
652
  }
653
+ const authenticatedContext = loginContext(url, tokens.accessToken, g.insecure, g);
654
+ await verifyLoginPlatform(authenticatedContext);
597
655
  if (!account) {
598
- account = await resolveAccount(
599
- url,
600
- tokens.accessToken,
601
- Boolean(g.insecure),
602
- tokens.idToken
603
- );
656
+ account = await resolveAccount(authenticatedContext, tokens.idToken);
604
657
  }
605
658
  report(
606
659
  attachToken(url, tokens.accessToken, {
607
660
  refreshToken: tokens.refreshToken,
608
661
  idToken: tokens.idToken,
662
+ expiresAt: tokens.expiresAt,
609
663
  username: account,
610
664
  insecure: g.insecure
611
665
  })
@@ -620,25 +674,22 @@ User code: ${userCode}
620
674
  process.stdout.write(`${token}
621
675
  `);
622
676
  });
623
- cmd.command("whoami [url]").description("Show current user identity (from the token)").option("--no-lookup", "skip the backend identity fallback (eacp/user/get)").action(async (_url, opts, cmd2) => {
677
+ cmd.command("whoami [url]").description("Show current user identity (from the token)").option("--no-lookup", "skip the backend identity lookup (bkn-safe /me)").action(async (_url, opts, cmd2) => {
624
678
  const g = cmd2.optsWithGlobals();
625
679
  const me = whoami({ user: g.user });
626
680
  if (opts.lookup !== false && me.baseUrl && me.sub) {
627
681
  try {
628
- const u = await getUserSafe(
629
- {
630
- baseUrl: me.baseUrl,
631
- token: currentToken({ user: g.user }),
632
- businessDomain: DEFAULT_BUSINESS_DOMAIN,
633
- insecure: Boolean(g.insecure)
634
- },
635
- me.sub
636
- );
682
+ const u = await getMeSafe({
683
+ baseUrl: me.baseUrl,
684
+ token: currentToken({ user: g.user }),
685
+ insecure: g.insecure ?? sessionInsecure({ user: g.user })
686
+ });
637
687
  if (u.account) me.username = u.account;
638
688
  if (u.name) me.name = u.name;
639
689
  } catch {
640
690
  }
641
691
  }
692
+ warnEnvShadow(me.baseUrl);
642
693
  const out = outputOptions(cmd2);
643
694
  if (out.json || out.compact || out.full) {
644
695
  printJson(me, out);
@@ -699,8 +750,9 @@ User code: ${userCode}
699
750
  baseUrl: url ?? g.baseUrl,
700
751
  token: g.token,
701
752
  user: g.user,
702
- businessDomain: g.bizDomain,
703
- insecure: g.insecure
753
+ insecure: g.insecure,
754
+ versionCheckMode: "cli",
755
+ ...retryOptionsFrom(g)
704
756
  });
705
757
  const account = opts.account ?? await promptLine("Account: ");
706
758
  const oldPassword = opts.oldPassword ?? await promptLine("Current password: ", true);
@@ -758,6 +810,24 @@ TOKENS
758
810
 
759
811
  // src/commands/admin.ts
760
812
  var int = (v) => Number.parseInt(v, 10);
813
+ var NEW_USER_PASSWORD_ENV = "BKN_NEW_USER_PASSWORD";
814
+ async function newUserPassword(opts) {
815
+ if (opts.password) return opts.password;
816
+ if (opts.promptPassword) {
817
+ const typed = await promptLine("Initial password: ", true);
818
+ if (typed) return typed;
819
+ throw new InputError("No password entered.");
820
+ }
821
+ const fromEnv = process.env[NEW_USER_PASSWORD_ENV];
822
+ if (fromEnv) return fromEnv;
823
+ throw new InputError(
824
+ `A new user needs an initial password: pass --password, --prompt-password, or set ${NEW_USER_PASSWORD_ENV}.`
825
+ );
826
+ }
827
+ function departmentIds(values) {
828
+ if (values === void 0) return void 0;
829
+ return values.flatMap((v) => v.split(",")).map((v) => v.trim()).filter((v) => v && v !== "-1");
830
+ }
761
831
  var DEFAULT_RESET_PASSWORD = "openbkn";
762
832
  async function importLicenseFile(cmd, file, receipt) {
763
833
  const text = readFileSync2(file, "utf8");
@@ -769,10 +839,9 @@ function adminCommand() {
769
839
  const admin = new Command2("admin").description("Orgs, users, roles, license, audit log");
770
840
  registerAuthLeaves(admin.command("auth").description("Operator authentication"));
771
841
  const org = admin.command("org").description("Departments and org structure");
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) => {
842
+ org.command("list").description("List departments \u2192 {departments, total}").option("--name <s>", "filter by name").option("--limit <n>", "page size", int, 100).option("--offset <n>", "page offset", int, 0).action(async (opts, cmd) => {
773
843
  printJson(
774
844
  await clientFrom(cmd).admin.orgList({
775
- role: opts.role,
776
845
  name: opts.name,
777
846
  limit: opts.limit,
778
847
  offset: opts.offset
@@ -783,39 +852,37 @@ function adminCommand() {
783
852
  org.command("get <id>").description("Get one department").action(async (id, _opts, cmd) => {
784
853
  printJson(await clientFrom(cmd).admin.orgGet(id), outputOptions(cmd));
785
854
  });
786
- org.command("members <id>").description("List members of a department").option("--role <r>", "role qualifier", "super_admin").option("--fields <s>", "fields segment", "users").option("--limit <n>", "page size", int, 100).option("--offset <n>", "page offset", int, 0).action(async (id, opts, cmd) => {
855
+ org.command("members <id>").description("List direct members of a department \u2192 {users, total}").option("--limit <n>", "page size (default: all members)", int).option("--offset <n>", "page offset", int).action(async (id, opts, cmd) => {
787
856
  printJson(
788
857
  await clientFrom(cmd).admin.orgMembers(id, {
789
- role: opts.role,
790
858
  limit: opts.limit,
791
859
  offset: opts.offset
792
860
  }),
793
861
  outputOptions(cmd)
794
862
  );
795
863
  });
796
- org.command("create").description("Create a department").requiredOption("--name <s>", "department name").option("--parent <id>", "parent department id", "-1").option("--code <s>", "department code").option("--remark <s>", "remark").option("--email <s>", "email").option("--manager <id>", "manager user id").option("--status <n>", "status (1=enabled)", int).option("--oss-id <id>", "object-storage site id").action(async (opts, cmd) => {
864
+ org.command("create").description("Create a department").requiredOption("--name <s>", "department name").option("--parent <id>", "parent department id (omit for a root department)").option("--code <s>", "department code").option("--remark <s>", "remark").option("--email <s>", "email").option("--manager <id>", "manager user id").action(async (opts, cmd) => {
797
865
  printJson(
798
866
  await clientFrom(cmd).admin.orgCreate({
799
867
  name: opts.name,
800
- parentId: opts.parent,
868
+ // "-1" was the ISF spelling of "no parent"; bkn-safe wants none.
869
+ parentId: opts.parent === "-1" ? void 0 : opts.parent,
801
870
  code: opts.code,
802
871
  remark: opts.remark,
803
872
  email: opts.email,
804
- managerID: opts.manager,
805
- status: opts.status
873
+ managerID: opts.manager
806
874
  }),
807
875
  outputOptions(cmd)
808
876
  );
809
877
  });
810
- org.command("update <id>").description("Update a department (only provided fields change)").option("--name <s>", "new name").option("--code <s>", "department code").option("--remark <s>", "remark").option("--email <s>", "email").option("--manager <id>", "manager user id").option("--status <n>", "status (1=enabled)", int).option("--oss-id <id>", "object-storage site id").action(async (id, opts, cmd) => {
878
+ org.command("update <id>").description("Update a department (only provided fields change)").option("--name <s>", "new name").option("--code <s>", "department code").option("--remark <s>", "remark").option("--email <s>", "email").option("--manager <id>", "manager user id").action(async (id, opts, cmd) => {
811
879
  printJson(
812
880
  await clientFrom(cmd).admin.orgUpdate(id, {
813
881
  name: opts.name,
814
882
  code: opts.code,
815
883
  remark: opts.remark,
816
884
  email: opts.email,
817
- managerID: opts.manager,
818
- status: opts.status
885
+ managerID: opts.manager
819
886
  }),
820
887
  outputOptions(cmd)
821
888
  );
@@ -823,14 +890,14 @@ function adminCommand() {
823
890
  org.command("delete <id>").description("Delete a department").action(async (id, _opts, cmd) => {
824
891
  printJson(await clientFrom(cmd).admin.orgDelete(id), outputOptions(cmd));
825
892
  });
826
- org.command("tree").description("Print the department hierarchy").option("--role <r>", "role qualifier", "super_admin").action(async (opts, cmd) => {
827
- const tree = await clientFrom(cmd).admin.orgTree(opts.role);
893
+ org.command("tree").description("Print the department hierarchy").action(async (_opts, cmd) => {
894
+ const tree = await clientFrom(cmd).admin.orgTree();
828
895
  const out = outputOptions(cmd);
829
896
  if (out.json) printJson(tree, out);
830
897
  else console.log(renderOrgTree(tree));
831
898
  });
832
899
  const user = admin.command("user").description("User management");
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) => {
900
+ user.command("list").description("List users \u2192 {users, total}").option("--org <id>", "only direct members of this department").option("--keyword <s>", "filter by name").option("--limit <n>", "page size", int, 100).option("--offset <n>", "page offset", int, 0).action(async (opts, cmd) => {
834
901
  printJson(
835
902
  await clientFrom(cmd).admin.userList({
836
903
  orgId: opts.org,
@@ -859,35 +926,27 @@ function adminCommand() {
859
926
  user.command("roles <user>").description("List roles granted to a user").action(async (userId, _opts, cmd) => {
860
927
  printJson(await clientFrom(cmd).admin.userRoles(userId), outputOptions(cmd));
861
928
  });
862
- user.command("create").description("Create a user (gets the platform default password)").requiredOption("--login <name>", "login name").option("--display-name <s>", "display name (defaults to login name)").option("--email <s>", "email").option("--department <id>", "department id", "-1").option("--code <s>", "user code").option("--position <s>", "position").option("--remark <s>", "remark").option("--tel <s>", "telephone").option("--priority <n>", "priority", int).option("--csf-level <n>", "confidentiality level", int).action(async (opts, cmd) => {
929
+ user.command("create").description("Create a user with an explicit initial password (must change on first login)").requiredOption("--login <name>", "login name").option("--password <s>", `initial password (or set ${NEW_USER_PASSWORD_ENV})`).option("--prompt-password", "type the initial password interactively (input hidden)").option("--display-name <s>", "display name").option("--email <s>", "email").option("--department <id...>", "initial department id(s)").option("--tel <s>", "telephone").action(async (opts, cmd) => {
930
+ const password = await newUserPassword(opts);
863
931
  printJson(
864
932
  await clientFrom(cmd).admin.userCreate({
865
933
  loginName: opts.login,
934
+ password,
866
935
  displayName: opts.displayName,
867
936
  email: opts.email,
868
- departmentIds: opts.department ? [opts.department] : void 0,
869
- code: opts.code,
870
- position: opts.position,
871
- remark: opts.remark,
872
- telNumber: opts.tel,
873
- priority: opts.priority,
874
- csfLevel: opts.csfLevel
937
+ departmentIds: departmentIds(opts.department),
938
+ telNumber: opts.tel
875
939
  }),
876
940
  outputOptions(cmd)
877
941
  );
878
942
  });
879
- user.command("update <id>").description("Update a user (only provided fields change)").option("--display-name <s>", "display name").option("--email <s>", "email").option("--code <s>", "user code").option("--position <s>", "position").option("--remark <s>", "remark").option("--tel <s>", "telephone").option("--manager <id>", "manager user id").option("--idcard <s>", "id-card number").option("--priority <n>", "priority", int).option("--csf-level <n>", "confidentiality level", int).option("--csf-level2 <n>", "secondary confidentiality level", int).option("--expire-time <n>", "account expiry (epoch, -1 = never)", int).action(async (id, opts, cmd) => {
943
+ user.command("update <id>").description("Update a user (only provided fields change)").option("--display-name <s>", "display name").option("--email <s>", "email").option("--tel <s>", "telephone").option("--department <id...>", "replace department membership with these id(s)").action(async (id, opts, cmd) => {
880
944
  printJson(
881
945
  await clientFrom(cmd).admin.userUpdate(id, {
882
946
  displayName: opts.displayName,
883
947
  email: opts.email,
884
- code: opts.code,
885
- position: opts.position,
886
- remark: opts.remark,
887
948
  telNumber: opts.tel,
888
- managerID: opts.manager,
889
- priority: opts.priority,
890
- csfLevel: opts.csfLevel
949
+ departmentIds: opts.department === void 0 ? void 0 : departmentIds(opts.department)
891
950
  }),
892
951
  outputOptions(cmd)
893
952
  );
@@ -916,19 +975,24 @@ function adminCommand() {
916
975
  );
917
976
  });
918
977
  const role = admin.command("role").description("Role management");
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) => {
978
+ role.command("list").description("List roles \u2192 {roles, total}").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 (system | business | custom)").action(async (opts, cmd) => {
920
979
  printJson(
921
- await clientFrom(cmd).admin.roleList({ keyword: opts.keyword, limit: opts.limit }),
980
+ await clientFrom(cmd).admin.roleList({
981
+ keyword: opts.keyword,
982
+ limit: opts.limit,
983
+ offset: opts.offset,
984
+ source: opts.source
985
+ }),
922
986
  outputOptions(cmd)
923
987
  );
924
988
  });
925
989
  role.command("get <role>").description("Get a role by id").option("--view <mode>", "detail view mode").action(async (roleId, _opts, cmd) => {
926
990
  printJson(await clientFrom(cmd).admin.roleGet(roleId), outputOptions(cmd));
927
991
  });
928
- role.command("members <role>").description("List members of a role").option("--keyword <s>", "filter by keyword").option("--type <t>", "filter by member type (user|department|group|app)").option("--member <spec...>", "filter to specific member(s) '<type>:<id-or-name>'").option("--limit <n>", "page size", int, 100).option("--offset <n>", "page offset", int, 0).action(async (roleId, opts, cmd) => {
992
+ role.command("members <role>").description("List members of a role \u2192 {members, total}").option("--limit <n>", "page size (default: all members)", int).option("--offset <n>", "page offset", int).action(async (roleId, opts, cmd) => {
929
993
  printJson(
930
994
  await clientFrom(cmd).admin.roleMembers(roleId, {
931
- keyword: opts.keyword,
995
+ offset: opts.offset,
932
996
  limit: opts.limit
933
997
  }),
934
998
  outputOptions(cmd)
@@ -968,7 +1032,10 @@ function adminCommand() {
968
1032
  ["grant-perm", true],
969
1033
  ["revoke-perm", false]
970
1034
  ]) {
971
- role.command(`${verb} <role>`).description(`${grant ? "Grant" : "Revoke"} a permission on a custom role (403 on built-in)`).requiredOption("--resource-type <t>", "resource type (e.g. catalog)").option("--resource-id <id>", "resource id ('*' = whole type)", "*").requiredOption("--operations <list>", "comma-separated operations").action(async (roleId, opts, cmd) => {
1035
+ role.command(`${verb} <role>`).description(`${grant ? "Grant" : "Revoke"} a permission on a custom role (403 on built-in)`).requiredOption(
1036
+ "--resource-type <t>",
1037
+ "resource type: knowledge_network|catalog|resource|tool_box|function|mcp|skill"
1038
+ ).option("--resource-id <id>", "resource id ('*' = whole type)", "*").requiredOption("--operations <list>", "comma-separated operations").action(async (roleId, opts, cmd) => {
972
1039
  printJson(
973
1040
  await clientFrom(cmd).admin.rolePermission(
974
1041
  roleId,
@@ -1003,8 +1070,8 @@ function adminCommand() {
1003
1070
  const isLlm = kind === "llm";
1004
1071
  const m = admin.command(kind).description(`${kind} management`);
1005
1072
  const ns = isLlm ? "llm" : "small";
1006
- const list = m.command("list").description(`List ${kind} models`).option("--name <s>", "filter by name").option("--page <n>", "page", int, 1).option("--size <n>", "page size", int, DEFAULT_LIST_LIMIT);
1007
- (isLlm ? list.option("--series <s>", "model series") : list.option("--type <t>", "model type")).action(async (opts, cmd) => {
1073
+ const list2 = m.command("list").description(`List ${kind} models`).option("--name <s>", "filter by name").option("--page <n>", "page", int, 1).option("--size <n>", "page size", int, DEFAULT_LIST_LIMIT);
1074
+ list2.option("--type <t>", "model type filter").action(async (opts, cmd) => {
1008
1075
  printJson(
1009
1076
  await clientFrom(cmd).models[ns].list({
1010
1077
  name: opts.name,
@@ -1080,18 +1147,34 @@ function adminCommand() {
1080
1147
  ).action(async (_opts, cmd) => {
1081
1148
  printJson(await clientFrom(cmd).admin.licenseFingerprint(), outputOptions(cmd));
1082
1149
  });
1083
- admin.command("audit").description("Audit log queries").command("list").description("List login audit events").option("--user <name>", "filter by user").option("--start <time>", "start time").option("--end <time>", "end time").option("--page <n>", "page", int, 1).option("--size <n>", "page size", int, 30).action(async (opts, cmd) => {
1150
+ const audit = admin.command("audit").description("Audit trail: management mutations and token-gate refusals (bkn-safe)");
1151
+ audit.command("list").description("List audit log entries, newest first \u2192 {logs, total}").option("--actor-id <id>", "token subject id of the actor").option("--request-id <id>", "all rows written for one mutating HTTP request").option("--resource <noun>", "route noun, e.g. users, role-bindings, policies").option("--action <verb>", "business verb, e.g. create, grant, revoke, reset_password").option("--target-id <id>", "target id").option("--failed-only", "only refused and failed attempts (HTTP 4xx/5xx)").option("--from <time>", "created at or after this RFC 3339 timestamp (inclusive)").option("--to <time>", "created before this RFC 3339 timestamp (exclusive)").option("--before-id <id>", "keyset tiebreaker for rows sharing the --to timestamp").option("--offset <n>", "rows to skip", int).option("--limit <n>", `page size, 0..${AUDIT_LOG_MAX_LIMIT}`, int, DEFAULT_LIST_LIMIT).addOption(new Option("--start <time>", "alias of --from").hideHelp()).addOption(new Option("--end <time>", "alias of --to").hideHelp()).action(async (opts, cmd) => {
1152
+ if (opts.start !== void 0 && opts.from !== void 0) {
1153
+ throw new InputError("--start is an alias of --from; pass only one.");
1154
+ }
1155
+ if (opts.end !== void 0 && opts.to !== void 0) {
1156
+ throw new InputError("--end is an alias of --to; pass only one.");
1157
+ }
1084
1158
  printJson(
1085
1159
  await clientFrom(cmd).admin.auditList({
1086
- user: opts.user,
1087
- start: opts.start,
1088
- end: opts.end,
1089
- page: opts.page,
1090
- size: opts.size
1160
+ actorId: opts.actorId,
1161
+ requestId: opts.requestId,
1162
+ resource: opts.resource,
1163
+ action: opts.action,
1164
+ targetId: opts.targetId,
1165
+ failedOnly: Boolean(opts.failedOnly),
1166
+ from: opts.from ?? opts.start,
1167
+ to: opts.to ?? opts.end,
1168
+ beforeId: opts.beforeId,
1169
+ offset: opts.offset,
1170
+ limit: opts.limit
1091
1171
  }),
1092
1172
  outputOptions(cmd)
1093
1173
  );
1094
1174
  });
1175
+ audit.command("get <id>").description("Get one audit log entry").action(async (id, _opts, cmd) => {
1176
+ printJson(await clientFrom(cmd).admin.auditGet(id), outputOptions(cmd));
1177
+ });
1095
1178
  const adminConfig = admin.command("config").description("Admin CLI config (active platform)");
1096
1179
  adminConfig.command("show").description("Show the active platform").action((_opts, cmd) => {
1097
1180
  printJson({ baseUrl: activePlatform() }, outputOptions(cmd));
@@ -1120,11 +1203,12 @@ function adminCommand() {
1120
1203
  baseUrl: g.baseUrl,
1121
1204
  token: g.token,
1122
1205
  user: g.user,
1123
- businessDomain: g.bizDomain,
1124
- insecure: g.insecure
1206
+ insecure: g.insecure,
1207
+ versionCheckMode: "cli",
1208
+ ...retryOptionsFrom(g)
1125
1209
  }),
1126
1210
  url,
1127
- { method: opts.request, header: opts.header, data: opts.data, businessDomain: g.bizDomain }
1211
+ { method: opts.request, header: opts.header, data: opts.data }
1128
1212
  );
1129
1213
  const out = outputOptions(cmd);
1130
1214
  try {
@@ -1218,129 +1302,155 @@ function appkeyCommand() {
1218
1302
 
1219
1303
  // src/commands/bkn.ts
1220
1304
  import { Command as Command4 } from "commander";
1221
-
1222
- // src/utils/bkn-validate.ts
1223
- import { existsSync, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
1224
- import { join, resolve } from "path";
1225
- var BKN_OBJECT_NAME_MAX_LENGTH = 40;
1226
- function parseFrontmatter(text) {
1227
- if (!text.startsWith("---")) return null;
1228
- const end = text.indexOf("\n---", 3);
1229
- if (end === -1) return null;
1230
- const block = text.slice(3, end);
1231
- const fm = {};
1232
- for (const line of block.split("\n")) {
1233
- const m = line.match(/^\s*(type|id|name)\s*:\s*(.+?)\s*$/);
1234
- if (m?.[1]) fm[m[1]] = m[2]?.replace(/^["']|["']$/g, "");
1305
+ var int3 = (v) => Number.parseInt(v, 10);
1306
+ function rejectUnknownSearchOptions(cmd) {
1307
+ let root = cmd;
1308
+ while (root.parent) root = root.parent;
1309
+ const raw = root.rawArgs ?? [];
1310
+ const separator = raw.indexOf("--");
1311
+ const afterSeparator = separator < 0 ? [] : raw.slice(separator + 1);
1312
+ for (const arg of cmd.args) {
1313
+ if (/^--[A-Za-z]/.test(arg) && !afterSeparator.includes(arg)) {
1314
+ throw new InputError(
1315
+ `unknown option '${arg}' for bkn search (see --help); to search for text that starts with "--", put it after a -- separator`
1316
+ );
1317
+ }
1318
+ }
1319
+ if (cmd.args.length > 2) {
1320
+ throw new InputError(
1321
+ `bkn search takes <kn-id> <query>, received ${cmd.args.length} arguments; quote a multi-word query`
1322
+ );
1323
+ }
1324
+ }
1325
+ var SYSTEM_PROPERTIES = ["_instance_id", "_instance_identity", "_display"];
1326
+ var DIRECTIONS = ["asc", "desc"];
1327
+ var NAME_OR_UPDATE_TIME = ["update_time", "name"];
1328
+ var SCHEDULE_SORTS = [
1329
+ "create_time",
1330
+ "update_time",
1331
+ "next_run_time",
1332
+ "last_run_time",
1333
+ "name"
1334
+ ];
1335
+ var CAPABILITY_SORTS = ["create_time", "update_time"];
1336
+ var sortDirection = oneOf("--direction", DIRECTIONS);
1337
+ var IMPORT_MODES = ["normal", "overwrite", "ignore"];
1338
+ var BINDING_POLICIES = ["preserve", "detach"];
1339
+ function knListLimit(v) {
1340
+ if (v === "-1") return -1;
1341
+ const n = /^\d+$/.test(v) ? Number(v) : Number.NaN;
1342
+ if (!(n >= 1 && n <= 1e3)) {
1343
+ throw new InputError(`--limit must be an integer from 1 to 1000, or -1 for all (got '${v}')`);
1235
1344
  }
1236
- return fm;
1345
+ return n;
1237
1346
  }
1238
- function bknFiles(dir) {
1239
- if (!existsSync(dir) || !statSync(dir).isDirectory()) return [];
1240
- return readdirSync(dir).filter((f) => f.endsWith(".bkn")).map((f) => join(dir, f));
1347
+ var nonNegativeInt = (flag) => (v) => {
1348
+ const n = /^\d+$/.test(v) ? Number(v) : Number.NaN;
1349
+ if (!Number.isSafeInteger(n)) {
1350
+ throw new InputError(`${flag} must be a non-negative integer (got '${v}')`);
1351
+ }
1352
+ return n;
1353
+ };
1354
+ function actionLogKeyword(v) {
1355
+ if (v.length > 128) {
1356
+ throw new InputError(`--keyword must be at most 128 characters (got ${v.length})`);
1357
+ }
1358
+ return v;
1241
1359
  }
1242
- function endpointRefs(text) {
1243
- const refs = [];
1244
- const idx = text.indexOf("### Endpoint");
1245
- if (idx === -1) return refs;
1246
- const after = text.slice(idx + "### Endpoint".length);
1247
- const next = after.indexOf("\n###");
1248
- const section = next === -1 ? after : after.slice(0, next);
1249
- for (const line of section.split("\n")) {
1250
- const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
1251
- if (cells.length < 2) continue;
1252
- const [source, target] = cells;
1253
- if (!source || !target) continue;
1254
- if (/^source$/i.test(source) || /^-+$/.test(source)) continue;
1255
- refs.push({ source, target });
1360
+ function writeModeFlags(cmd, withImportMode) {
1361
+ cmd.option("--no-strict-mode", "skip dependency validation (strict_mode=false)");
1362
+ if (withImportMode) {
1363
+ cmd.option(
1364
+ "--import-mode <mode>",
1365
+ "existing ids/names: normal (default, error) | overwrite | ignore",
1366
+ oneOf("--import-mode", IMPORT_MODES)
1367
+ );
1256
1368
  }
1257
- return refs;
1369
+ return cmd;
1258
1370
  }
1259
- function validateBknDirectory(dirPath) {
1260
- const dir = resolve(dirPath);
1261
- const errors = [];
1262
- const warnings = [];
1263
- if (!existsSync(dir) || !statSync(dir).isDirectory()) {
1264
- return {
1265
- valid: false,
1266
- dir,
1267
- counts: { objectTypes: 0, relationTypes: 0, conceptGroups: 0 },
1268
- errors: [`Not a directory: ${dir}`],
1269
- warnings: []
1270
- };
1371
+ function writeModes(opts) {
1372
+ return {
1373
+ branch: opts.branch,
1374
+ strictMode: opts.strictMode === false ? false : void 0,
1375
+ importMode: opts.importMode
1376
+ };
1377
+ }
1378
+ function systemProperties(value) {
1379
+ const list2 = csv(value);
1380
+ for (const p of list2 ?? []) oneOf("--exclude-system-properties", SYSTEM_PROPERTIES)(p);
1381
+ return list2;
1382
+ }
1383
+ var CAPABILITY_TYPES = ["skill", "function", "mcp_tool"];
1384
+ function capabilityEntries(opts) {
1385
+ const targets = [opts.skill, opts.box, opts.mcp].filter((v) => v !== void 0);
1386
+ if (targets.length !== 1) {
1387
+ throw new InputError(
1388
+ "Name exactly one of --skill <ids>, --box <box-id> or --mcp <mcp-id> to attach."
1389
+ );
1271
1390
  }
1272
- const networkPath = join(dir, "network.bkn");
1273
- if (!existsSync(networkPath)) {
1274
- errors.push("Missing network.bkn at the BKN root.");
1275
- } else {
1276
- const fm = parseFrontmatter(readFileSync3(networkPath, "utf8"));
1277
- if (!fm) errors.push("network.bkn has no frontmatter block.");
1278
- else {
1279
- if (fm.type !== "knowledge_network")
1280
- errors.push(`network.bkn type must be 'knowledge_network', got '${fm.type ?? ""}'.`);
1281
- if (!fm.id) errors.push("network.bkn is missing 'id'.");
1282
- if (!fm.name) errors.push("network.bkn is missing 'name'.");
1391
+ const comment = opts.comment || void 0;
1392
+ if (opts.skill !== void 0) {
1393
+ if (opts.tool !== void 0 || opts.allTools) {
1394
+ throw new InputError("--tool and --all-tools go with --box or --mcp, not --skill.");
1283
1395
  }
1396
+ const ids = csv(opts.skill) ?? [];
1397
+ if (ids.length === 0) throw new InputError("--skill needs at least one skill id.");
1398
+ return ids.map((id) => ({ capability_type: "skill", capability_id: id, comment }));
1284
1399
  }
1285
- const otIds = /* @__PURE__ */ new Set();
1286
- const otFiles = bknFiles(join(dir, "object_types"));
1287
- for (const file of otFiles) {
1288
- const fm = parseFrontmatter(readFileSync3(file, "utf8"));
1289
- const rel = file.slice(dir.length + 1);
1290
- if (!fm || fm.type !== "object_type") {
1291
- errors.push(`${rel}: not a valid object_type (missing/wrong frontmatter type).`);
1292
- continue;
1293
- }
1294
- if (!fm.id) errors.push(`${rel}: object_type missing 'id'.`);
1295
- if (!fm.name) errors.push(`${rel}: object_type missing 'name'.`);
1296
- if (fm.name && [...fm.name].length > BKN_OBJECT_NAME_MAX_LENGTH) {
1297
- errors.push(
1298
- `${rel}: object_type name exceeds ${BKN_OBJECT_NAME_MAX_LENGTH} codepoints ('${fm.name}').`
1299
- );
1300
- }
1301
- if (fm.id) {
1302
- if (otIds.has(fm.id)) errors.push(`Duplicate object_type id '${fm.id}'.`);
1303
- otIds.add(fm.id);
1304
- }
1400
+ const flag = opts.box !== void 0 ? "--box" : "--mcp";
1401
+ const owner = (opts.box ?? opts.mcp ?? "").trim();
1402
+ const type = opts.box !== void 0 ? "function" : "mcp_tool";
1403
+ if (!owner) throw new InputError(`${flag} needs an id.`);
1404
+ if (opts.tool !== void 0 && opts.allTools) {
1405
+ throw new InputError("Use --tool or --all-tools, not both.");
1305
1406
  }
1306
- const rtFiles = bknFiles(join(dir, "relation_types"));
1307
- for (const file of rtFiles) {
1308
- const text = readFileSync3(file, "utf8");
1309
- const fm = parseFrontmatter(text);
1310
- const rel = file.slice(dir.length + 1);
1311
- if (!fm || fm.type !== "relation_type") {
1312
- errors.push(`${rel}: not a valid relation_type (missing/wrong frontmatter type).`);
1313
- continue;
1314
- }
1315
- if (!fm.id) errors.push(`${rel}: relation_type missing 'id'.`);
1316
- for (const { source, target } of endpointRefs(text)) {
1317
- if (otIds.size > 0 && !otIds.has(source))
1318
- warnings.push(`${rel}: endpoint source '${source}' is not a known object_type.`);
1319
- if (otIds.size > 0 && !otIds.has(target))
1320
- warnings.push(`${rel}: endpoint target '${target}' is not a known object_type.`);
1321
- }
1407
+ if (opts.allTools) return [{ capability_type: type, box_id: owner, all_tools: true, comment }];
1408
+ const tools = csv(opts.tool) ?? [];
1409
+ if (tools.length === 0) {
1410
+ throw new InputError(
1411
+ `${flag} binds tools, not the container: add --tool <ids> or --all-tools.`
1412
+ );
1413
+ }
1414
+ return tools.map((tool) => ({
1415
+ capability_type: type,
1416
+ box_id: owner,
1417
+ capability_id: tool,
1418
+ comment
1419
+ }));
1420
+ }
1421
+ function warnUnboundCapabilities(result, declared) {
1422
+ if (declared.malformed) {
1423
+ process.stderr.write(
1424
+ `warning: network.bkn capabilities ${declared.malformed}; none of it was bound.
1425
+ `
1426
+ );
1427
+ }
1428
+ const report = result?.capabilities;
1429
+ const skipped = Array.isArray(report?.skipped) ? report.skipped.filter(
1430
+ (s) => typeof s === "object" && s !== null
1431
+ ) : [];
1432
+ if (skipped.length === 0) return;
1433
+ const noun = skipped.length === 1 ? "capability was" : "capabilities were";
1434
+ process.stderr.write(`warning: ${skipped.length} declared ${noun} not bound:
1435
+ `);
1436
+ for (const skip of skipped) {
1437
+ const owner = skip.box_name ? `${skip.box_name} / ` : "";
1438
+ const name = skip.name || skip.declared_id || "(unnamed)";
1439
+ const detail = skip.detail ? ` \u2014 ${skip.detail}` : "";
1440
+ process.stderr.write(` ${skip.capability_type} ${owner}${name}: ${skip.reason}${detail}
1441
+ `);
1322
1442
  }
1323
- const cgFiles = bknFiles(join(dir, "concept_groups"));
1324
- return {
1325
- valid: errors.length === 0,
1326
- dir,
1327
- counts: {
1328
- objectTypes: otFiles.length,
1329
- relationTypes: rtFiles.length,
1330
- conceptGroups: cgFiles.length
1331
- },
1332
- errors,
1333
- warnings
1334
- };
1335
1443
  }
1336
-
1337
- // src/commands/bkn.ts
1338
- var int3 = (v) => Number.parseInt(v, 10);
1339
1444
  function bknCommand() {
1340
1445
  const bkn = new Command4("bkn").description(
1341
1446
  "Knowledge networks: schema, metrics, search, import/export"
1342
1447
  );
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) => {
1448
+ bkn.command("list").description("List knowledge networks").option("--limit <n>", "page size, 1\u20131000, or -1 for all", knListLimit, 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(
1449
+ "--sort <field>",
1450
+ "update_time | name",
1451
+ oneOf("--sort", NAME_OR_UPDATE_TIME),
1452
+ "update_time"
1453
+ ).option("--direction <dir>", "asc | desc", sortDirection, "desc").action(async (_opts, cmd) => {
1344
1454
  const o = cmd.optsWithGlobals();
1345
1455
  const data = await clientFrom(cmd).kn.list({
1346
1456
  limit: o.limit,
@@ -1352,16 +1462,31 @@ function bknCommand() {
1352
1462
  });
1353
1463
  printJson(data, outputOptions(cmd));
1354
1464
  });
1355
- bkn.command("get <kn-id>").description("Get a knowledge network (use --stats or --export)").option("--stats", "include statistics").option("--export", "return the full export payload").action(async (knId, opts, cmd) => {
1465
+ bkn.command("get <kn-id>").description("Get a knowledge network (use --stats or --export)").option("--stats", "include statistics").option("--export", "return the full export payload").option("--branch <b>", "branch (default: main)").option(
1466
+ "--detail-level <level>",
1467
+ "full (default) | summary \u2014 sent as detail_level; the service applies it only with --export (openbkn-ai/bkn-foundry#1632)",
1468
+ oneOf("--detail-level", ["full", "summary"])
1469
+ ).action(async (knId, opts, cmd) => {
1356
1470
  const data = await clientFrom(cmd).kn.get(knId, {
1357
1471
  stats: opts.stats,
1358
- exportMode: opts.export
1472
+ exportMode: opts.export,
1473
+ branch: opts.branch,
1474
+ detailLevel: opts.detailLevel
1359
1475
  });
1360
1476
  printJson(data, outputOptions(cmd));
1361
1477
  });
1362
- bkn.command("search <kn-id> <query>").description(
1478
+ bkn.command("search <kn-id> <query>").allowUnknownOption().allowExcessArguments().description(
1363
1479
  "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) => {
1480
+ ).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(
1481
+ "--max-object-types <n>",
1482
+ "positive count of object types that may take part",
1483
+ positiveInt("--max-object-types")
1484
+ ).option(
1485
+ "--max-instances <n>",
1486
+ "positive count of instances per object type",
1487
+ positiveInt("--max-instances")
1488
+ ).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) => {
1489
+ rejectUnknownSearchOptions(cmd);
1365
1490
  const data = await clientFrom(cmd).kn.search(knId, query, {
1366
1491
  objectTypes: csv(opts.objectTypes),
1367
1492
  excludeObjectTypes: csv(opts.excludeObjectTypes),
@@ -1380,36 +1505,59 @@ function bknCommand() {
1380
1505
  ];
1381
1506
  for (const [name, listMethod, crud] of schemaGroups) {
1382
1507
  const g = bkn.command(name).description(`${name} list/get/...`);
1383
- g.command("list <kn-id>").description(`List ${name}s`).option("--branch <b>", "branch", "main").action(async (knId, opts, cmd) => {
1508
+ g.command("list <kn-id>").description(`List ${name}s`).option("--branch <b>", "branch", "main").option("--name-pattern <s>", "fuzzy name filter").option("--tag <s>", "exact tag filter").action(async (knId, opts, cmd) => {
1384
1509
  printJson(
1385
- await clientFrom(cmd).kn[listMethod](knId, { branch: opts.branch }),
1510
+ await clientFrom(cmd).kn[listMethod](knId, {
1511
+ branch: opts.branch,
1512
+ namePattern: opts.namePattern,
1513
+ tag: opts.tag
1514
+ }),
1386
1515
  outputOptions(cmd)
1387
1516
  );
1388
1517
  });
1389
1518
  if (crud) {
1390
- g.command("get <kn-id> <id>").description(`Get ${name} \u2192 {entries}`).action(async (knId, id, _o, cmd) => {
1391
- printJson(await clientFrom(cmd).kn[`${crud}Get`](knId, id), outputOptions(cmd));
1519
+ g.command("get <kn-id> <id>").description(`Get ${name} \u2192 {entries}`).option("--branch <b>", "branch (default: main)").action(async (knId, id, opts, cmd) => {
1520
+ printJson(
1521
+ await clientFrom(cmd).kn[`${crud}Get`](knId, id, { branch: opts.branch }),
1522
+ outputOptions(cmd)
1523
+ );
1392
1524
  });
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) => {
1525
+ writeModeFlags(
1526
+ g.command("create <kn-id>").description(`Create ${name}s (--body / --body-file) \u2192 body is {entries:[\u2026]}`).option(
1527
+ "--body <json>",
1528
+ "body JSON {entries:[\u2026]} (a bare array is wrapped) \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1529
+ ).option("--body-file <path>", "read body JSON from a file").option("--branch <b>", "branch (default: main)"),
1530
+ true
1531
+ ).action(async (knId, opts, cmd) => {
1397
1532
  printJson(
1398
- await clientFrom(cmd).kn[`${crud}Create`](knId, readBody(opts)),
1533
+ await clientFrom(cmd).kn[`${crud}Create`](knId, readBody(opts), writeModes(opts)),
1399
1534
  outputOptions(cmd)
1400
1535
  );
1401
1536
  });
1402
- g.command("update <kn-id> <id>").description(`Update ${name} (--body / --body-file)`).option(
1537
+ g.command("update <kn-id> <id>").description(`Update ${name} (--body / --body-file); the body carries base_version`).option(
1403
1538
  "--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) => {
1539
+ "body JSON, including base_version from `get` \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1540
+ ).option("--body-file <path>", "read body JSON from a file").option("--branch <b>", "branch (default: main)").option("--no-strict-mode", "skip dependency validation (strict_mode=false)").action(async (knId, id, opts, cmd) => {
1406
1541
  printJson(
1407
- await clientFrom(cmd).kn[`${crud}Update`](knId, id, readBody(opts)),
1542
+ await clientFrom(cmd).kn[`${crud}Update`](knId, id, readBody(opts), {
1543
+ branch: opts.branch,
1544
+ strictMode: opts.strictMode === false ? false : void 0
1545
+ }),
1408
1546
  outputOptions(cmd)
1409
1547
  );
1410
1548
  });
1411
- g.command("delete <kn-id> <id>").description(`Delete ${name}`).option("-y, --yes", "skip confirmation").action(async (knId, id, _o, cmd) => {
1412
- printJson(await clientFrom(cmd).kn[`${crud}Delete`](knId, id), outputOptions(cmd));
1549
+ const del = g.command("delete <kn-id> <id>").description(`Delete ${name}`).option("-y, --yes", "skip confirmation").option("--branch <b>", "branch (default: main)");
1550
+ if (crud === "objectType") {
1551
+ del.option("--force", "delete even when a relation type still binds it (force_delete)");
1552
+ }
1553
+ del.action(async (knId, id, opts, cmd) => {
1554
+ printJson(
1555
+ await clientFrom(cmd).kn[`${crud}Delete`](knId, id, {
1556
+ branch: opts.branch,
1557
+ forceDelete: opts.force
1558
+ }),
1559
+ outputOptions(cmd)
1560
+ );
1413
1561
  });
1414
1562
  }
1415
1563
  }
@@ -1417,23 +1565,36 @@ function bknCommand() {
1417
1565
  actionType?.command("query <kn-id> <at-id>").description("Query an action type (--body / --body-file JSON)").option(
1418
1566
  "--body <json>",
1419
1567
  "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) => {
1568
+ ).option("--body-file <path>", "read query JSON from a file").option("--branch <b>", "branch (default: main)").option("--include-type-info", "include the action type definition").option(
1569
+ "--exclude-system-properties <list>",
1570
+ "drop system fields: _instance_id,_instance_identity,_display"
1571
+ ).action(async (knId, atId, opts, cmd) => {
1572
+ const excludeSystemProperties = systemProperties(opts.excludeSystemProperties);
1421
1573
  printJson(
1422
- await clientFrom(cmd).kn.actionTypeQuery(knId, atId, readBody(opts)),
1574
+ await clientFrom(cmd).kn.actionTypeQuery(knId, atId, readBody(opts), {
1575
+ branch: opts.branch,
1576
+ includeTypeInfo: opts.includeTypeInfo,
1577
+ excludeSystemProperties
1578
+ }),
1423
1579
  outputOptions(cmd)
1424
1580
  );
1425
1581
  });
1426
1582
  actionType?.command("execute <kn-id> <at-id>").description("Execute an action type (--body / --body-file envelope JSON)").option(
1427
1583
  "--body <json>",
1428
1584
  "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) => {
1585
+ ).option("--body-file <path>", "read envelope JSON from a file").option("--branch <b>", "branch (default: main)").action(async (knId, atId, opts, cmd) => {
1430
1586
  printJson(
1431
- await clientFrom(cmd).kn.actionTypeExecute(knId, atId, readBody(opts)),
1587
+ await clientFrom(cmd).kn.actionTypeExecute(knId, atId, readBody(opts), {
1588
+ branch: opts.branch
1589
+ }),
1432
1590
  outputOptions(cmd)
1433
1591
  );
1434
1592
  });
1435
- actionType?.command("get <kn-id> <at-id>").description("Get an action type").action(async (knId, atId, _o, cmd) => {
1436
- printJson(await clientFrom(cmd).kn.actionTypeGet(knId, atId), outputOptions(cmd));
1593
+ actionType?.command("get <kn-id> <at-id>").description("Get an action type").option("--branch <b>", "branch (default: main)").action(async (knId, atId, opts, cmd) => {
1594
+ printJson(
1595
+ await clientFrom(cmd).kn.actionTypeGet(knId, atId, { branch: opts.branch }),
1596
+ outputOptions(cmd)
1597
+ );
1437
1598
  });
1438
1599
  bkn.command("stats <kn-id>").description("Get knowledge-network statistics (alias for get --stats)").action(async (knId, _opts, cmd) => {
1439
1600
  printJson(await clientFrom(cmd).kn.get(knId, { stats: true }), outputOptions(cmd));
@@ -1445,46 +1606,159 @@ function bknCommand() {
1445
1606
  objectType?.command("query <kn-id> <ot-id>").description("Query instances of an object type (--body / --body-file JSON)").option(
1446
1607
  "--body <json>",
1447
1608
  "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) => {
1609
+ ).option("--body-file <path>", "read query JSON from a file").option("--branch <b>", "branch (default: main)").option("--include-type-info", "include the object type definition").option("--include-logic-params", "include the computation parameters of logic properties").option(
1610
+ "--exclude-system-properties <list>",
1611
+ "drop system fields: _instance_id,_instance_identity,_display"
1612
+ ).option("--ignoring-store-cache", "skip the index and read the store directly").addHelpText(
1613
+ "after",
1614
+ `
1615
+ The first page sends {limit, condition?, sort?, need_total?, properties?}. How the next
1616
+ page is asked for depends on the deploy: one that includes foundry #1623 answers with
1617
+ paging.next_cursor (null on the last page) \u2014 resend the query with "cursor" set to it.
1618
+ An older deploy answers without paging \u2014 resend the query with "offset" instead.
1619
+
1620
+ openbkn bkn object-type query <kn-id> <ot-id> --body '{"limit": 20}'
1621
+ openbkn bkn object-type query <kn-id> <ot-id> --body '{"limit": 20, "cursor": "<next_cursor>"}'
1622
+ openbkn bkn object-type query <kn-id> <ot-id> --body '{"limit": 20, "offset": 20}' # older deploys`
1623
+ ).action(async (knId, otId, opts, cmd) => {
1624
+ const excludeSystemProperties = systemProperties(opts.excludeSystemProperties);
1449
1625
  printJson(
1450
- await clientFrom(cmd).kn.objectTypeQuery(knId, otId, readBody(opts)),
1626
+ await clientFrom(cmd).kn.objectTypeQuery(knId, otId, readBody(opts), {
1627
+ branch: opts.branch,
1628
+ includeTypeInfo: opts.includeTypeInfo,
1629
+ includeLogicParams: opts.includeLogicParams,
1630
+ excludeSystemProperties,
1631
+ ignoringStoreCache: opts.ignoringStoreCache
1632
+ }),
1451
1633
  outputOptions(cmd)
1452
1634
  );
1453
1635
  });
1454
- bkn.command("create <name>").description("Create an (empty) knowledge network").option("--branch <b>", "branch", "main").action(async (name, opts, cmd) => {
1455
- printJson(await clientFrom(cmd).kn.create({ name, branch: opts.branch }), outputOptions(cmd));
1636
+ bkn.command("create <name>").description("Create an (empty) knowledge network").option("--branch <b>", "branch, sent in both the query and the body", "main").option(
1637
+ "--import-mode <mode>",
1638
+ "an existing id/name: normal (default, error) | overwrite | ignore",
1639
+ oneOf("--import-mode", IMPORT_MODES)
1640
+ ).option("--no-strict-mode", "skip dependency validation (strict_mode=false)").option(
1641
+ "--binding-policy <policy>",
1642
+ "environment-local bindings: preserve (default) | detach",
1643
+ oneOf("--binding-policy", BINDING_POLICIES)
1644
+ ).action(async (name, opts, cmd) => {
1645
+ printJson(
1646
+ await clientFrom(cmd).kn.create({
1647
+ name,
1648
+ ...writeModes(opts),
1649
+ bindingPolicy: opts.bindingPolicy
1650
+ }),
1651
+ outputOptions(cmd)
1652
+ );
1456
1653
  });
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) => {
1461
- printJson(await clientFrom(cmd).kn.update(knId, readBody(opts)), outputOptions(cmd));
1654
+ writeModeFlags(
1655
+ bkn.command("update <kn-id>").description("Update a knowledge network (--body / --body-file)").option(
1656
+ "--body <json>",
1657
+ "update body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1658
+ ).option("--body-file <path>", "read update body JSON from a file").option("--branch <b>", "branch (default: the body's branch, else main)"),
1659
+ true
1660
+ ).action(async (knId, opts, cmd) => {
1661
+ printJson(
1662
+ await clientFrom(cmd).kn.update(knId, readBody(opts), writeModes(opts)),
1663
+ outputOptions(cmd)
1664
+ );
1462
1665
  });
1463
- bkn.command("delete <kn-id>").description("Delete a knowledge network").option("-y, --yes", "skip confirmation").action(async (knId, _opts, cmd) => {
1464
- printJson(await clientFrom(cmd).kn.delete(knId), outputOptions(cmd));
1666
+ bkn.command("delete <kn-id>").description("Delete a knowledge network").option("-y, --yes", "skip confirmation").option("--branch <b>", "branch (default: main)").action(async (knId, opts, cmd) => {
1667
+ printJson(await clientFrom(cmd).kn.delete(knId, { branch: opts.branch }), outputOptions(cmd));
1465
1668
  });
1466
1669
  bkn.command("subgraph <kn-id>").description("Query a subgraph (--body / --body-file JSON)").option(
1467
1670
  "--body <json>",
1468
1671
  "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) => {
1470
- printJson(await clientFrom(cmd).kn.subgraph(knId, readBody(opts)), outputOptions(cmd));
1672
+ ).option("--body-file <path>", "read subgraph query JSON from a file").option(
1673
+ "--query-type <type>",
1674
+ "relation_path to follow given paths (default: explore from a start point)",
1675
+ oneOf("--query-type", ["", "relation_path"])
1676
+ ).option("--branch <b>", "branch (default: main)").option("--include-logic-params", "include the computation parameters of logic properties").option(
1677
+ "--exclude-system-properties <list>",
1678
+ "drop system fields: _instance_id,_instance_identity,_display"
1679
+ ).option("--ignoring-store-cache", "skip the index and read the store directly").action(async (knId, opts, cmd) => {
1680
+ const excludeSystemProperties = systemProperties(opts.excludeSystemProperties);
1681
+ printJson(
1682
+ await clientFrom(cmd).kn.subgraph(knId, readBody(opts), {
1683
+ queryType: opts.queryType,
1684
+ branch: opts.branch,
1685
+ includeLogicParams: opts.includeLogicParams,
1686
+ excludeSystemProperties,
1687
+ ignoringStoreCache: opts.ignoringStoreCache
1688
+ }),
1689
+ outputOptions(cmd)
1690
+ );
1471
1691
  });
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) => {
1692
+ const actionLog = bkn.command("action-log").description("Action logs \u2014 list/get/cancel; list pages with --search-after");
1693
+ actionLog.command("list <kn-id>").description("List action logs").option(
1694
+ "--status <s>",
1695
+ "pending | running | completed | failed | cancelled",
1696
+ oneOf("--status", ["pending", "running", "completed", "failed", "cancelled"])
1697
+ ).option("--action-type-id <id>", "filter by action type").option(
1698
+ "--trigger-type <t>",
1699
+ "manual | scheduled",
1700
+ oneOf("--trigger-type", ["manual", "scheduled"])
1701
+ ).option(
1702
+ "--keyword <s>",
1703
+ "case-insensitive substring of the execution id (max 128 characters)",
1704
+ actionLogKeyword
1705
+ ).option("--start-time-from <ms>", "start time lower bound, epoch milliseconds", int3).option("--start-time-to <ms>", "start time upper bound, epoch milliseconds", int3).option("--limit <n>", "page size, 1\u20131000", positiveInt("--limit", 1e3), DEFAULT_LIST_LIMIT).option(
1706
+ "--offset <n>",
1707
+ "page offset; not allowed with --search-after",
1708
+ nonNegativeInt("--offset")
1709
+ ).option("--need-total", "also return total_count").option("--search-after <cursor>", "deep-paging cursor from the previous page (comma-joined)").action(async (knId, opts, cmd) => {
1710
+ if (opts.searchAfter !== void 0 && opts.offset !== void 0) {
1711
+ throw new InputError("--offset cannot be combined with --search-after; use one of them");
1712
+ }
1474
1713
  printJson(
1475
1714
  await clientFrom(cmd).kn.actionLogs(knId, {
1476
1715
  status: opts.status,
1477
1716
  actionTypeId: opts.actionTypeId,
1478
- limit: opts.limit
1717
+ triggerType: opts.triggerType,
1718
+ keyword: opts.keyword,
1719
+ startTimeFrom: opts.startTimeFrom,
1720
+ startTimeTo: opts.startTimeTo,
1721
+ limit: opts.limit,
1722
+ offset: opts.offset,
1723
+ needTotal: opts.needTotal,
1724
+ searchAfter: opts.searchAfter
1479
1725
  }),
1480
1726
  outputOptions(cmd)
1481
1727
  );
1482
1728
  });
1483
- actionLog.command("get <kn-id> <log-id>").description("Get an action log").action(async (knId, logId, _opts, cmd) => {
1484
- printJson(await clientFrom(cmd).kn.actionLog(knId, logId), outputOptions(cmd));
1729
+ actionLog.command("get <kn-id> <log-id>").description("Get an action log").option(
1730
+ "--results-limit <n>",
1731
+ "page size of the embedded results, 1\u20131000",
1732
+ positiveInt("--results-limit", 1e3)
1733
+ ).option(
1734
+ "--results-offset <n>",
1735
+ "offset into the embedded results; offset + limit must not exceed 10000",
1736
+ nonNegativeInt("--results-offset")
1737
+ ).option(
1738
+ "--results-status <s>",
1739
+ "success | failed",
1740
+ oneOf("--results-status", ["success", "failed"])
1741
+ ).action(async (knId, logId, opts, cmd) => {
1742
+ const window = (opts.resultsOffset ?? 0) + (opts.resultsLimit ?? 100);
1743
+ if (opts.resultsOffset !== void 0 && window > 1e4) {
1744
+ throw new InputError(
1745
+ `--results-offset + --results-limit must not exceed 10000 (got ${window})`
1746
+ );
1747
+ }
1748
+ printJson(
1749
+ await clientFrom(cmd).kn.actionLog(knId, logId, {
1750
+ resultsLimit: opts.resultsLimit,
1751
+ resultsOffset: opts.resultsOffset,
1752
+ resultsStatus: opts.resultsStatus
1753
+ }),
1754
+ outputOptions(cmd)
1755
+ );
1485
1756
  });
1486
- actionLog.command("cancel <kn-id> <log-id>").description("Cancel a running action").action(async (knId, logId, _opts, cmd) => {
1487
- printJson(await clientFrom(cmd).kn.cancelActionLog(knId, logId), outputOptions(cmd));
1757
+ actionLog.command("cancel <kn-id> <log-id>").description("Cancel a running action").option("--reason <text>", "why it was cancelled").action(async (knId, logId, opts, cmd) => {
1758
+ printJson(
1759
+ await clientFrom(cmd).kn.cancelActionLog(knId, logId, { reason: opts.reason }),
1760
+ outputOptions(cmd)
1761
+ );
1488
1762
  });
1489
1763
  bkn.command("action-execution <kn-id> <execution-id>").description("Get action execution status").action(async (knId, execId, _opts, cmd) => {
1490
1764
  printJson(await clientFrom(cmd).kn.actionExecution(knId, execId), outputOptions(cmd));
@@ -1493,141 +1767,297 @@ function bknCommand() {
1493
1767
  metric.command("query <kn-id> <metric-id>").description("Query a metric's data (--body / --body-file JSON)").option(
1494
1768
  "--body <json>",
1495
1769
  "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) => {
1770
+ ).option("--body-file <path>", "read query JSON from a file").option("--branch <b>", "branch (default: main)").option("--fill-null", "align trend series to every bucket, filling gaps with null").action(async (knId, metricId, opts, cmd) => {
1497
1771
  printJson(
1498
- await clientFrom(cmd).kn.metricQuery(knId, metricId, readBody(opts)),
1772
+ await clientFrom(cmd).kn.metricQuery(knId, metricId, readBody(opts), {
1773
+ branch: opts.branch,
1774
+ fillNull: opts.fillNull
1775
+ }),
1499
1776
  outputOptions(cmd)
1500
1777
  );
1501
1778
  });
1502
1779
  metric.command("dry-run <kn-id>").description("Dry-run a metric definition (--body / --body-file JSON)").option(
1503
1780
  "--body <json>",
1504
1781
  "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) => {
1506
- printJson(await clientFrom(cmd).kn.metricDryRun(knId, readBody(opts)), outputOptions(cmd));
1782
+ ).option("--body-file <path>", "read metric definition JSON from a file").option("--branch <b>", "branch (default: main)").option("--fill-null", "align trend series to every bucket, filling gaps with null").action(async (knId, opts, cmd) => {
1783
+ printJson(
1784
+ await clientFrom(cmd).kn.metricDryRun(knId, readBody(opts), {
1785
+ branch: opts.branch,
1786
+ fillNull: opts.fillNull
1787
+ }),
1788
+ outputOptions(cmd)
1789
+ );
1507
1790
  });
1508
- metric.command("list <kn-id>").description("List metrics").action(async (knId, _o, cmd) => {
1509
- printJson(await clientFrom(cmd).kn.metricList(knId), outputOptions(cmd));
1791
+ metric.command("list <kn-id>").description("List metrics").option("--branch <b>", "branch", "main").option("--name-pattern <s>", "fuzzy name filter").option("--tag <s>", "exact tag filter").action(async (knId, opts, cmd) => {
1792
+ printJson(
1793
+ await clientFrom(cmd).kn.metricList(knId, {
1794
+ branch: opts.branch,
1795
+ namePattern: opts.namePattern,
1796
+ tag: opts.tag
1797
+ }),
1798
+ outputOptions(cmd)
1799
+ );
1510
1800
  });
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) => {
1512
- printJson(await clientFrom(cmd).kn.metricGet(knId, id), outputOptions(cmd));
1801
+ metric.command("get <kn-id> <metric-id>").description("Get a metric \u2192 {entries}, since the route takes a list of ids").option("--branch <b>", "branch (default: main)").action(async (knId, id, opts, cmd) => {
1802
+ printJson(
1803
+ await clientFrom(cmd).kn.metricGet(knId, id, { branch: opts.branch }),
1804
+ outputOptions(cmd)
1805
+ );
1513
1806
  });
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) => {
1518
- printJson(await clientFrom(cmd).kn.metricCreate(knId, readBody(opts)), outputOptions(cmd));
1807
+ writeModeFlags(
1808
+ metric.command("create <kn-id>").description("Create metrics (--body / --body-file) \u2192 body is {entries:[\u2026]}").option(
1809
+ "--body <json>",
1810
+ "body JSON {entries:[\u2026]} (a bare array is wrapped) \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1811
+ ).option("--body-file <path>", "read body JSON from a file").option("--branch <b>", "branch (default: main)"),
1812
+ true
1813
+ ).action(async (knId, opts, cmd) => {
1814
+ printJson(
1815
+ await clientFrom(cmd).kn.metricCreate(knId, readBody(opts), writeModes(opts)),
1816
+ outputOptions(cmd)
1817
+ );
1519
1818
  });
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) => {
1819
+ writeModeFlags(
1820
+ metric.command("update <kn-id> <metric-id>").description("Update a metric (--body / --body-file)").option(
1821
+ "--body <json>",
1822
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1823
+ ).option("--body-file <path>", "read body JSON from a file").option("--branch <b>", "branch (default: main)"),
1824
+ false
1825
+ ).action(async (knId, id, opts, cmd) => {
1524
1826
  printJson(
1525
- await clientFrom(cmd).kn.metricUpdate(knId, id, readBody(opts)),
1827
+ await clientFrom(cmd).kn.metricUpdate(knId, id, readBody(opts), writeModes(opts)),
1526
1828
  outputOptions(cmd)
1527
1829
  );
1528
1830
  });
1529
- metric.command("delete <kn-id> <metric-id>").description("Delete a metric").action(async (knId, id, _o, cmd) => {
1530
- printJson(await clientFrom(cmd).kn.metricDelete(knId, id), outputOptions(cmd));
1831
+ metric.command("delete <kn-id> <metric-id>").description("Delete a metric").option("--branch <b>", "branch (default: main)").action(async (knId, id, opts, cmd) => {
1832
+ printJson(
1833
+ await clientFrom(cmd).kn.metricDelete(knId, id, { branch: opts.branch }),
1834
+ outputOptions(cmd)
1835
+ );
1531
1836
  });
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) => {
1536
- printJson(await clientFrom(cmd).kn.metricValidate(knId, readBody(opts)), outputOptions(cmd));
1837
+ writeModeFlags(
1838
+ metric.command("validate <kn-id>").description("Validate a metric definition (--body / --body-file)").option(
1839
+ "--body <json>",
1840
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1841
+ ).option("--body-file <path>", "read body JSON from a file").option("--branch <b>", "branch (default: main)"),
1842
+ true
1843
+ ).action(async (knId, opts, cmd) => {
1844
+ printJson(
1845
+ await clientFrom(cmd).kn.metricValidate(knId, readBody(opts), writeModes(opts)),
1846
+ outputOptions(cmd)
1847
+ );
1537
1848
  });
1538
1849
  const cg = bkn.command("concept-group").description("Concept groups \u2014 list/get");
1539
- cg.command("list <kn-id>").description("List concept groups").action(async (knId, _o, cmd) => {
1540
- printJson(await clientFrom(cmd).kn.conceptGroups(knId), outputOptions(cmd));
1850
+ cg.command("list <kn-id>").description("List concept groups (all of them unless --limit is given)").option("--branch <b>", "branch (default: main)").option("--name-pattern <s>", "fuzzy name filter").option("--tag <s>", "exact tag filter").option("--sort <field>", "update_time | name", oneOf("--sort", NAME_OR_UPDATE_TIME)).option("--direction <dir>", "asc | desc", sortDirection).option("--offset <n>", "page offset", int3).option("--limit <n>", "page size (default: -1, all)", int3).action(async (knId, opts, cmd) => {
1851
+ printJson(
1852
+ await clientFrom(cmd).kn.conceptGroups(knId, {
1853
+ branch: opts.branch,
1854
+ namePattern: opts.namePattern,
1855
+ tag: opts.tag,
1856
+ sort: opts.sort,
1857
+ direction: opts.direction,
1858
+ offset: opts.offset,
1859
+ limit: opts.limit
1860
+ }),
1861
+ outputOptions(cmd)
1862
+ );
1541
1863
  });
1542
- cg.command("get <kn-id> <cg-id>").description("Get a concept group").action(async (knId, cgId, _o, cmd) => {
1543
- printJson(await clientFrom(cmd).kn.conceptGroup(knId, cgId), outputOptions(cmd));
1864
+ cg.command("get <kn-id> <cg-id>").description("Get a concept group").option("--branch <b>", "branch (default: main)").option("--stats", "include statistics").option("--mode <mode>", "response view mode (default view when omitted)").action(async (knId, cgId, opts, cmd) => {
1865
+ printJson(
1866
+ await clientFrom(cmd).kn.conceptGroup(knId, cgId, {
1867
+ branch: opts.branch,
1868
+ includeStatistics: opts.stats,
1869
+ mode: opts.mode
1870
+ }),
1871
+ outputOptions(cmd)
1872
+ );
1544
1873
  });
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) => {
1874
+ writeModeFlags(
1875
+ cg.command("create <kn-id>").description("Create a concept group (--body / --body-file)").option(
1876
+ "--body <json>",
1877
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1878
+ ).option("--body-file <path>", "read body JSON from a file").option("--branch <b>", "branch (default: main)"),
1879
+ true
1880
+ ).action(async (knId, opts, cmd) => {
1549
1881
  printJson(
1550
- await clientFrom(cmd).kn.conceptGroupCreate(knId, readBody(opts)),
1882
+ await clientFrom(cmd).kn.conceptGroupCreate(knId, readBody(opts), writeModes(opts)),
1551
1883
  outputOptions(cmd)
1552
1884
  );
1553
1885
  });
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) => {
1886
+ writeModeFlags(
1887
+ cg.command("update <kn-id> <cg-id>").description("Update a concept group (--body / --body-file)").option(
1888
+ "--body <json>",
1889
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1890
+ ).option("--body-file <path>", "read body JSON from a file").option("--branch <b>", "branch (default: main)"),
1891
+ false
1892
+ ).action(async (knId, cgId, opts, cmd) => {
1558
1893
  printJson(
1559
- await clientFrom(cmd).kn.conceptGroupUpdate(knId, cgId, readBody(opts)),
1894
+ await clientFrom(cmd).kn.conceptGroupUpdate(knId, cgId, readBody(opts), writeModes(opts)),
1560
1895
  outputOptions(cmd)
1561
1896
  );
1562
1897
  });
1563
- cg.command("delete <kn-id> <cg-id>").description("Delete a concept group").action(async (knId, cgId, _o, cmd) => {
1564
- printJson(await clientFrom(cmd).kn.conceptGroupDelete(knId, cgId), outputOptions(cmd));
1898
+ cg.command("delete <kn-id> <cg-id>").description("Delete a concept group").option("--branch <b>", "branch (default: main)").action(async (knId, cgId, opts, cmd) => {
1899
+ printJson(
1900
+ await clientFrom(cmd).kn.conceptGroupDelete(knId, cgId, { branch: opts.branch }),
1901
+ outputOptions(cmd)
1902
+ );
1565
1903
  });
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) => {
1904
+ writeModeFlags(
1905
+ cg.command("add-members <kn-id> <cg-id>").description("Add object types to a concept group (--body / --body-file)").option(
1906
+ "--body <json>",
1907
+ "body JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (bkn-backend)"
1908
+ ).option("--body-file <path>", "read body JSON from a file").option("--branch <b>", "branch (default: main)"),
1909
+ false
1910
+ ).action(async (knId, cgId, opts, cmd) => {
1570
1911
  printJson(
1571
- await clientFrom(cmd).kn.conceptGroupAddMembers(knId, cgId, readBody(opts)),
1912
+ await clientFrom(cmd).kn.conceptGroupAddMembers(knId, cgId, readBody(opts), writeModes(opts)),
1572
1913
  outputOptions(cmd)
1573
1914
  );
1574
1915
  });
1575
- cg.command("remove-members <kn-id> <cg-id> <ot-ids>").description("Remove object types (comma-joined ids) from a concept group").action(async (knId, cgId, otIds, _o, cmd) => {
1916
+ cg.command("remove-members <kn-id> <cg-id> <ot-ids>").description("Remove object types (comma-joined ids) from a concept group").option("--branch <b>", "branch (default: main)").action(async (knId, cgId, otIds, opts, cmd) => {
1576
1917
  printJson(
1577
- await clientFrom(cmd).kn.conceptGroupRemoveMembers(knId, cgId, otIds),
1918
+ await clientFrom(cmd).kn.conceptGroupRemoveMembers(knId, cgId, otIds, {
1919
+ branch: opts.branch
1920
+ }),
1578
1921
  outputOptions(cmd)
1579
1922
  );
1580
1923
  });
1581
1924
  const sched = bkn.command("action-schedule").description("Action schedules \u2014 list/get");
1582
- sched.command("list <kn-id>").description("List action schedules").action(async (knId, _o, cmd) => {
1583
- printJson(await clientFrom(cmd).kn.actionSchedules(knId), outputOptions(cmd));
1925
+ sched.command("list <kn-id>").description("List action schedules (all of them unless --limit is given)").option("--branch <b>", "branch (default: main)").option("--name-pattern <s>", "fuzzy name filter").option("--action-type-id <id>", "filter by bound action type").option("--status <s>", "active | inactive", oneOf("--status", ["active", "inactive"])).option("--sort <field>", SCHEDULE_SORTS.join(" | "), oneOf("--sort", SCHEDULE_SORTS)).option("--direction <dir>", "asc | desc", sortDirection).option("--offset <n>", "page offset", int3).option("--limit <n>", "page size (default: -1, all)", int3).action(async (knId, opts, cmd) => {
1926
+ printJson(
1927
+ await clientFrom(cmd).kn.actionSchedules(knId, {
1928
+ branch: opts.branch,
1929
+ namePattern: opts.namePattern,
1930
+ actionTypeId: opts.actionTypeId,
1931
+ status: opts.status,
1932
+ sort: opts.sort,
1933
+ direction: opts.direction,
1934
+ offset: opts.offset,
1935
+ limit: opts.limit
1936
+ }),
1937
+ outputOptions(cmd)
1938
+ );
1584
1939
  });
1585
- sched.command("get <kn-id> <schedule-id>").description("Get an action schedule").action(async (knId, sId, _o, cmd) => {
1586
- printJson(await clientFrom(cmd).kn.actionSchedule(knId, sId), outputOptions(cmd));
1940
+ sched.command("get <kn-id> <schedule-id>").description("Get an action schedule").option("--branch <b>", "branch (default: main)").action(async (knId, sId, opts, cmd) => {
1941
+ printJson(
1942
+ await clientFrom(cmd).kn.actionSchedule(knId, sId, { branch: opts.branch }),
1943
+ outputOptions(cmd)
1944
+ );
1587
1945
  });
1588
1946
  sched.command("create <kn-id>").description("Create an action schedule (--body / --body-file)").option(
1589
1947
  "--body <json>",
1590
1948
  "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) => {
1949
+ ).option("--body-file <path>", "read body JSON from a file").option("--branch <b>", "branch (default: main)").action(async (knId, opts, cmd) => {
1592
1950
  printJson(
1593
- await clientFrom(cmd).kn.actionScheduleCreate(knId, readBody(opts)),
1951
+ await clientFrom(cmd).kn.actionScheduleCreate(knId, readBody(opts), {
1952
+ branch: opts.branch
1953
+ }),
1594
1954
  outputOptions(cmd)
1595
1955
  );
1596
1956
  });
1597
1957
  sched.command("update <kn-id> <schedule-id>").description("Update an action schedule (--body / --body-file)").option(
1598
1958
  "--body <json>",
1599
1959
  "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) => {
1960
+ ).option("--body-file <path>", "read body JSON from a file").option("--branch <b>", "branch (default: main)").action(async (knId, sId, opts, cmd) => {
1601
1961
  printJson(
1602
- await clientFrom(cmd).kn.actionScheduleUpdate(knId, sId, readBody(opts)),
1962
+ await clientFrom(cmd).kn.actionScheduleUpdate(knId, sId, readBody(opts), {
1963
+ branch: opts.branch
1964
+ }),
1603
1965
  outputOptions(cmd)
1604
1966
  );
1605
1967
  });
1606
1968
  sched.command("set-status <kn-id> <schedule-id>").description("Set an action schedule's status (--body / --body-file)").option(
1607
1969
  "--body <json>",
1608
1970
  "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) => {
1971
+ ).option("--body-file <path>", "read body JSON from a file").option("--branch <b>", "branch (default: main)").action(async (knId, sId, opts, cmd) => {
1610
1972
  printJson(
1611
- await clientFrom(cmd).kn.actionScheduleSetStatus(knId, sId, readBody(opts)),
1973
+ await clientFrom(cmd).kn.actionScheduleSetStatus(knId, sId, readBody(opts), {
1974
+ branch: opts.branch
1975
+ }),
1612
1976
  outputOptions(cmd)
1613
1977
  );
1614
1978
  });
1615
- sched.command("delete <kn-id> <schedule-ids>").description("Delete action schedule(s) (comma-joined ids)").action(async (knId, ids, _o, cmd) => {
1616
- printJson(await clientFrom(cmd).kn.actionScheduleDelete(knId, ids), outputOptions(cmd));
1979
+ sched.command("delete <kn-id> <schedule-ids>").description("Delete action schedule(s) (comma-joined ids)").option("--branch <b>", "branch (default: main)").action(async (knId, ids, opts, cmd) => {
1980
+ printJson(
1981
+ await clientFrom(cmd).kn.actionScheduleDelete(knId, ids, { branch: opts.branch }),
1982
+ outputOptions(cmd)
1983
+ );
1617
1984
  });
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(
1619
- "--embedding-model <name-or-id>",
1620
- "small-model name (or numeric ID, resolved to its name) for declared vector indexes (with --build)"
1621
- ).action(async (dir, opts, cmd) => {
1985
+ const capability = bkn.command("capability").description("Skills, tool box tools and MCP tools bound to a network \u2014 list/attach/detach");
1986
+ capability.command("list <kn-id>").description("List what the network has bound \u2192 {entries, boxes, total_count, \u2026}").option("--type <type>", "skill | function | mcp_tool").option("--box <box-id>", "only the tools of this tool box or MCP Server").option(
1987
+ "--metadata-type <kind>",
1988
+ "only function bindings whose tool box is openapi | function",
1989
+ oneOf("--metadata-type", ["openapi", "function"])
1990
+ ).option("--with-detail", "also fill description and status (one extra call per skill)").option("--branch <name>", "knowledge network branch (default: main)").option("--limit <n>", "page size", int3).option("--offset <n>", "page offset", int3).option("--sort <field>", "create_time | update_time", oneOf("--sort", CAPABILITY_SORTS)).option("--direction <dir>", "asc | desc", sortDirection).action(async (knId, opts, cmd) => {
1991
+ if (opts.type && !CAPABILITY_TYPES.includes(opts.type)) {
1992
+ throw new InputError(`--type must be one of ${CAPABILITY_TYPES.join(", ")}.`);
1993
+ }
1622
1994
  printJson(
1623
- await clientFrom(cmd).kn.push(dir, {
1995
+ await clientFrom(cmd).kn.capabilityList(knId, {
1624
1996
  branch: opts.branch,
1625
- build: Boolean(opts.build),
1626
- embeddingModel: opts.embeddingModel
1997
+ type: opts.type,
1998
+ boxId: opts.box,
1999
+ metadataType: opts.metadataType,
2000
+ withDetail: opts.withDetail,
2001
+ limit: opts.limit,
2002
+ offset: opts.offset,
2003
+ sort: opts.sort,
2004
+ direction: opts.direction
1627
2005
  }),
1628
2006
  outputOptions(cmd)
1629
2007
  );
1630
2008
  });
2009
+ capability.command("attach <kn-id>").description("Bind skills, tool box tools or MCP tools; binding one twice is a no-op").option("--skill <ids>", "skill ids (comma-separated)").option("--box <box-id>", "a tool box \u2014 name its tools with --tool, or take --all-tools").option("--mcp <mcp-id>", "an MCP Server \u2014 name its tools with --tool, or take --all-tools").option("--tool <ids>", "tool ids for --box, tool names for --mcp (comma-separated)").option("--all-tools", "every enabled tool the box or server holds now").option("--comment <text>", "note stored on each new binding").option("--branch <name>", "knowledge network branch (default: main)").addHelpText(
2010
+ "after",
2011
+ `
2012
+ A binding is always to a tool, never to a box. --all-tools is expanded when you run it
2013
+ into one binding per tool the box holds at that moment; a tool added to the box later is
2014
+ not bound until you attach it (\`capability list\` counts it under boxes[].unmounted_tools).
2015
+
2016
+ openbkn bkn capability attach <kn-id> --skill <skill-id>
2017
+ openbkn bkn capability attach <kn-id> --box <box-id> --tool <tool-id>[,<tool-id>]
2018
+ openbkn bkn capability attach <kn-id> --box <box-id> --all-tools
2019
+ openbkn bkn capability attach <kn-id> --mcp <mcp-id> --tool <tool-name>`
2020
+ ).action(async (knId, opts, cmd) => {
2021
+ const entries = capabilityEntries(opts);
2022
+ printJson(
2023
+ await clientFrom(cmd).kn.capabilityAttach(knId, entries, { branch: opts.branch }),
2024
+ outputOptions(cmd)
2025
+ );
2026
+ });
2027
+ capability.command("detach <kn-id> <binding-ids>").description("Release bindings by id (comma-separated, from `capability list`)").option("--branch <name>", "knowledge network branch (default: main)").action(async (knId, ids, opts, cmd) => {
2028
+ const bindingIds = csv(ids) ?? [];
2029
+ if (bindingIds.length === 0) throw new InputError("Name at least one binding id.");
2030
+ await clientFrom(cmd).kn.capabilityDetach(knId, bindingIds, { branch: opts.branch });
2031
+ printJson(
2032
+ { kn_id: knId, branch: opts.branch ?? "main", detached: bindingIds },
2033
+ outputOptions(cmd)
2034
+ );
2035
+ });
2036
+ 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(
2037
+ "--import-mode <mode>",
2038
+ "sent as import_mode: normal (default) | overwrite | ignore \u2014 0.1.5 deploys accept but do not apply it yet",
2039
+ oneOf("--import-mode", ["normal", "overwrite", "ignore"])
2040
+ ).option(
2041
+ "--no-strict-mode",
2042
+ "send strict_mode=false (skip dependency checks) \u2014 0.1.5 deploys accept but do not apply it yet"
2043
+ ).option(
2044
+ "--binding-policy <policy>",
2045
+ "environment-local bindings: preserve (default) | detach",
2046
+ oneOf("--binding-policy", ["preserve", "detach"])
2047
+ ).action(async (dir, opts, cmd) => {
2048
+ const declared = validateBknDirectory(dir).capabilities;
2049
+ const result = await clientFrom(cmd).kn.push(dir, {
2050
+ branch: opts.branch,
2051
+ importMode: opts.importMode,
2052
+ strictMode: opts.strictMode === false ? false : void 0,
2053
+ bindingPolicy: opts.bindingPolicy,
2054
+ verifyIntegrity: true,
2055
+ onIntegrityWarning: (warning) => process.stderr.write(`warning: ${warning}
2056
+ `)
2057
+ });
2058
+ warnUnboundCapabilities(result, declared);
2059
+ printJson(result, outputOptions(cmd));
2060
+ });
1631
2061
  bkn.command("pull <kn-id> [directory]").description("Download a knowledge network as a BKN tar and extract it locally").option("--branch <name>", "source branch", "main").action(async (knId, dir, opts, cmd) => {
1632
2062
  printJson(
1633
2063
  await clientFrom(cmd).kn.pull(knId, dir ?? knId, { branch: opts.branch }),
@@ -1637,31 +2067,69 @@ function bknCommand() {
1637
2067
  bkn.command("relation-type-paths <kn-id>").description("Query relation-type paths between object types (--body / --body-file JSON)").option(
1638
2068
  "--body <json>",
1639
2069
  "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) => {
2070
+ ).option("--body-file <path>", "read request JSON from a file").option("--branch <b>", "branch (default: main)").addHelpText(
2071
+ "after",
2072
+ `
2073
+ The body needs source_object_type_id, direction (forward | backward | bidirectional) and
2074
+ path_length (1\u20133). The contract's enum spells the reverse direction \`reverse\`, but the
2075
+ backend refuses it with 400 InvalidParameter.Direction; use \`backward\`.
2076
+
2077
+ openbkn bkn relation-type-paths <kn-id> \\
2078
+ --body '{"source_object_type_id": "<ot-id>", "direction": "bidirectional", "path_length": 2}'`
2079
+ ).action(async (knId, opts, cmd) => {
1641
2080
  printJson(
1642
- await clientFrom(cmd).kn.relationTypePaths(knId, readBody(opts)),
2081
+ await clientFrom(cmd).kn.relationTypePaths(knId, readBody(opts), { branch: opts.branch }),
1643
2082
  outputOptions(cmd)
1644
2083
  );
1645
2084
  });
1646
- bkn.command("resources").description("List BKN-backend resources").action(async (_opts, cmd) => {
1647
- printJson(await clientFrom(cmd).kn.bknResources(), outputOptions(cmd));
1648
- });
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(
1650
- "--embedding-fields <map>",
1651
- "columns to vectorize per table (with --build): '<table>:<col>[+<col>...][,...]'"
2085
+ bkn.command("cypher <kn-id>").description(
2086
+ "Answer a read-only Cypher query over the network's model, outside any Trace session"
2087
+ ).requiredOption(
2088
+ "--query <cypher>",
2089
+ "read-only Cypher: labels are object types, relationship types are relation types"
1652
2090
  ).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) => {
2091
+ "--params <json>",
2092
+ `values for $name placeholders, as a JSON object: '{"floor": 100}'`
2093
+ ).option("--branch <branch>", "knowledge network branch (default: main)").addHelpText(
2094
+ "after",
2095
+ `
2096
+ The compiler behind \`openbkn context run-cypher\`, called directly: the same subset,
2097
+ the same limits, the same refusals naming the construct and its position. Answers
2098
+ {columns, entries}; the generated SQL is never returned. Nothing is recorded in BKN
2099
+ Trace \u2014 use context run-cypher when the query is part of a conversation an agent
2100
+ should be able to account for.
2101
+
2102
+ openbkn bkn cypher <kn-id> \\
2103
+ --query "MATCH (c:customer)<-[:rel_order_customer]-(o:order) RETURN c.city AS city, count(*) AS orders"`
2104
+ ).action(async (knId, opts, cmd) => {
2105
+ if (!opts.query.trim()) throw new InputError("--query must not be empty.");
2106
+ printJson(
2107
+ await clientFrom(cmd).kn.cypher(knId, opts.query, {
2108
+ branch: opts.branch,
2109
+ parameters: cypherParams(opts.params)
2110
+ }),
2111
+ outputOptions(cmd)
2112
+ );
2113
+ });
2114
+ bkn.command("resources").description("List BKN-backend resources (knowledge networks) \u2192 {entries, total_count}").option("--keyword <s>", "name keyword filter").option("--limit <n>", "page size", int3, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int3, 0).option("--sort <field>", "sort field (default: name)").option("--direction <dir>", "asc | desc", sortDirection).action(async (opts, cmd) => {
2115
+ printJson(
2116
+ await clientFrom(cmd).kn.bknResources({
2117
+ keyword: opts.keyword,
2118
+ limit: opts.limit,
2119
+ offset: opts.offset,
2120
+ sort: opts.sort,
2121
+ direction: opts.direction
2122
+ }),
2123
+ outputOptions(cmd)
2124
+ );
2125
+ });
2126
+ 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("--no-rollback", "keep a partially-created KN on failure").action(async (catalogId, opts, cmd) => {
1656
2127
  printJson(
1657
2128
  await clientFrom(cmd).kn.createFromCatalog({
1658
2129
  catalogId,
1659
2130
  name: opts.name,
1660
2131
  tables: csv(opts.tables),
1661
2132
  pkMap: opts.pkMap ? parsePkMap(opts.pkMap) : void 0,
1662
- build: Boolean(opts.build),
1663
- embeddingFields: opts.embeddingFields ? parseEmbeddingFields(opts.embeddingFields) : void 0,
1664
- embeddingModel: opts.embeddingModel,
1665
2133
  noRollback: opts.rollback === false,
1666
2134
  onProgress: (m) => console.error(m)
1667
2135
  }),
@@ -1680,6 +2148,7 @@ function bknCommand() {
1680
2148
  "action-type",
1681
2149
  "metric",
1682
2150
  "concept-group",
2151
+ "capability",
1683
2152
  "action-log",
1684
2153
  "action-schedule"
1685
2154
  ],
@@ -1692,6 +2161,7 @@ function bknCommand() {
1692
2161
  "search",
1693
2162
  "subgraph",
1694
2163
  "relation-type-paths",
2164
+ "cypher",
1695
2165
  "resources",
1696
2166
  "action-execution",
1697
2167
  "validate"
@@ -1703,6 +2173,10 @@ function bknCommand() {
1703
2173
  RUN: ["query", "dry-run", "validate"],
1704
2174
  WRITE: ["create", "update", "delete"]
1705
2175
  });
2176
+ groupChildren(capability, {
2177
+ READ: ["list"],
2178
+ WRITE: ["attach", "detach"]
2179
+ });
1706
2180
  guide(
1707
2181
  bkn,
1708
2182
  `WHERE IDS COME FROM
@@ -1718,6 +2192,13 @@ SEARCH VS THE MCP SIDE
1718
2192
  EDITING SCHEMA AS FILES
1719
2193
  pull <kn-id> ./dir -> edit -> validate ./dir -> push ./dir
1720
2194
  \`validate\` is offline and catches structure errors before the upload.
2195
+ network.bkn may declare the skills and tools the network depends on under
2196
+ \`capabilities:\`. push binds them by id, then by name; one this platform cannot
2197
+ resolve is skipped, not fatal, and push lists every skip on stderr.
2198
+
2199
+ CAPABILITIES
2200
+ \`capability list <kn-id>\` shows what a network has bound \u2014 the set Context Loader
2201
+ recalls skills and tools from; a network with nothing bound recalls nothing.
1721
2202
 
1722
2203
  REQUEST BODIES
1723
2204
  create/update take a definition; query/execute/dry-run take a query. Both are documented
@@ -1726,7 +2207,7 @@ REQUEST BODIES
1726
2207
 
1727
2208
  CREATING FROM DATA
1728
2209
  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
2210
+ then \`openbkn vega resource build <resource-id>\` produces the index. There is no
1730
2211
  whole-network build.`
1731
2212
  );
1732
2213
  return group(bkn, "DATA & KNOWLEDGE");
@@ -1739,7 +2220,7 @@ function collect(value, prev) {
1739
2220
  return prev;
1740
2221
  }
1741
2222
  function callCommand() {
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(
2223
+ 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 on the current platform").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(
1743
2224
  "-F, --form <field>",
1744
2225
  "multipart field key=value or key=@file (repeatable)",
1745
2226
  collect,
@@ -1751,8 +2232,9 @@ function callCommand() {
1751
2232
  baseUrl: g.baseUrl,
1752
2233
  token: g.token,
1753
2234
  user: g.user,
1754
- businessDomain: g.bizDomain,
1755
2235
  insecure: g.insecure,
2236
+ versionCheckMode: "cli",
2237
+ ...retryOptionsFrom(g),
1756
2238
  ...trace ? { trace } : {}
1757
2239
  });
1758
2240
  const res = await rawCall(ctx, url, {
@@ -1760,7 +2242,6 @@ function callCommand() {
1760
2242
  header: opts.header,
1761
2243
  data: opts.data ?? opts.dataRaw,
1762
2244
  form: opts.form,
1763
- businessDomain: g.bizDomain,
1764
2245
  verbose: opts.verbose
1765
2246
  });
1766
2247
  const out = outputOptions(cmd2);
@@ -1785,69 +2266,42 @@ function callCommand() {
1785
2266
 
1786
2267
  FINDING THE PATH
1787
2268
  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.`
2269
+ read the path and request body there rather than guessing. Authentication and
2270
+ TLS flags are injected the same way as for any other command.`
1790
2271
  );
1791
2272
  return group(cmd, "RAW API");
1792
2273
  }
1793
2274
 
1794
2275
  // src/commands/config.ts
1795
2276
  import { Command as Command6 } from "commander";
1796
- function requireActive() {
1797
- const baseUrl = activePlatform();
1798
- if (!baseUrl) throw new InputError("No active platform. Run `openbkn auth login <url>` first.");
1799
- return baseUrl;
1800
- }
1801
2277
  function configCommand() {
1802
- const config = new Command6("config").description("Remember a platform URL / business domain");
1803
- config.command("show").description("Show the active platform and business domain").action((_opts, cmd) => {
2278
+ const config = new Command6("config").description("Remember a platform URL");
2279
+ config.command("show").description("Show the active platform").action((_opts, cmd) => {
1804
2280
  const baseUrl = activePlatform();
1805
- printJson(
1806
- {
1807
- baseUrl,
1808
- businessDomain: baseUrl ? readPlatformConfig(baseUrl).businessDomain : void 0
1809
- },
1810
- outputOptions(cmd)
1811
- );
2281
+ printJson({ baseUrl }, outputOptions(cmd));
1812
2282
  });
1813
- config.command("set <key> <value>").description("Set a config value (baseUrl | businessDomain)").action((key, value, _opts, cmd) => {
2283
+ config.command("set <key> <value>").description("Set a config value (baseUrl)").action((key, value, _opts, cmd) => {
1814
2284
  if (key === "baseUrl") {
1815
2285
  setActivePlatform(trimTrailingSlashes(value));
1816
- } else if (key === "businessDomain") {
1817
- updatePlatformConfig(requireActive(), { businessDomain: value });
1818
2286
  } else {
1819
- throw new InputError(`Unknown config key: ${key} (expected baseUrl | businessDomain)`);
2287
+ throw new InputError(`Unknown config key: ${key} (expected baseUrl)`);
1820
2288
  }
1821
2289
  printJson({ ok: true, key, value }, outputOptions(cmd));
1822
2290
  });
1823
- config.command("set-bd <value>").description("Set the default business domain for the active platform").action((value, _opts, cmd) => {
1824
- const baseUrl = requireActive();
1825
- updatePlatformConfig(baseUrl, { businessDomain: value });
1826
- printJson({ baseUrl, businessDomain: value }, outputOptions(cmd));
1827
- });
1828
- config.command("list-bd").description("List business domains (requires login)").action(() => {
1829
- throw new InputError("Not yet implemented \u2014 requires backend business-domains API.");
1830
- });
1831
- groupChildren(config, { READ: ["show", "list-bd"], WRITE: ["set", "set-bd"] });
2291
+ groupChildren(config, { READ: ["show"], WRITE: ["set"] });
1832
2292
  return group(config, "SIGN IN & SETTINGS");
1833
2293
  }
1834
2294
 
1835
2295
  // src/commands/context.ts
1836
- import { Command as Command7 } from "commander";
2296
+ import { Command as Command7, Option as Option2 } from "commander";
1837
2297
  var int4 = (v) => Number.parseInt(v, 10);
2298
+ var list = (v) => v.split(",").map((part) => part.trim()).filter(Boolean);
1838
2299
  var collectArg = (v, prev) => {
1839
2300
  prev.push(v);
1840
2301
  return prev;
1841
2302
  };
1842
2303
  function buildArgs(opts) {
1843
- let out = {};
1844
- if (opts.args) {
1845
- try {
1846
- out = parseBigIntJSON(opts.args);
1847
- } catch {
1848
- throw new InputError("--args must be valid JSON");
1849
- }
1850
- }
2304
+ const out = readJsonArgs(opts) ?? {};
1851
2305
  for (const pair of opts.arg ?? []) {
1852
2306
  const idx = pair.indexOf("=");
1853
2307
  if (idx <= 0) throw new InputError(`--arg must be key=value (got: ${pair})`);
@@ -1882,20 +2336,34 @@ function contextCommand() {
1882
2336
  const cmd = new Command7("context").description(
1883
2337
  "Ask a network questions (the MCP interface agents use)"
1884
2338
  );
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
- };
2339
+ const jsonArgs = (opts) => readJsonArgs(opts, true);
1893
2340
  cmd.command("search-schema <kn-id> <query>").description(
1894
2341
  "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) => {
2342
+ ).option(
2343
+ "--concept-groups <ids>",
2344
+ "comma-separated concept-group ids to confine recall to (ids from kn-detail)",
2345
+ list
2346
+ ).option(
2347
+ "--only <kinds>",
2348
+ "return only these concept kinds, comma-separated: object,relation,action,metric",
2349
+ list
2350
+ ).addOption(new Option2("--scope <kinds>", "alias of --only").argParser(list).hideHelp()).option("--max <n>", "max concepts per kind", int4).option(
2351
+ "--schema-brief",
2352
+ "trimmed schema: property name/type only (MCP tool default true; REST default false)"
2353
+ ).option("--no-schema-brief", "full schema: adds property comments, primary keys and tags").option("--include-columns", "add each data property's physical column (needed for run-sql)").option("--rerank", "re-rank relation types (the server default)").option("--no-rerank", "skip relation-type re-ranking").option("--rerank-model <name>", "override the deploy's rerank model (operators only)").action(async (knId, query, opts, cmd2) => {
2354
+ if (opts.only && opts.scope) throw new InputError("--scope is an alias of --only; pass one");
2355
+ const kinds = opts.only ?? opts.scope;
2356
+ const scope = {
2357
+ ...kinds ? scopeFromKinds(kinds) : {},
2358
+ ...opts.conceptGroups?.length ? { conceptGroups: opts.conceptGroups } : {}
2359
+ };
1896
2360
  const data = await clientFrom(cmd2).context.searchSchema(knId, query, {
1897
- searchScope: opts.scope ? String(opts.scope).split(",") : void 0,
1898
- maxConcepts: opts.max
2361
+ searchScope: Object.keys(scope).length > 0 ? scope : void 0,
2362
+ maxConcepts: opts.max,
2363
+ schemaBrief: opts.schemaBrief,
2364
+ enableRerank: opts.rerank,
2365
+ rerankModel: opts.rerankModel,
2366
+ includeColumns: opts.includeColumns
1899
2367
  });
1900
2368
  printJson(data, outputOptions(cmd2));
1901
2369
  });
@@ -1903,21 +2371,37 @@ function contextCommand() {
1903
2371
  `Query one object type's instances \u2014 \`--args '{"ot_id":"<id>","limit":10}'\` \u2192 {datas, total_count}`
1904
2372
  ).option(
1905
2373
  "--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) => {
2374
+ "tool arguments as JSON (- for stdin); kn_id is filled from <kn-id> unless given; --schema prints the shape"
2375
+ ).option("--args-file <path>", "read tool arguments as JSON from a file (or - for stdin)").option("--schema", "print this tool's argument schema from the deploy instead of calling it").action(async (knId, opts, cmd2) => {
1908
2376
  if (opts.schema) return printToolSchema(cmd2, knId, "query_object_instance");
1909
- const args = jsonArgs(opts.args);
2377
+ const args = jsonArgs(opts);
1910
2378
  printJson(await clientFrom(cmd2).context.queryObjectInstance(knId, args), outputOptions(cmd2));
1911
2379
  });
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) => {
2380
+ cmd.command("search-capabilities <kn-id>").description(
2381
+ "Rank every capability the network mounted: skills, functions, API tools, MCP tools"
2382
+ ).option("--query <text>", "rank by relevance; omit to list in mount order").option("--types <list>", "comma-separated: skill,function,mcp_tool", list).option(
2383
+ "--metadata-types <list>",
2384
+ "comma-separated: openapi,function (splits function tools)",
2385
+ list
2386
+ ).option("--owner-id <id>", "one toolbox or MCP server").option("--limit <n>", "max results (1-100)", int4).action(async (knId, opts, cmd2) => {
1913
2387
  printJson(
1914
- await clientFrom(cmd2).context.findSkills(knId, otId, opts.topK),
2388
+ await clientFrom(cmd2).context.searchCapabilities(knId, {
2389
+ query: opts.query,
2390
+ types: opts.types,
2391
+ metadataTypes: opts.metadataTypes,
2392
+ ownerId: opts.ownerId,
2393
+ limit: opts.limit
2394
+ }),
1915
2395
  outputOptions(cmd2)
1916
2396
  );
1917
2397
  });
1918
- cmd.command("kn-detail <kn-id>").description("Get a KN's schema at a detail level (progressive: summary skeleton \u2192 drill down)").option("--detail-level <level>", "summary (default) | full", "summary").action(async (knId, opts, cmd2) => {
1919
- const level = opts.detailLevel === "full" ? "full" : "summary";
1920
- printJson(await clientFrom(cmd2).context.knDetail(knId, level), outputOptions(cmd2));
2398
+ cmd.command("kn-detail <kn-id>").description("Get a KN's schema at a detail level (progressive: summary skeleton \u2192 drill down)").option(
2399
+ "--detail-level <level>",
2400
+ "summary (default) | full",
2401
+ oneOf("--detail-level", ["summary", "full"]),
2402
+ "summary"
2403
+ ).action(async (knId, opts, cmd2) => {
2404
+ printJson(await clientFrom(cmd2).context.knDetail(knId, opts.detailLevel), outputOptions(cmd2));
1921
2405
  });
1922
2406
  cmd.command("object-types <kn-id> <ids...>").description("Full definitions for the given object-type ids (unmatched \u2192 `missing`)").action(async (knId, ids, _opts, cmd2) => {
1923
2407
  printJson(await clientFrom(cmd2).context.objectTypes(knId, ids), outputOptions(cmd2));
@@ -1994,6 +2478,51 @@ adds paging and --need-total but records nothing in Trace.`
1994
2478
  if (opts.timeout !== void 0) args.query_timeout = opts.timeout;
1995
2479
  printJson(await clientFrom(cmd2).context.toolCall(knId, "run_sql", args), outputOptions(cmd2));
1996
2480
  });
2481
+ cmd.command("run-cypher <kn-id>").description(
2482
+ "Filter along a path and aggregate across object types with read-only Cypher over the model"
2483
+ ).option(
2484
+ "--query <cypher>",
2485
+ "read-only Cypher: labels are object types, relationship types are relation types"
2486
+ ).option(
2487
+ "--params <json>",
2488
+ `values for $name placeholders, as a JSON object: '{"floor": 100}'`
2489
+ ).option("--branch <branch>", "knowledge network branch (default: main)").option("--schema", "print this tool's argument schema from the deploy instead of calling it").addHelpText(
2490
+ "after",
2491
+ `
2492
+ Everything is named the way the model names it \u2014 no resource id, no physical column:
2493
+
2494
+ openbkn context search-schema <kn-id> "<what you are after>" --json
2495
+ \u2192 object type and relation type ids or names, property names
2496
+
2497
+ openbkn context run-cypher <kn-id> \\
2498
+ --query "MATCH (o:order)-[:rel_order_item_order]->(i:order_item)
2499
+ WHERE o.status IN ['paid','shipped'] AND o.amount > \\$floor
2500
+ RETURN o.status AS status, count(*) AS orders ORDER BY orders DESC LIMIT 20" \\
2501
+ --params '{"floor": 100}'
2502
+
2503
+ Supported: several MATCH clauses and comma-separated paths; -[:R]->, <-[:R]- and
2504
+ undirected -[:R]-; WHERE with comparisons, IN, IS NULL, AND/OR/NOT; RETURN with
2505
+ aliases, DISTINCT and count/sum/avg/min/max (grouping by the other returned columns);
2506
+ ORDER BY, SKIP, LIMIT. Refused with the construct and its position: OPTIONAL MATCH,
2507
+ WITH, UNION, variable-length paths, functions and arithmetic, returning a whole node.
2508
+ A query holds at most 8 relationships and 9 nodes; without LIMIT it returns 1000 rows,
2509
+ and LIMIT may not exceed 10000.
2510
+
2511
+ What Cypher cannot say \u2014 CTEs, UNION, window functions \u2014 or a resource never modelled
2512
+ as an object type belongs in run-sql. The same query runs outside any Trace session
2513
+ through \`openbkn bkn cypher\`.`
2514
+ ).action(async (knId, opts, cmd2) => {
2515
+ if (opts.schema) return printToolSchema(cmd2, knId, "run_cypher");
2516
+ if (!opts.query)
2517
+ throw new InputError("--query is required (or use --schema to see the shape)");
2518
+ printJson(
2519
+ await clientFrom(cmd2).context.runCypher(knId, opts.query, {
2520
+ branch: opts.branch,
2521
+ parameters: cypherParams(opts.params)
2522
+ }),
2523
+ outputOptions(cmd2)
2524
+ );
2525
+ });
1997
2526
  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
2527
  "--direction <d>",
1999
2528
  "forward | backward | bidirectional \u2014 pick bidirectional when unsure how the relation reads",
@@ -2002,7 +2531,10 @@ adds paging and --need-total but records nothing in Trace.`
2002
2531
  "--limit <n>",
2003
2532
  "instances of the STARTING type, not paths or total objects",
2004
2533
  (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(
2534
+ ).option(
2535
+ "--args <json>",
2536
+ "extra tool arguments merged in (condition, sort, offset \u2026; - for stdin)"
2537
+ ).option("--args-file <path>", "read extra tool arguments as JSON from a file (or - for stdin)").option("--schema", "print this tool's argument schema from the deploy instead of calling it").addHelpText(
2006
2538
  "after",
2007
2539
  `
2008
2540
  Use this when the topology is the question \u2014 "what does this supplier touch?" \u2014
@@ -2014,7 +2546,7 @@ Paths multiply with each hop, so start at 1 or 2.`
2014
2546
  throw new InputError("--hops is required (or use --schema to see the shape)");
2015
2547
  }
2016
2548
  const args = {
2017
- ...opts.args ? jsonArgs(opts.args) : {},
2549
+ ...readJsonArgs(opts) ?? {},
2018
2550
  source_object_type_id: objectTypeId,
2019
2551
  direction: opts.direction,
2020
2552
  path_length: opts.hops
@@ -2027,8 +2559,8 @@ Paths multiply with each hop, so start at 1 or 2.`
2027
2559
  });
2028
2560
  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
2561
  "--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(
2562
+ "tool arguments: analysis_dimensions, time, condition, having, order_by, limit, fill_null; - for stdin"
2563
+ ).option("--args-file <path>", "read tool arguments as JSON from a file (or - for stdin)").option("--schema", "print this tool's argument schema from the deploy instead of calling it").addHelpText(
2032
2564
  "after",
2033
2565
  `
2034
2566
  Metric ids come from an object type: \`context object-types <kn-id> <ot-id>\`
@@ -2036,7 +2568,7 @@ lists them under related_metrics. The definition owns the arithmetic, so
2036
2568
  rewriting it with \`run-sql\` produces a number the platform will not agree with.`
2037
2569
  ).action(async (knId, metricId, opts, cmd2) => {
2038
2570
  if (opts.schema) return printToolSchema(cmd2, knId, "query_metric");
2039
- const args = { ...opts.args ? jsonArgs(opts.args) : {}, metric_id: metricId };
2571
+ const args = { ...readJsonArgs(opts) ?? {}, metric_id: metricId };
2040
2572
  printJson(
2041
2573
  await clientFrom(cmd2).context.toolCall(knId, "query_metric", args),
2042
2574
  outputOptions(cmd2)
@@ -2047,8 +2579,8 @@ rewriting it with \`run-sql\` produces a number the platform will not agree with
2047
2579
  });
2048
2580
  cmd.command("tool-call <kn-id> <name>").description("Call any MCP tool by name \u2014 current or future (use `tools` to discover)").option(
2049
2581
  "--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(
2582
+ "tool arguments as JSON (- for stdin); kn_id (for network-scoped tools) and response_format=json are filled unless given \u2014 input schema comes from `context tools <kn-id>`"
2583
+ ).option("--args-file <path>", "read tool arguments as JSON from a file (or - for stdin)").option(
2052
2584
  "--arg <key=value>",
2053
2585
  "one argument (repeatable; value parsed as JSON, else string)",
2054
2586
  collectArg,
@@ -2056,8 +2588,23 @@ rewriting it with \`run-sql\` produces a number the platform will not agree with
2056
2588
  ).option(
2057
2589
  "--schema",
2058
2590
  "print the named tool's argument schema from the deploy instead of calling it"
2059
- ).action(async (knId, name, opts, cmd2) => {
2591
+ ).option("--receipt", "return the validated operation receipt (requires --json or --compact)").action(async (knId, name, opts, cmd2) => {
2592
+ if (opts.receipt && opts.schema) {
2593
+ throw new InputError("--receipt cannot be combined with --schema");
2594
+ }
2595
+ if (opts.receipt && !cmd2.optsWithGlobals().json && !cmd2.optsWithGlobals().compact) {
2596
+ throw new InputError("--receipt requires --json or --compact");
2597
+ }
2060
2598
  if (opts.schema) return printToolSchema(cmd2, knId, name);
2599
+ if (opts.receipt) {
2600
+ const { value, receipt } = await clientFrom(cmd2).context.managedToolCall(
2601
+ knId,
2602
+ name,
2603
+ buildArgs(opts)
2604
+ );
2605
+ printJson({ value, bkn_receipt: receipt }, { ...outputOptions(cmd2), json: true });
2606
+ return;
2607
+ }
2061
2608
  printJson(
2062
2609
  await clientFrom(cmd2).context.toolCall(knId, name, buildArgs(opts)),
2063
2610
  outputOptions(cmd2)
@@ -2065,7 +2612,7 @@ rewriting it with \`run-sql\` produces a number the platform will not agree with
2065
2612
  });
2066
2613
  cmd.command("call-method <kn-id> <method>").description(
2067
2614
  "Call any MCP method by name (e.g. tools/list, resources/read) \u2014 current or future"
2068
- ).option("--args <json>", "method params as JSON \u2014 see `context call-method <kn-id> tools/list`").option(
2615
+ ).option("--args <json>", "method params as JSON; use - for stdin").option("--args-file <path>", "read method params as JSON from a file (or - for stdin)").option(
2069
2616
  "--arg <key=value>",
2070
2617
  "one param (repeatable; value parsed as JSON, else string)",
2071
2618
  collectArg,
@@ -2088,49 +2635,36 @@ rewriting it with \`run-sql\` produces a number the platform will not agree with
2088
2635
  cmd.command("prompts <kn-id>").description("List MCP prompts").action(async (knId, _opts, cmd2) => {
2089
2636
  printJson(await clientFrom(cmd2).context.prompts(knId), outputOptions(cmd2));
2090
2637
  });
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) => {
2095
- let args;
2096
- if (opts.args) {
2097
- try {
2098
- args = parseBigIntJSON(opts.args);
2099
- } catch {
2100
- throw new InputError("--args must be valid JSON");
2101
- }
2102
- }
2638
+ cmd.command("prompt <kn-id> <name>").description("Get one MCP prompt (--args JSON for prompt arguments)").option("--args <json>", "prompt arguments as JSON; use - for stdin").option("--args-file <path>", "read prompt arguments as JSON from a file (or - for stdin)").action(async (knId, name, opts, cmd2) => {
2639
+ const args = readJsonArgs(opts);
2103
2640
  printJson(await clientFrom(cmd2).context.prompt(knId, name, args), outputOptions(cmd2));
2104
2641
  });
2105
2642
  cmd.command("query-instance-subgraph <kn-id>").description("Query an instance subgraph across relation-type paths").option(
2106
2643
  "--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) => {
2644
+ "tool arguments as JSON (- for stdin); kn_id is filled from <kn-id> unless given; --schema prints the shape"
2645
+ ).option("--args-file <path>", "read tool arguments as JSON from a file (or - for stdin)").option("--schema", "print this tool's argument schema from the deploy instead of calling it").action(async (knId, opts, cmd2) => {
2109
2646
  if (opts.schema) return printToolSchema(cmd2, knId, "query_instance_subgraph");
2110
2647
  printJson(
2111
- await clientFrom(cmd2).context.queryInstanceSubgraph(knId, jsonArgs(opts.args)),
2648
+ await clientFrom(cmd2).context.queryInstanceSubgraph(knId, jsonArgs(opts)),
2112
2649
  outputOptions(cmd2)
2113
2650
  );
2114
2651
  });
2115
2652
  cmd.command("get-logic-properties <kn-id>").description("Compute logic-property values for instances").option(
2116
2653
  "--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) => {
2654
+ "tool arguments as JSON (- for stdin); kn_id is filled from <kn-id> unless given; --schema prints the shape"
2655
+ ).option("--args-file <path>", "read tool arguments as JSON from a file (or - for stdin)").option("--schema", "print this tool's argument schema from the deploy instead of calling it").action(async (knId, opts, cmd2) => {
2119
2656
  if (opts.schema) return printToolSchema(cmd2, knId, "get_logic_properties_values");
2120
2657
  printJson(
2121
- await clientFrom(cmd2).context.logicProperties(knId, jsonArgs(opts.args)),
2658
+ await clientFrom(cmd2).context.logicProperties(knId, jsonArgs(opts)),
2122
2659
  outputOptions(cmd2)
2123
2660
  );
2124
2661
  });
2125
2662
  cmd.command("get-action-info <kn-id>").description("Fetch action info / dynamic tools for an instance").option(
2126
2663
  "--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) => {
2664
+ "tool arguments as JSON (- for stdin); kn_id is filled from <kn-id> unless given; --schema prints the shape"
2665
+ ).option("--args-file <path>", "read tool arguments as JSON from a file (or - for stdin)").option("--schema", "print this tool's argument schema from the deploy instead of calling it").action(async (knId, opts, cmd2) => {
2129
2666
  if (opts.schema) return printToolSchema(cmd2, knId, "get_action_info");
2130
- printJson(
2131
- await clientFrom(cmd2).context.actionInfo(knId, jsonArgs(opts.args)),
2132
- outputOptions(cmd2)
2133
- );
2667
+ printJson(await clientFrom(cmd2).context.actionInfo(knId, jsonArgs(opts)), outputOptions(cmd2));
2134
2668
  });
2135
2669
  groupChildren(cmd, {
2136
2670
  GROUPS: ["conversation"],
@@ -2146,11 +2680,12 @@ rewriting it with \`run-sql\` produces a number the platform will not agree with
2146
2680
  "templates",
2147
2681
  "prompts",
2148
2682
  "prompt",
2149
- "find-skills"
2683
+ "search-capabilities"
2150
2684
  ],
2151
2685
  RUN: [
2152
2686
  "query-object-instance",
2153
2687
  "run-sql",
2688
+ "run-cypher",
2154
2689
  "explore-subgraph",
2155
2690
  "query-metric",
2156
2691
  "query-instance-subgraph",
@@ -2170,15 +2705,29 @@ rewriting it with \`run-sql\` produces a number the platform will not agree with
2170
2705
  get-logic-properties / get-action-info computed values, runnable actions
2171
2706
 
2172
2707
  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.
2708
+ Aggregation, ranking, GROUP BY or joins are not query-object-instance. When the question
2709
+ can be stated in object types and relation types \u2014 filter along a path, aggregate what
2710
+ it reaches \u2014 use \`run-cypher <kn-id>\`: model names only, no resource ids or physical
2711
+ columns. SQL through \`run-sql <kn-id>\` is for what the Cypher subset cannot say.
2712
+ Unknown topology is \`explore-subgraph <kn-id> <ot-id> --hops <n>\`, not a hand-built path.
2176
2713
 
2177
2714
  THE SAME ID, FOUR NAMES
2178
2715
  An object type's id is \`concept_id\` in search-schema output, \`id\` in kn-detail and
2179
2716
  get_object_types, \`ot_id\` in query-object-instance arguments, and \`object_type_id\` on
2180
2717
  an instance row. Same value throughout \u2014 carry it across, do not look it up again.
2181
2718
 
2719
+ QUERY ARGUMENTS
2720
+ query-object-instance rejects unknown argument keys before calling the platform. Combine
2721
+ conditions with condition.sub_conditions, not condition.conditions; vector search belongs in
2722
+ condition.operation=knn, not a top-level knn argument. Do not combine condition and filters:
2723
+ the platform ignores filters when condition is present. Page with cursor (the MCP tool's
2724
+ key) or offset, never both; search_after (the REST name) is passed through unchanged for a
2725
+ deploy that advertises it. Any kn_id in --args must match <kn-id>. For properties and condition.field,
2726
+ run object-types <kn-id> <ot-id> first to see the valid field names. Specifying properties
2727
+ also reads the object-type schema once and rejects unknown names before the query. --schema shows the
2728
+ argument shape advertised by this deploy. Every --args option also accepts --args-file <path>,
2729
+ while --args - reads JSON from stdin.
2730
+
2182
2731
  RAW MCP
2183
2732
  tools <kn-id> lists what this deploy advertises, with each tool's input schema.
2184
2733
  tool-call / call-method reach anything the named commands above do not cover.`
@@ -2217,7 +2766,6 @@ var returns_default = {
2217
2766
  "bkn list": ["entries", "total_count"],
2218
2767
  "bkn get": [
2219
2768
  "branch",
2220
- "business_domain",
2221
2769
  "color",
2222
2770
  "comment",
2223
2771
  "create_time",
@@ -2239,7 +2787,6 @@ var returns_default = {
2239
2787
  "bkn action-type get": ["entries"],
2240
2788
  "bkn stats": [
2241
2789
  "branch",
2242
- "business_domain",
2243
2790
  "color",
2244
2791
  "comment",
2245
2792
  "create_time",
@@ -2400,7 +2947,6 @@ var returns_default = {
2400
2947
  ],
2401
2948
  "skill list": ["data", "has_next", "has_prev", "page", "page_size", "total", "total_pages"],
2402
2949
  "skill get": [
2403
- "business_domain_id",
2404
2950
  "category",
2405
2951
  "category_name",
2406
2952
  "create_time",
@@ -2416,7 +2962,6 @@ var returns_default = {
2416
2962
  ],
2417
2963
  "skill market": ["data", "has_next", "has_prev", "page", "page_size", "total", "total_pages"],
2418
2964
  "skill market-get": [
2419
- "business_domain_id",
2420
2965
  "category",
2421
2966
  "category_name",
2422
2967
  "create_time",
@@ -2437,8 +2982,8 @@ var returns_default = {
2437
2982
  "skill names": ["entries"],
2438
2983
  "skill history": "array",
2439
2984
  "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"],
2985
+ "sandbox deps": ["dependencies", "session_id"],
2986
+ "sandbox template": ["code_template", "template_type"],
2442
2987
  "trace graph": [
2443
2988
  "data",
2444
2989
  "duration_nano",
@@ -2572,7 +3117,7 @@ var returns_default = {
2572
3117
  "updated_at"
2573
3118
  ],
2574
3119
  "admin user roles": ["roles"],
2575
- "admin role list": ["roles"],
3120
+ "admin role list": ["roles", "total"],
2576
3121
  "admin role get": [
2577
3122
  "built_in",
2578
3123
  "created_at",
@@ -2631,7 +3176,7 @@ var SERVICE_PROBES = [
2631
3176
  { service: "vega-backend", path: "/api/vega-backend/v1/catalogs?limit=1" },
2632
3177
  {
2633
3178
  service: "agent-operator-integration",
2634
- path: "/api/agent-operator-integration/v1/tool-box/list?page=1&size=1"
3179
+ path: "/api/agent-operator-integration/v1/tool-box/list?page=1&page_size=1"
2635
3180
  },
2636
3181
  { service: "agent-observability", path: "/api/agent-observability/v1/conversations?limit=1" },
2637
3182
  { service: "mf-model-manager", path: "/api/mf-model-manager/v1/llm/list" },
@@ -2644,7 +3189,7 @@ var COMMAND_SERVICE = [
2644
3189
  [/^resource\b/, "vega-backend"],
2645
3190
  [/^context\b/, "agent-retrieval"],
2646
3191
  [/^trace\b/, "agent-observability"],
2647
- [/^(skill|toolbox|tool|function)\b/, "agent-operator-integration"],
3192
+ [/^(skill|mcp|sandbox|toolbox|tool|function|api)\b/, "agent-operator-integration"],
2648
3193
  [/^model\b/, "mf-model-manager"],
2649
3194
  [/^(auth|appkey)\b/, "safe"],
2650
3195
  [/^admin (llm|small-model)\b/, "mf-model-manager"],
@@ -2656,10 +3201,11 @@ var COMMAND_TOOL = {
2656
3201
  "context query-instance-subgraph": "query_instance_subgraph",
2657
3202
  "context explore-subgraph": "explore_subgraph",
2658
3203
  "context run-sql": "run_sql",
3204
+ "context run-cypher": "run_cypher",
2659
3205
  "context query-metric": "query_metric",
2660
3206
  "context get-logic-properties": "get_logic_properties_values",
2661
3207
  "context get-action-info": "get_action_info",
2662
- "context find-skills": "find_skills",
3208
+ "context search-capabilities": "search_capabilities",
2663
3209
  "context kn-detail": "get_kn_detail",
2664
3210
  "context object-types": "get_object_types",
2665
3211
  "context relation-types": "get_relation_types"
@@ -2714,6 +3260,7 @@ var ID_SOURCES = {
2714
3260
  "at-id": "openbkn bkn action-type list <kn-id>",
2715
3261
  "metric-id": "openbkn bkn metric list <kn-id>",
2716
3262
  "skill-id": "openbkn skill list",
3263
+ "mcp-id": "openbkn mcp list",
2717
3264
  "box-id": "openbkn toolbox list",
2718
3265
  "tool-id": "openbkn tool list --toolbox <box-id>",
2719
3266
  "conversation-id": "openbkn trace conversations list",
@@ -2727,8 +3274,7 @@ var ID_SOURCES = {
2727
3274
  "conversation-ids": "openbkn trace conversations list",
2728
3275
  "model-ids": "openbkn model llm list",
2729
3276
  "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>",
3277
+ "document-ids": "openbkn vega resource document-get <resource-id> <document-ids...>",
2732
3278
  // Named entities, wherever they appear; the overrides below cover the cases
2733
3279
  // where the same word means something else.
2734
3280
  role: "openbkn admin role list",
@@ -2750,12 +3296,15 @@ var GROUP_ID_SOURCES = [
2750
3296
  [/^vega discover-schedule\b/, "openbkn vega discover-schedule list"],
2751
3297
  [/^vega discover-task\b/, "openbkn vega discover-task list"],
2752
3298
  [/^vega semantic-task\b/, "openbkn vega semantic-task list"],
2753
- [/^vega dataset\b/, "openbkn vega dataset build-list"],
3299
+ [/^vega build-task\b/, "openbkn vega build-task list"],
2754
3300
  [/^bkn action-schedule\b/, "openbkn bkn action-schedule list <kn-id>"],
2755
3301
  [/^vega connector-type\b/, "openbkn vega connector-type list"],
2756
3302
  [/^resource\b/, "openbkn resource list"],
2757
3303
  [/^skill\b/, "openbkn skill list"],
3304
+ [/^mcp\b/, "openbkn mcp list"],
2758
3305
  [/^toolbox\b/, "openbkn toolbox list"],
3306
+ [/^function\b/, "openbkn function list --toolbox <box-id>"],
3307
+ [/^api\b/, "openbkn api list --toolbox <box-id>"],
2759
3308
  [/^tool\b/, "openbkn tool list --toolbox <box-id>"],
2760
3309
  [/^appkey\b/, "openbkn appkey list"],
2761
3310
  [/^admin org\b/, "openbkn admin org list"],
@@ -2764,6 +3313,7 @@ var GROUP_ID_SOURCES = [
2764
3313
  [/^admin role\b/, "openbkn admin role list"],
2765
3314
  [/^admin llm\b/, "openbkn admin llm list"],
2766
3315
  [/^admin small-model\b/, "openbkn admin small-model list"],
3316
+ [/^admin audit\b/, "openbkn admin audit list"],
2767
3317
  [/^model llm\b/, "openbkn model llm list"],
2768
3318
  [/^model small\b/, "openbkn model small list"],
2769
3319
  [/^bkn object-type\b/, "openbkn bkn object-type list <kn-id>"],
@@ -2776,13 +3326,15 @@ var GROUP_ID_SOURCES = [
2776
3326
  function sourceOf(argName, path, isLast) {
2777
3327
  const override = ARGUMENT_OVERRIDES[`${path.join(" ")}|${argName}`];
2778
3328
  if (override !== void 0) return override ?? void 0;
3329
+ const parent = path.slice(0, -1).join(" ");
3330
+ const groupSource = GROUP_ID_SOURCES.find(([re]) => re.test(parent))?.[1];
3331
+ if (/^tool-ids?$/i.test(argName) && groupSource) return groupSource;
2779
3332
  const byName = ID_SOURCES[argName];
2780
3333
  if (byName) return byName;
2781
3334
  if (!isLast) return void 0;
2782
3335
  const generic = /^(id|ids|cg-id|modelid|role|user|schedule-id|schedule-ids|task-id|log-id)$/i.test(argName);
2783
3336
  if (!generic) return void 0;
2784
- const parent = path.slice(0, -1).join(" ");
2785
- return GROUP_ID_SOURCES.find(([re]) => re.test(parent))?.[1];
3337
+ return groupSource;
2786
3338
  }
2787
3339
  function describeOption(opt) {
2788
3340
  return {
@@ -2813,7 +3365,7 @@ function describeNode(cmd, parentPath, depth) {
2813
3365
  };
2814
3366
  if (depth <= 0) return children.length ? { ...skeleton, hasCommands: true } : skeleton;
2815
3367
  const aliases = cmd.aliases();
2816
- const options = cmd.options.filter((o) => o.long !== "--help");
3368
+ const options = cmd.options.filter((o) => o.long !== "--help" && !o.hidden);
2817
3369
  return {
2818
3370
  ...skeleton,
2819
3371
  ...aliases.length ? { aliases } : {},
@@ -2849,8 +3401,8 @@ var FIELD_MEANINGS = {
2849
3401
  guide: "prose the command's own --help prints under its command list",
2850
3402
  commands: "nested commands"
2851
3403
  };
2852
- function resolve2(program2, path) {
2853
- let node = program2;
3404
+ function resolve(program, path) {
3405
+ let node = program;
2854
3406
  for (const name of path) {
2855
3407
  const child = node.commands.find((c) => c.name() === name || c.aliases().includes(name));
2856
3408
  if (!child) throw new InputError(`no such command: ${path.join(" ")}`);
@@ -2858,29 +3410,29 @@ function resolve2(program2, path) {
2858
3410
  }
2859
3411
  return node;
2860
3412
  }
2861
- function describeCommandTree(program2, opts = {}) {
3413
+ function describeCommandTree(program, opts = {}) {
2862
3414
  activeProbe = opts.probe;
2863
3415
  const depth = opts.depth === void 0 ? Number.MAX_SAFE_INTEGER : opts.depth - 1;
2864
3416
  if (opts.path?.length) {
2865
3417
  return {
2866
3418
  sections: SECTION_MEANINGS,
2867
3419
  fields: FIELD_MEANINGS,
2868
- ...describeNode(resolve2(program2, opts.path), opts.path.slice(0, -1), depth + 1)
3420
+ ...describeNode(resolve(program, opts.path), opts.path.slice(0, -1), depth + 1)
2869
3421
  };
2870
3422
  }
2871
- const top = program2.commands.filter(
3423
+ const top = program.commands.filter(
2872
3424
  (c) => !c.name().startsWith("help") && c.name() !== "describe"
2873
3425
  );
2874
3426
  return {
2875
- name: program2.name(),
2876
- version: program2.version(),
2877
- summary: program2.description(),
3427
+ name: program.name(),
3428
+ version: program.version(),
3429
+ summary: program.description(),
2878
3430
  sections: SECTION_MEANINGS,
2879
3431
  fields: FIELD_MEANINGS,
2880
3432
  ...opts.probe ? { probe: opts.probe } : {},
2881
3433
  commands: top.map((cmd) => describeNode(cmd, [], depth)),
2882
- globalOptions: program2.options.filter((o) => o.long !== "--help").map(describeOption),
2883
- guide: guideOf(program2)
3434
+ globalOptions: program.options.filter((o) => o.long !== "--help").map(describeOption),
3435
+ guide: guideOf(program)
2884
3436
  };
2885
3437
  }
2886
3438
  function rows(node, indent = "") {
@@ -2922,14 +3474,14 @@ function renderText(tree) {
2922
3474
  }
2923
3475
  return out.join("\n");
2924
3476
  }
2925
- function describeCommand(program2) {
3477
+ function describeCommand(program) {
2926
3478
  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
3479
  "--depth <n>",
2928
3480
  "how many levels to walk (1 = this level only)",
2929
3481
  (v) => Number.parseInt(v, 10)
2930
3482
  ).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
3483
  const probe = opts.probe ? await probeDeploy(self, (/* @__PURE__ */ new Date()).toISOString()) : void 0;
2932
- const tree = describeCommandTree(program2, { path, depth: opts.depth, probe });
3484
+ const tree = describeCommandTree(program, { path, depth: opts.depth, probe });
2933
3485
  const out = outputOptions(self);
2934
3486
  if (!out.json && !out.compact && !opts.pretty) {
2935
3487
  process.stdout.write(`${renderText(tree)}
@@ -2942,12 +3494,11 @@ function describeCommand(program2) {
2942
3494
  }
2943
3495
 
2944
3496
  // src/commands/function.ts
2945
- import { readFileSync as readFileSync4 } from "fs";
3497
+ import { readFileSync as readFileSync3 } from "fs";
2946
3498
  import { Command as Command9 } from "commander";
2947
- var int5 = (v) => Number.parseInt(v, 10);
2948
3499
  function readCode(file) {
2949
3500
  try {
2950
- return readFileSync4(file === "-" ? 0 : file, "utf8");
3501
+ return readFileSync3(file === "-" ? 0 : file, "utf8");
2951
3502
  } catch (err) {
2952
3503
  throw new InputError(
2953
3504
  `Cannot read ${file === "-" ? "stdin" : file}: ${err instanceof Error ? err.message : err}`
@@ -2969,8 +3520,21 @@ function parseJsonOption(raw, label) {
2969
3520
  throw new InputError(`--${label} must be valid JSON`);
2970
3521
  }
2971
3522
  }
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");
3523
+ function parseJsonObjectOption(raw, label) {
3524
+ const parsed = parseJsonOption(raw, label);
3525
+ if (parsed === void 0) return void 0;
3526
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
3527
+ throw new InputError(`--${label} must be a JSON object`);
3528
+ }
3529
+ return parsed;
3530
+ }
3531
+ function definitionFlags(c, includeType = true, flagSet = "all") {
3532
+ if (flagSet === "openapi-import") return c;
3533
+ if (flagSet === "openapi-update") {
3534
+ return c.option("--name <n>", "tool name; required, update replaces the tool").option("--description <d>", "what it does; required, update replaces the tool");
3535
+ }
3536
+ const flags = 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("--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");
3537
+ return includeType ? flags.option("--type <t>", "function | openapi", "function") : flags;
2974
3538
  }
2975
3539
  function parameterList(raw, label) {
2976
3540
  const parsed = parseJsonOption(raw, label);
@@ -2990,18 +3554,41 @@ function functionDefinitionFrom(file, opts) {
2990
3554
  dependenciesUrl: opts.indexUrl
2991
3555
  };
2992
3556
  }
2993
- function functionCommand() {
2994
- const cmd = new Command9("function").description(
3557
+ function generationType(value) {
3558
+ return oneOf("type", FunctionAiGenerationType)(value);
3559
+ }
3560
+ function generationRequestFrom(type, opts) {
3561
+ const inputs = parameterList(opts.inputs, "inputs");
3562
+ const outputs = parameterList(opts.outputs, "outputs");
3563
+ if (type === "python_function_generator") {
3564
+ if (!opts.query?.trim()) {
3565
+ throw new InputError("--query is required for python_function_generator");
3566
+ }
3567
+ if (opts.code !== void 0) {
3568
+ throw new InputError("--code only applies to metadata_param_generator");
3569
+ }
3570
+ return { query: opts.query, inputs, outputs };
3571
+ }
3572
+ if (opts.query !== void 0) {
3573
+ throw new InputError("--query only applies to python_function_generator");
3574
+ }
3575
+ if (!opts.code) {
3576
+ throw new InputError("--code <file> is required for metadata_param_generator");
3577
+ }
3578
+ return { code: readCode(opts.code), inputs, outputs };
3579
+ }
3580
+ function sandboxCommand() {
3581
+ const cmd = new Command9("sandbox").description(
2995
3582
  "Sandbox functions: run Python on the platform without registering anything"
2996
3583
  );
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(
3584
+ 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", positiveInt("--timeout")).option("--dep <name@version>", "install a package first (repeatable)", collectDep).option("--index-url <url>", "package index to install from (default PyPI)").option(
2998
3585
  "--pass-token",
2999
3586
  "put your credential in the sandbox's BKN_TOKEN so `sandbox_sdk.bkn` calls BKN as you"
3000
3587
  ).action(async (file, opts, cmd2) => {
3001
3588
  const client = clientFrom(cmd2);
3002
3589
  const result = await client.functions.run({
3003
3590
  code: readCode(file),
3004
- event: parseJsonOption(opts.event, "event") ?? {},
3591
+ event: parseJsonObjectOption(opts.event, "event") ?? {},
3005
3592
  timeout: opts.timeout,
3006
3593
  dependencies: opts.dep,
3007
3594
  dependenciesUrl: opts.indexUrl,
@@ -3032,12 +3619,38 @@ function functionCommand() {
3032
3619
  outputOptions(cmd2)
3033
3620
  );
3034
3621
  });
3035
- cmd.command("template").description("The handler() skeleton to start from").option("--type <t>", "template type (python)", "python").action(async (opts, cmd2) => {
3622
+ cmd.command("template").description("The handler() skeleton to start from").option(
3623
+ "--type <t>",
3624
+ "template type (python)",
3625
+ oneOf("--type", FUNCTION_TEMPLATE_TYPES),
3626
+ "python"
3627
+ ).action(async (opts, cmd2) => {
3036
3628
  printJson(await clientFrom(cmd2).functions.template(opts.type), outputOptions(cmd2));
3037
3629
  });
3630
+ cmd.command("generate <type>").description("Generate function code or parameter metadata with the platform's default LLM").option("--query <text>", "natural-language request (python_function_generator)").option("--code <file>", "existing code to analyse (metadata_param_generator; `-` reads stdin)").option("--inputs <json>", "known input parameters to constrain generation").option("--outputs <json>", "known output parameters to constrain generation").option(
3631
+ "--timeout <s>",
3632
+ "seconds to wait for the model (default 300; a default ingress answers 504 at 60)",
3633
+ positiveInt("--timeout")
3634
+ ).action(async (type, opts, cmd2) => {
3635
+ const direction = generationType(type);
3636
+ printJson(
3637
+ await clientFrom(cmd2).functions.generate(
3638
+ direction,
3639
+ generationRequestFrom(direction, opts),
3640
+ opts.timeout === void 0 ? {} : { timeoutMs: opts.timeout * 1e3 }
3641
+ ),
3642
+ outputOptions(cmd2)
3643
+ );
3644
+ });
3645
+ cmd.command("prompt <type>").description("Read the prompt template used for one Function AI generation direction").action(async (type, _opts, cmd2) => {
3646
+ printJson(
3647
+ await clientFrom(cmd2).functions.promptTemplate(generationType(type)),
3648
+ outputOptions(cmd2)
3649
+ );
3650
+ });
3038
3651
  groupChildren(cmd, {
3039
- READ: ["deps", "versions", "template"],
3040
- RUN: ["run", "infer-schema"]
3652
+ READ: ["deps", "versions", "template", "prompt"],
3653
+ RUN: ["run", "infer-schema", "generate"]
3041
3654
  });
3042
3655
  guide(
3043
3656
  cmd,
@@ -3048,7 +3661,7 @@ function functionCommand() {
3048
3661
  return {"sum": event.get("a", 0) + event.get("b", 0)}
3049
3662
 
3050
3663
  \`--event\` is that argument, the return value comes back as \`result\`, and
3051
- \`print\` output as \`stdout\`. \`function template\` prints the skeleton.
3664
+ \`print\` output as \`stdout\`. \`sandbox template\` prints the skeleton.
3052
3665
 
3053
3666
  READING THE ANSWER
3054
3667
  Code that raises still answers HTTP 200 \u2014 \`exit_code\` is the verdict and the
@@ -3061,24 +3674,168 @@ function functionCommand() {
3061
3674
  --pass-token puts it in BKN_TOKEN so that code runs as you.
3062
3675
 
3063
3676
  ORDER OF WORK
3064
- function deps what is already importable
3065
- function run ./add.py --event ... iterate here; nothing is kept
3677
+ sandbox deps what is already importable
3678
+ sandbox run ./add.py --event ... iterate here; nothing is kept
3679
+ sandbox generate python_function_generator --query "..."
3680
+ draft a handler with the platform model
3066
3681
  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`
3682
+ function create ./add.py --toolbox the same code, now a registered Function Tool
3683
+ function debug <tool-id> --toolbox try it before enabling or publishing
3684
+ function enable <tool-id> --toolbox
3685
+ a tool is off until enabled
3686
+ toolbox publish <box-id> execute needs a published box
3687
+ function execute <tool-id> --toolbox
3688
+ the call agents make
3689
+
3690
+ LONG JOBS
3691
+ A registered function called through the toolbox is cut at about 30s whatever
3692
+ its --timeout, and answers 200 with result: null. Keep long work on
3693
+ \`sandbox run\`, which waits for the sandbox's own limit. The ingress still
3694
+ answers 504 when its read timeout passes: 60s by default, unless the deploy
3695
+ raises proxy-read-timeout.`
3696
+ );
3697
+ return group(cmd, "TOOLS & SKILLS");
3698
+ }
3699
+
3700
+ // src/commands/mcp.ts
3701
+ import { Command as Command10, Option as Option3 } from "commander";
3702
+ var REDACTED = "<redacted>";
3703
+ var SECRET_NAME = /(^|[-_.])(authorization|auth|cookie|token|api[-_]?key|apikey|secret|pass|password|passwd|pwd|credentials?|private[-_]?key|access[-_]?key([-_]?id)?|secret[-_]?key|ak|sk)$/i;
3704
+ var SCHEMA_KEYS = /* @__PURE__ */ new Set(["inputSchema", "input_schema", "outputSchema", "output_schema"]);
3705
+ function redactUrl(value) {
3706
+ let url;
3707
+ try {
3708
+ url = new URL(value);
3709
+ } catch {
3710
+ return value;
3711
+ }
3712
+ let changed = false;
3713
+ if (url.password) {
3714
+ url.password = REDACTED;
3715
+ changed = true;
3716
+ }
3717
+ for (const name of [...url.searchParams.keys()]) {
3718
+ if (SECRET_NAME.test(name)) {
3719
+ url.searchParams.set(name, REDACTED);
3720
+ changed = true;
3721
+ }
3722
+ }
3723
+ return changed ? url.toString() : value;
3724
+ }
3725
+ function redactArgs(args) {
3726
+ return args.map((arg, i) => {
3727
+ if (typeof arg !== "string") return arg;
3728
+ const assigned = /^(-{0,2})([^=\s]+)=(.*)$/.exec(arg);
3729
+ if (assigned?.[2] && SECRET_NAME.test(assigned[2])) {
3730
+ return `${assigned[1]}${assigned[2]}=${REDACTED}`;
3731
+ }
3732
+ const previous = args[i - 1];
3733
+ if (typeof previous === "string" && /^--?[^=\s]+$/.test(previous)) {
3734
+ if (SECRET_NAME.test(previous.replace(/^-+/, ""))) return REDACTED;
3735
+ }
3736
+ return arg;
3737
+ });
3738
+ }
3739
+ function maskValues(value) {
3740
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
3741
+ return Object.fromEntries(Object.keys(value).map((key) => [key, REDACTED]));
3742
+ }
3743
+ function redactMcpOutput(value) {
3744
+ if (Array.isArray(value)) return value.map(redactMcpOutput);
3745
+ if (!value || typeof value !== "object") return value;
3746
+ return Object.fromEntries(
3747
+ Object.entries(value).map(([key, item]) => {
3748
+ if (SCHEMA_KEYS.has(key)) return [key, item];
3749
+ if (key === "headers" || key === "env") return [key, maskValues(item)];
3750
+ if (key === "args" && Array.isArray(item)) return [key, redactArgs(item)];
3751
+ if (typeof item === "string" && /url$/i.test(key)) return [key, redactUrl(item)];
3752
+ if (item !== null && typeof item !== "object" && SECRET_NAME.test(key)) {
3753
+ return [key, REDACTED];
3754
+ }
3755
+ return [key, redactMcpOutput(item)];
3756
+ })
3757
+ );
3758
+ }
3759
+ function mcpCommand() {
3760
+ const cmd = new Command10("mcp").description(
3761
+ "Registered MCP Servers: inspect their configuration and discover their tools"
3762
+ );
3763
+ cmd.command("list").description("List MCP Servers you can access").option("--name <text>", "filter by name").option("--source <text>", "filter by source").option("--category <text>", "filter by category").option("--status <status>", "filter by status (unpublish|published|offline|editing)").option("--create-user <text>", "filter by creator").option("--internal", "show only internal servers").addOption(
3764
+ new Option3("--mode <mode>", "filter by connection mode").choices([
3765
+ "sse",
3766
+ "stream",
3767
+ "stdio_uv",
3768
+ "stdio_npx"
3769
+ ])
3770
+ ).addOption(
3771
+ new Option3("--sort-by <field>", "sort field").choices(["update_time", "create_time", "name"])
3772
+ ).addOption(new Option3("--sort-order <order>", "sort direction").choices(["asc", "desc"])).option(
3773
+ "--limit <n>",
3774
+ "page size (1-100)",
3775
+ positiveInt("--limit", MAX_PAGE_SIZE),
3776
+ DEFAULT_LIST_LIMIT
3777
+ ).option("--page <n>", "page (1-based)", positiveInt("--page"), 1).option("--all", "return every accessible server (ignores --limit/--page defaults)").action(async (opts, command) => {
3778
+ const paging = (flag) => opts.all && command.getOptionValueSource(flag) === "default" ? void 0 : opts[flag];
3779
+ printJson(
3780
+ redactMcpOutput(
3781
+ await clientFrom(command).mcp.list({
3782
+ page: paging("page"),
3783
+ pageSize: paging("limit"),
3784
+ sortBy: opts.sortBy,
3785
+ sortOrder: opts.sortOrder,
3786
+ name: opts.name,
3787
+ source: opts.source,
3788
+ category: opts.category,
3789
+ status: opts.status,
3790
+ createUser: opts.createUser,
3791
+ isInternal: opts.internal || void 0,
3792
+ mode: opts.mode,
3793
+ all: opts.all || void 0
3794
+ })
3795
+ ),
3796
+ outputOptions(command)
3797
+ );
3798
+ });
3799
+ cmd.command("get <mcp-id>").description("Read one MCP Server's configuration and platform connection addresses").action(async (mcpId, _opts, command) => {
3800
+ printJson(redactMcpOutput(await clientFrom(command).mcp.get(mcpId)), outputOptions(command));
3801
+ });
3802
+ cmd.command("tools <mcp-id>").description("List the MCP protocol tools a registered server advertises; does not invoke one").option("--draft", "list the tools of the draft configuration (needs view permission)").action(async (mcpId, opts, command) => {
3803
+ printJson(
3804
+ redactMcpOutput(
3805
+ await clientFrom(command).mcp.tools(mcpId, { draft: opts.draft || void 0 })
3806
+ ),
3807
+ outputOptions(command)
3808
+ );
3809
+ });
3810
+ groupChildren(cmd, { READ: ["list", "get", "tools"] });
3811
+ guide(
3812
+ cmd,
3813
+ `WHAT THIS READS
3814
+ These are registered MCP Servers in the execution factory, not Context Loader's
3815
+ agent-facing MCP retrieval interface. \`mcp get\` includes the platform connection
3816
+ addresses; \`mcp tools\` only discovers the remote tool schemas and does not call one.
3817
+
3818
+ ORDER OF WORK
3819
+ mcp list choose an accessible server
3820
+ mcp get <mcp-id> inspect its configuration and connection addresses
3821
+ mcp tools <mcp-id> inspect tool names and input schemas
3822
+
3823
+ CREDENTIALS
3824
+ Output masks header and env values, secrets in launch args and URLs, and
3825
+ credential-named fields. Tool input schemas are printed as advertised.`
3070
3826
  );
3071
3827
  return group(cmd, "TOOLS & SKILLS");
3072
3828
  }
3073
3829
 
3074
3830
  // src/commands/model.ts
3075
- import { Command as Command10 } from "commander";
3076
- var int6 = (v) => Number.parseInt(v, 10);
3831
+ import { Command as Command11 } from "commander";
3832
+ var int5 = (v) => Number.parseInt(v, 10);
3077
3833
  async function resolveLlmModelName(client, model) {
3078
3834
  if (!/^\d+$/.test(model)) return model;
3079
3835
  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;
3836
+ const name = detail?.model_name ?? detail?.data?.model_name;
3837
+ if (!name) throw new InputError(`No LLM found with id ${model}.`);
3838
+ return name;
3082
3839
  }
3083
3840
  function addManagementCommands(parent, kind) {
3084
3841
  parent.command("add").description("Register a model (definition JSON via --body / --body-file)").option(
@@ -3093,6 +3850,14 @@ function addManagementCommands(parent, kind) {
3093
3850
  ).option("--body-file <path>", "read model definition JSON from a file").action(async (opts, cmd) => {
3094
3851
  printJson(await clientFrom(cmd).models[kind].edit(readBody(opts)), outputOptions(cmd));
3095
3852
  });
3853
+ if (kind === "llm") {
3854
+ parent.commands.find((c) => c.name() === "edit")?.addHelpText(
3855
+ "after",
3856
+ `
3857
+ Send the full definition, including "quota" (true | false): some platform
3858
+ versions answer an edit without it with HTTP 500 ModelFactory.Mydb.DataBase.ParameterError.`
3859
+ );
3860
+ }
3096
3861
  parent.command("delete <model-ids>").description("Delete model(s) (comma-joined ids)").action(async (ids, _o, cmd) => {
3097
3862
  printJson(await clientFrom(cmd).models[kind].delete(csv(ids) ?? []), outputOptions(cmd));
3098
3863
  });
@@ -3104,11 +3869,11 @@ function addManagementCommands(parent, kind) {
3104
3869
  });
3105
3870
  }
3106
3871
  function modelCommand() {
3107
- const model = new Command10("model").description(
3872
+ const model = new Command11("model").description(
3108
3873
  "Large and small models: chat, embeddings, rerank, defaults"
3109
3874
  );
3110
3875
  const llm = model.command("llm").description("Large language models");
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) => {
3876
+ 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", int5, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int5, 1).action(async (opts, cmd) => {
3112
3877
  printJson(
3113
3878
  await clientFrom(cmd).models.llm.list({
3114
3879
  name: opts.name,
@@ -3141,7 +3906,7 @@ function modelCommand() {
3141
3906
  });
3142
3907
  addManagementCommands(llm, "llm");
3143
3908
  const small = model.command("small").description("Small models (embedding / reranker)");
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) => {
3909
+ 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", int5, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int5, 1).action(async (opts, cmd) => {
3145
3910
  printJson(
3146
3911
  await clientFrom(cmd).models.small.list({
3147
3912
  name: opts.name,
@@ -3203,24 +3968,152 @@ Examples:
3203
3968
  }
3204
3969
 
3205
3970
  // src/commands/resource.ts
3206
- import { Command as Command11 } from "commander";
3207
- var int7 = (v) => Number.parseInt(v, 10);
3971
+ import { Command as Command12 } from "commander";
3972
+ var int6 = (flag) => (v) => {
3973
+ if (!/^-?\d+$/.test(v)) throw new InputError(`${flag} must be an integer (got '${v}')`);
3974
+ const n = Number(v);
3975
+ if (!Number.isSafeInteger(n)) throw new InputError(`${flag} must be an integer (got '${v}')`);
3976
+ return n;
3977
+ };
3978
+ var nonNegativeInt2 = (flag) => (v) => {
3979
+ const n = int6(flag)(v);
3980
+ if (n < 0) throw new InputError(`${flag} must be 0 or greater (got '${v}')`);
3981
+ return n;
3982
+ };
3983
+ var keepAliveSec = (v) => {
3984
+ const n = int6("--keep-alive-sec")(v);
3985
+ if (n < 60 || n > 3600) {
3986
+ throw new InputError(`--keep-alive-sec must be an integer from 60 to 3600 (got '${v}')`);
3987
+ }
3988
+ return n;
3989
+ };
3990
+ var SORT_FIELDS = ["name", "create_time", "update_time"];
3991
+ var DIRECTIONS2 = ["asc", "desc"];
3992
+ function addResourceListOptions(cmd, categoryFlag) {
3993
+ const alias = categoryFlag === "--category" ? "--type" : "--category";
3994
+ return cmd.option("--catalog-id <id>", "filter by catalog id").option("--name <name>", "filter by name").option(
3995
+ `${categoryFlag} <category>`,
3996
+ `resource category: ${ResourceCategory.options.join(" | ")}`,
3997
+ oneOf(categoryFlag, ResourceCategory.options)
3998
+ ).option(
3999
+ `${alias} <category>`,
4000
+ `alias of ${categoryFlag}`,
4001
+ oneOf(alias, ResourceCategory.options)
4002
+ ).option(
4003
+ "--status <status>",
4004
+ `filter by status: ${ResourceStatus.options.join(" | ")}`,
4005
+ oneOf("--status", ResourceStatus.options)
4006
+ ).option(
4007
+ "--enabled <bool>",
4008
+ "filter by enabled state: true | false",
4009
+ oneOf("--enabled", ["true", "false"])
4010
+ ).option("--last-discover-status <status>", "filter by latest discovery status").option("--schema <name>", "filter by source schema").option("--limit <n>", "page size", int6("--limit"), DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", nonNegativeInt2("--offset"), 0).option(
4011
+ "--sort <field>",
4012
+ `sort field: ${SORT_FIELDS.join(" | ")}`,
4013
+ oneOf("--sort", SORT_FIELDS)
4014
+ ).option(
4015
+ "--direction <dir>",
4016
+ `sort direction: ${DIRECTIONS2.join(" | ")}`,
4017
+ oneOf("--direction", DIRECTIONS2)
4018
+ );
4019
+ }
4020
+ function resourceListOptionsFrom(opts) {
4021
+ return {
4022
+ catalogId: opts.catalogId,
4023
+ name: opts.name,
4024
+ category: opts.category ?? opts.type,
4025
+ status: opts.status,
4026
+ enabled: opts.enabled === void 0 ? void 0 : opts.enabled === "true",
4027
+ lastDiscoverStatus: opts.lastDiscoverStatus,
4028
+ schema: opts.schema,
4029
+ limit: opts.limit,
4030
+ offset: opts.offset,
4031
+ sort: opts.sort,
4032
+ direction: opts.direction
4033
+ };
4034
+ }
4035
+ function addResourceQueryOptions(cmd) {
4036
+ return cmd.option("--limit <n>", "row limit", int6("--limit")).option("--offset <n>", "row offset", nonNegativeInt2("--offset")).option(
4037
+ "--paging-mode <mode>",
4038
+ "paging mode: single | cursor",
4039
+ oneOf("--paging-mode", ["single", "cursor"])
4040
+ ).option("--keep-alive-sec <s>", "cursor keep-alive in seconds (60\u20133600)", keepAliveSec).option("--cursor <cursor>", "opaque cursor returned by the previous page").option("--need-total", "include total count").option("--filter <json>", "Vega filter_condition JSON object").option("--sort <field:dir,...>", "sort fields, e.g. create_time:desc,id:asc").option("--output-fields <f1,f2>", "comma-separated fields to return").option(
4041
+ "--binary-mode <mode>",
4042
+ "Binary fields as metadata (byte length, default) | content (Base64)",
4043
+ oneOf("--binary-mode", ["metadata", "content"])
4044
+ ).option("--ignore-local-index", "read a table's source even when a local index is available");
4045
+ }
4046
+ function parseFilter(raw) {
4047
+ let parsed;
4048
+ try {
4049
+ parsed = parseBigIntJSON(raw);
4050
+ } catch {
4051
+ throw new InputError("--filter must be valid JSON");
4052
+ }
4053
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
4054
+ throw new InputError("--filter must be a JSON object");
4055
+ }
4056
+ return parsed;
4057
+ }
4058
+ function parseSort(raw) {
4059
+ const items = csv(raw) ?? [];
4060
+ if (items.length === 0) throw new InputError("--sort must name at least one field");
4061
+ return items.map((item) => {
4062
+ const at = item.lastIndexOf(":");
4063
+ const field = at === -1 ? item : item.slice(0, at);
4064
+ const direction = at === -1 ? "asc" : item.slice(at + 1);
4065
+ if (!field || direction !== "asc" && direction !== "desc") {
4066
+ throw new InputError(`--sort entries must be field[:asc|desc] (got '${item}')`);
4067
+ }
4068
+ return { field, direction };
4069
+ });
4070
+ }
4071
+ function resourceQueryOptionsFrom(opts, defaultLimit) {
4072
+ if (opts.cursor) {
4073
+ const initialOnly = [
4074
+ ["--limit", opts.limit],
4075
+ ["--offset", opts.offset],
4076
+ ["--paging-mode", opts.pagingMode],
4077
+ ["--keep-alive-sec", opts.keepAliveSec],
4078
+ ["--filter", opts.filter],
4079
+ ["--sort", opts.sort],
4080
+ ["--output-fields", opts.outputFields],
4081
+ ["--binary-mode", opts.binaryMode],
4082
+ ["--ignore-local-index", opts.ignoreLocalIndex]
4083
+ ].filter(([, value]) => value !== void 0);
4084
+ if (initialOnly.length > 0) {
4085
+ throw new InputError(
4086
+ `--cursor cannot be combined with initial-query options (${initialOnly.map(([flag]) => flag).join(", ")})`
4087
+ );
4088
+ }
4089
+ return {
4090
+ cursor: opts.cursor,
4091
+ ...opts.needTotal ? { needTotal: true } : {}
4092
+ };
4093
+ }
4094
+ return {
4095
+ limit: opts.limit ?? defaultLimit,
4096
+ offset: opts.offset ?? 0,
4097
+ pagingMode: opts.pagingMode,
4098
+ keepAliveSec: opts.keepAliveSec,
4099
+ needTotal: opts.needTotal ? true : void 0,
4100
+ ...opts.filter !== void 0 ? { filterCondition: parseFilter(opts.filter) } : {},
4101
+ ...opts.sort !== void 0 ? { sort: parseSort(opts.sort) } : {},
4102
+ ...opts.outputFields !== void 0 ? { outputFields: csv(opts.outputFields) ?? [] } : {},
4103
+ ...opts.binaryMode !== void 0 ? { binaryMode: opts.binaryMode } : {},
4104
+ ...opts.ignoreLocalIndex ? { ignoreLocalIndex: true } : {}
4105
+ };
4106
+ }
3208
4107
  function resourceCommand() {
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) => {
3211
- const data = await clientFrom(cmd2).resource.list({
3212
- catalogId: opts.catalogId,
3213
- category: opts.category ?? opts.type,
3214
- status: opts.status,
3215
- schema: opts.schema,
3216
- limit: opts.limit,
3217
- offset: opts.offset,
3218
- sort: opts.sort,
3219
- direction: opts.direction
3220
- });
4108
+ const cmd = new Command12("resource").alias("res").description("Tables and views behind a network: find, inspect, sample, enable, disable");
4109
+ addResourceListOptions(
4110
+ cmd.command("list").description("List resources under a catalog"),
4111
+ "--category"
4112
+ ).action(async (opts, cmd2) => {
4113
+ const data = await clientFrom(cmd2).resource.list(resourceListOptionsFrom(opts));
3221
4114
  printJson(data, outputOptions(cmd2));
3222
4115
  });
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) => {
4116
+ 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", int6("--limit"), DEFAULT_LIST_LIMIT).action(async (opts, cmd2) => {
3224
4117
  const data = await clientFrom(cmd2).resource.find(opts.name, {
3225
4118
  exact: opts.exact,
3226
4119
  catalogId: opts.catalogId,
@@ -3238,15 +4131,13 @@ function resourceCommand() {
3238
4131
  printJson(result, outputOptions(cmd2));
3239
4132
  });
3240
4133
  }
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) => {
3242
- const data = await clientFrom(cmd2).resource.query(id, {
3243
- limit: opts.limit,
3244
- offset: opts.offset,
3245
- pagingMode: opts.pagingMode,
3246
- keepAliveSec: opts.keepAliveSec,
3247
- cursor: opts.cursor,
3248
- needTotal: opts.needTotal
3249
- });
4134
+ addResourceQueryOptions(
4135
+ cmd.command("query <id>").description(`Fetch data rows from a resource (default limit ${DEFAULT_QUERY_LIMIT})`)
4136
+ ).action(async (id, opts, cmd2) => {
4137
+ const data = await clientFrom(cmd2).resource.query(
4138
+ id,
4139
+ resourceQueryOptionsFrom(opts, DEFAULT_QUERY_LIMIT)
4140
+ );
3250
4141
  printJson(data, outputOptions(cmd2));
3251
4142
  });
3252
4143
  cmd.command("delete <id>").description("Delete a resource").option("-y, --yes", "skip confirmation").action(async (id, _opts, cmd2) => {
@@ -3261,18 +4152,7 @@ function resourceCommand() {
3261
4152
  }
3262
4153
 
3263
4154
  // src/commands/skill.ts
3264
- import { Command as Command12 } from "commander";
3265
- var int8 = (v) => Number.parseInt(v, 10);
3266
- var positiveInt = (flag) => (v) => {
3267
- if (!/^\d+$/.test(v)) {
3268
- throw new InputError(`${flag} must be a positive integer (got '${v}')`);
3269
- }
3270
- const n = Number.parseInt(v, 10);
3271
- if (!Number.isSafeInteger(n) || n <= 0) {
3272
- throw new InputError(`${flag} must be a positive integer (got '${v}')`);
3273
- }
3274
- return n;
3275
- };
4155
+ import { Command as Command13 } from "commander";
3276
4156
  var MOCKED_EXIT_CODE = 125;
3277
4157
  function sandboxExitCode(result) {
3278
4158
  if (result?.mocked) return MOCKED_EXIT_CODE;
@@ -3280,31 +4160,70 @@ function sandboxExitCode(result) {
3280
4160
  if (!Number.isSafeInteger(code) || code < 0) return 1;
3281
4161
  return code > 255 ? 1 : code;
3282
4162
  }
3283
- var SKILL_SOURCES = ["custom", "internal"];
4163
+ function metadataBody(body) {
4164
+ if (body === null || typeof body !== "object" || Array.isArray(body)) {
4165
+ throw new InputError("update-metadata body must be a JSON object");
4166
+ }
4167
+ const b = body;
4168
+ const missing = ["name", "description", "category"].filter(
4169
+ (k) => typeof b[k] !== "string" || b[k].length === 0
4170
+ );
4171
+ if (missing.length > 0) {
4172
+ throw new InputError(
4173
+ `update-metadata replaces the metadata: ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} required (non-empty string)`
4174
+ );
4175
+ }
4176
+ if (b.source !== void 0 && !SKILL_SOURCES.includes(b.source)) {
4177
+ throw new InputError(
4178
+ `source must be one of: ${SKILL_SOURCES.join(" | ")} (got ${JSON.stringify(b.source)})`
4179
+ );
4180
+ }
4181
+ const ext = b.extend_info;
4182
+ if (ext !== void 0 && (ext === null || typeof ext !== "object" || Array.isArray(ext))) {
4183
+ throw new InputError("extend_info must be a JSON object");
4184
+ }
4185
+ return b;
4186
+ }
3284
4187
  function checkSource(source) {
3285
4188
  if (source === void 0 || SKILL_SOURCES.includes(source)) return source;
3286
4189
  throw new InputError(`--source must be one of: ${SKILL_SOURCES.join(" | ")} (got '${source}')`);
3287
4190
  }
3288
4191
  var draftOption = (c) => c.option("--draft", "read the draft (management) version instead of the published one");
3289
4192
  function skillCommand() {
3290
- const cmd = new Command12("skill").description(
4193
+ const cmd = new Command13("skill").description(
3291
4194
  "Skill packages (SKILL.md + files) agents load on demand"
3292
4195
  );
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);
4196
+ const listOpts = (c) => c.option("--name <s>", "filter by name").option("--category <s>", "filter by category").option("--create-user <s>", "filter by creator").option(
4197
+ "--sort-by <f>",
4198
+ "create_time | update_time | name (service default update_time)",
4199
+ oneOf("--sort-by", ["create_time", "update_time", "name"])
4200
+ ).option("--sort-order <o>", "asc | desc", oneOf("--sort-order", ["asc", "desc"])).option(
4201
+ "--limit <n>",
4202
+ `page size, 1-${MAX_PAGE_SIZE}`,
4203
+ positiveInt("--limit", MAX_PAGE_SIZE),
4204
+ DEFAULT_LIST_LIMIT
4205
+ ).option("--page <n>", "page (1-based)", positiveInt("--page"), 1).option("--all", "return every skill, ignoring page size");
4206
+ const listFilters = (opts) => ({
4207
+ name: opts.name,
4208
+ category: opts.category,
4209
+ createUser: opts.createUser,
4210
+ sortBy: opts.sortBy,
4211
+ sortOrder: opts.sortOrder,
4212
+ pageSize: opts.limit,
4213
+ page: opts.page,
4214
+ all: opts.all
4215
+ });
3294
4216
  listOpts(
3295
4217
  cmd.command("list").description(
3296
4218
  "List skills \u2192 {data, total, page, page_size, has_next}; the id to reuse is `skill_id`"
3297
4219
  )
3298
- ).option("--create-user <s>", "filter by creator").action(async (opts, cmd2) => {
4220
+ ).option(
4221
+ "--status <s>",
4222
+ "unpublish | published | offline | editing",
4223
+ oneOf("--status", ["unpublish", "published", "offline", "editing"])
4224
+ ).action(async (opts, cmd2) => {
3299
4225
  printJson(
3300
- await clientFrom(cmd2).skills.list({
3301
- name: opts.name,
3302
- source: opts.source,
3303
- status: opts.status,
3304
- createUser: opts.createUser,
3305
- pageSize: opts.limit,
3306
- page: opts.page
3307
- }),
4226
+ await clientFrom(cmd2).skills.list({ ...listFilters(opts), status: opts.status }),
3308
4227
  outputOptions(cmd2)
3309
4228
  );
3310
4229
  });
@@ -3314,15 +4233,7 @@ function skillCommand() {
3314
4233
  listOpts(
3315
4234
  cmd.command("market").description("Browse the skill market \u2192 {data, total, page, has_next}")
3316
4235
  ).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
- );
4236
+ printJson(await clientFrom(cmd2).skills.market(listFilters(opts)), outputOptions(cmd2));
3326
4237
  });
3327
4238
  cmd.command("market-get <skill-id>").description("Get a market skill by id").action(async (id, _opts, cmd2) => {
3328
4239
  printJson(await clientFrom(cmd2).skills.marketGet(id), outputOptions(cmd2));
@@ -3425,17 +4336,16 @@ ${files.length} files, ${bytes} B
3425
4336
  cmd.command("history <skill-id>").description("Show a skill's version history").action(async (id, _opts, cmd2) => {
3426
4337
  printJson(await clientFrom(cmd2).skills.history(id), outputOptions(cmd2));
3427
4338
  });
3428
- cmd.command("set-status <skill-id> <status>").description("Change status: unpublish | published | offline").action(async (id, status2, _opts, cmd2) => {
3429
- printJson(
3430
- await clientFrom(cmd2).skills.setStatus(id, status2),
3431
- outputOptions(cmd2)
3432
- );
4339
+ cmd.command("set-status <skill-id> <status>").description(`Publish or take down a skill: ${SKILL_STATUSES.join(" | ")}`).action(async (id, status2, _opts, cmd2) => {
4340
+ const target = oneOf("<status>", SKILL_STATUSES)(status2);
4341
+ printJson(await clientFrom(cmd2).skills.setStatus(id, target), outputOptions(cmd2));
3433
4342
  });
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) => {
4343
+ cmd.command("register <directory>").description("Zip a local skill directory and register it").option("--source <s>", `source tag: ${SKILL_SOURCES.join(" | ")}`, "custom").option("--category <s>", "category (service default other_category)").option("--extend-info <json>", "extra metadata as JSON").action(async (dir, opts, cmd2) => {
3435
4344
  const extendInfo = opts.extendInfo ? parseBigIntJSON(opts.extendInfo) : void 0;
3436
4345
  printJson(
3437
4346
  await clientFrom(cmd2).skills.register(dir, {
3438
4347
  source: checkSource(opts.source),
4348
+ category: opts.category,
3439
4349
  extendInfo
3440
4350
  }),
3441
4351
  outputOptions(cmd2)
@@ -3450,22 +4360,26 @@ ${files.length} files, ${bytes} B
3450
4360
  cmd.command("install <skill-id> [directory]").description("Download a skill archive and extract it locally").action(async (skillId, dir, _o, cmd2) => {
3451
4361
  printJson(await clientFrom(cmd2).skills.install(skillId, dir), outputOptions(cmd2));
3452
4362
  });
3453
- cmd.command("update-metadata <skill-id>").description("Update a skill's metadata (--body / --body-file JSON)").option(
4363
+ cmd.command("update-metadata <skill-id>").description(
4364
+ "Replace a skill's metadata (--body / --body-file JSON: name, description, category required)"
4365
+ ).option(
3454
4366
  "--body <json>",
3455
- "metadata JSON \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (execution-factory)"
4367
+ "{name, description, category, source?: custom|internal, extend_info?} \u2014 a full overwrite"
3456
4368
  ).option("--body-file <path>", "read metadata JSON from a file").action(async (skillId, opts, cmd2) => {
3457
4369
  printJson(
3458
- await clientFrom(cmd2).skills.updateMetadata(skillId, readBody(opts)),
4370
+ await clientFrom(cmd2).skills.updateMetadata(skillId, metadataBody(readBody(opts))),
3459
4371
  outputOptions(cmd2)
3460
4372
  );
3461
4373
  });
3462
4374
  cmd.command("update-package <skill-id> <directory>").description("Replace a skill's package from a local directory").action(async (skillId, dir, _o, cmd2) => {
3463
4375
  printJson(await clientFrom(cmd2).skills.updatePackage(skillId, dir), outputOptions(cmd2));
3464
4376
  });
3465
- cmd.command("republish <skill-id>").description("Republish a previous skill version").requiredOption("--version <v>", "version to republish").action(async (skillId, opts, cmd2) => {
4377
+ cmd.command("republish <skill-id>").description(
4378
+ "Copy a historical version back into the draft (publishes nothing; see publish-history)"
4379
+ ).requiredOption("--version <v>", "historical version to restore into the draft").action(async (skillId, opts, cmd2) => {
3466
4380
  printJson(await clientFrom(cmd2).skills.republish(skillId, opts.version), outputOptions(cmd2));
3467
4381
  });
3468
- cmd.command("publish-history <skill-id>").description("Publish a historical skill version").requiredOption("--version <v>", "version to publish").action(async (skillId, opts, cmd2) => {
4382
+ cmd.command("publish-history <skill-id>").description("Publish a historical skill version directly").requiredOption("--version <v>", "version to publish").action(async (skillId, opts, cmd2) => {
3469
4383
  printJson(
3470
4384
  await clientFrom(cmd2).skills.publishHistory(skillId, opts.version),
3471
4385
  outputOptions(cmd2)
@@ -3506,30 +4420,87 @@ PUBLISHED VS DRAFT
3506
4420
 
3507
4421
  AUTHORING
3508
4422
  register <dir> zips and registers; update-package replaces the files; update-metadata
3509
- changes only the metadata. set-status and republish move versions around.`
4423
+ changes only the metadata (a full overwrite: name, description, category required).
4424
+ set-status publishes or takes down the current version. republish copies a historical
4425
+ version back into the draft and publishes nothing; publish-history publishes one.`
3510
4426
  );
3511
4427
  return group(cmd, "TOOLS & SKILLS");
3512
4428
  }
3513
4429
 
3514
4430
  // src/commands/toolbox.ts
3515
- import { Command as Command13 } from "commander";
4431
+ import { Command as Command14, Option as Option4 } from "commander";
3516
4432
  import yaml from "js-yaml";
3517
- var int9 = (v) => Number.parseInt(v, 10);
4433
+ var SORT_ORDERS = ["asc", "desc"];
4434
+ var TOOLBOX_STATUSES = ["unpublish", "published", "offline"];
4435
+ function toolCallFailed(result) {
4436
+ if (!result || typeof result !== "object") return false;
4437
+ const { status_code: status2, error } = result;
4438
+ if (typeof status2 === "number" && status2 >= 400) return true;
4439
+ return typeof error === "string" ? error.length > 0 : error !== void 0 && error !== null;
4440
+ }
4441
+ function pathParams(parsed) {
4442
+ if (parsed === void 0) return void 0;
4443
+ for (const [k, v] of Object.entries(parsed)) {
4444
+ if (typeof v !== "string") {
4445
+ throw new InputError(
4446
+ `--path values must be strings (path parameter '${k}' is ${v === null ? "null" : Array.isArray(v) ? "an array" : typeof v})`
4447
+ );
4448
+ }
4449
+ }
4450
+ return parsed;
4451
+ }
4452
+ function globalParameterOption(raw) {
4453
+ const parsed = parseJsonObjectOption(raw, "global-parameter");
4454
+ if (parsed === void 0) return void 0;
4455
+ for (const k of ["name", "description", "in", "type"]) {
4456
+ if (typeof parsed[k] !== "string" || !parsed[k]) {
4457
+ throw new InputError(`--global-parameter needs a non-empty string '${k}'`);
4458
+ }
4459
+ }
4460
+ return parsed;
4461
+ }
3518
4462
  function toolboxCommand() {
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) => {
4463
+ const cmd = new Command14("toolbox").description("Toolboxes: group tools into one publishable box");
4464
+ cmd.command("list").description("List toolboxes \u2192 {data, total, page, page_size, has_next}; the id is `box_id`").option("--name <s>", "filter by toolbox name").addOption(new Option4("--keyword <s>", "deprecated: use --name").hideHelp()).option("--status <s>", "unpublish | published | offline", oneOf("--status", TOOLBOX_STATUSES)).option("--category <s>", "filter by category").option(
4465
+ "--metadata-type <t>",
4466
+ "openapi | function",
4467
+ oneOf("--metadata-type", ["openapi", "function"])
4468
+ ).option("--create-user <s>", "filter by creator").option("--release-user <s>", "filter by publisher").option(
4469
+ "--sort-by <f>",
4470
+ "create_time | update_time | name (service default update_time)",
4471
+ oneOf("--sort-by", ["create_time", "update_time", "name"])
4472
+ ).option("--sort-order <o>", "asc | desc", oneOf("--sort-order", SORT_ORDERS)).option(
4473
+ "--limit <n>",
4474
+ `page size, 1-${MAX_PAGE_SIZE}`,
4475
+ positiveInt("--limit", MAX_PAGE_SIZE),
4476
+ DEFAULT_LIST_LIMIT
4477
+ ).option("--page <n>", "page (1-based)", positiveInt("--page"), 1).option("--all", "return every toolbox, ignoring page size").addOption(new Option4("--offset <n>", "removed: use --page").hideHelp()).action(async (opts, cmd2) => {
4478
+ if (opts.offset !== void 0) {
4479
+ throw new InputError("--offset is not supported: the service pages by --page (1-based)");
4480
+ }
4481
+ if (opts.keyword !== void 0 && opts.name !== void 0) {
4482
+ throw new InputError("--keyword is a deprecated alias of --name; pass only --name");
4483
+ }
3521
4484
  printJson(
3522
4485
  await clientFrom(cmd2).toolboxes.list({
3523
- keyword: opts.keyword,
3524
- limit: opts.limit,
3525
- offset: opts.offset
4486
+ name: opts.name ?? opts.keyword,
4487
+ status: opts.status,
4488
+ category: opts.category,
4489
+ metadataType: opts.metadataType,
4490
+ createUser: opts.createUser,
4491
+ releaseUser: opts.releaseUser,
4492
+ sortBy: opts.sortBy,
4493
+ sortOrder: opts.sortOrder,
4494
+ pageSize: opts.limit,
4495
+ page: opts.page,
4496
+ all: opts.all
3526
4497
  }),
3527
4498
  outputOptions(cmd2)
3528
4499
  );
3529
4500
  });
3530
4501
  cmd.command("create").description(
3531
4502
  "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) => {
4503
+ ).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").option("--category <c>", "box category (service default other_category)").action(async (opts, cmd2) => {
3533
4504
  if (opts.type !== "openapi" && opts.type !== "function") {
3534
4505
  throw new InputError("--type must be openapi or function");
3535
4506
  }
@@ -3541,7 +4512,8 @@ function toolboxCommand() {
3541
4512
  name: opts.name,
3542
4513
  serviceUrl: opts.serviceUrl,
3543
4514
  description: opts.description,
3544
- metadataType: opts.type
4515
+ metadataType: opts.type,
4516
+ category: opts.category
3545
4517
  }),
3546
4518
  outputOptions(cmd2)
3547
4519
  );
@@ -3549,20 +4521,37 @@ function toolboxCommand() {
3549
4521
  cmd.command("publish <box-id>").description("Publish a toolbox").action(async (id, _opts, cmd2) => {
3550
4522
  printJson(await clientFrom(cmd2).toolboxes.publish(id), outputOptions(cmd2));
3551
4523
  });
3552
- cmd.command("unpublish <box-id>").description("Unpublish a toolbox (status=draft)").action(async (id, _opts, cmd2) => {
4524
+ cmd.command("unpublish <box-id>").description("Take a published toolbox down (status=offline)").action(async (id, _opts, cmd2) => {
3553
4525
  printJson(await clientFrom(cmd2).toolboxes.unpublish(id), outputOptions(cmd2));
3554
4526
  });
3555
4527
  cmd.command("delete <box-id>").description("Delete a toolbox").option("-y, --yes", "skip confirmation").action(async (id, _opts, cmd2) => {
3556
4528
  printJson(await clientFrom(cmd2).toolboxes.delete(id), outputOptions(cmd2));
3557
4529
  });
3558
- cmd.command("export <box-id>").description("Export a toolbox config to a local .adp file").requiredOption("-o, --out <file>", "output .adp path").option("--type <t>", "impex type: toolbox | mcp | operator", "toolbox").action(async (boxId, opts, cmd2) => {
4530
+ cmd.command("export <box-id>").description("Export a toolbox config to a local .adp file").requiredOption("-o, --out <file>", "output .adp path").option(
4531
+ "--type <t>",
4532
+ "impex type: toolbox | mcp | operator",
4533
+ oneOf("--type", IMPEX_TYPES),
4534
+ "toolbox"
4535
+ ).action(async (boxId, opts, cmd2) => {
3559
4536
  printJson(
3560
4537
  await clientFrom(cmd2).toolboxes.export(boxId, opts.out, opts.type),
3561
4538
  outputOptions(cmd2)
3562
4539
  );
3563
4540
  });
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) => {
3565
- printJson(await clientFrom(cmd2).toolboxes.import(file, opts.type), outputOptions(cmd2));
4541
+ cmd.command("import <file>").description("Import a toolbox config from a local .adp file").option(
4542
+ "--type <t>",
4543
+ "impex type: toolbox | mcp | operator",
4544
+ oneOf("--type", IMPEX_TYPES),
4545
+ "toolbox"
4546
+ ).option(
4547
+ "--mode <m>",
4548
+ "create (fail if it exists; service default) | upsert (update if it exists)",
4549
+ oneOf("--mode", ["create", "upsert"])
4550
+ ).action(async (file, opts, cmd2) => {
4551
+ printJson(
4552
+ await clientFrom(cmd2).toolboxes.import(file, opts.type, opts.mode),
4553
+ outputOptions(cmd2)
4554
+ );
3566
4555
  });
3567
4556
  groupChildren(cmd, {
3568
4557
  READ: ["list", "export"],
@@ -3575,41 +4564,81 @@ function toolboxCommand() {
3575
4564
  --type function its tools are platform functions, no service URL to give
3576
4565
 
3577
4566
  ORDER OF WORK
3578
- toolbox create --name "<n>" an empty box, in draft
4567
+ toolbox create --name "<n>" an empty box, not yet published
3579
4568
  tool create ./add.py --toolbox a function tool, or --type openapi for a spec
4569
+ tool debug <tool-id> call it while building, before enable/publish
3580
4570
  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
4571
+ toolbox publish <box-id> execute needs a published box
4572
+ tool execute <tool-id> call an enabled tool in a published box
3584
4573
 
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.
4574
+ Both gates apply to \`execute\`: the tool must be enabled and the box published \u2014
4575
+ an unpublished or offline box answers 400 ToolNotAvailable. \`debug\` skips both.
3587
4576
 
3588
4577
  export / import move a whole box between deploys as an .adp file.`
3589
4578
  );
3590
4579
  return group(cmd, "TOOLS & SKILLS");
3591
4580
  }
3592
4581
  function toolCommand() {
3593
- const cmd = new Command13("tool").description(
3594
- "Tools in a box: add one from code or a spec, enable it, call it"
4582
+ return buildToolCommand({ name: "tool", createCommand: "create", includeUpload: true });
4583
+ }
4584
+ function functionToolCommand() {
4585
+ return buildToolCommand({
4586
+ name: "function",
4587
+ metadataType: "function",
4588
+ createCommand: "create",
4589
+ includeUpload: false
4590
+ });
4591
+ }
4592
+ function apiToolCommand() {
4593
+ return buildToolCommand({
4594
+ name: "api",
4595
+ metadataType: "openapi",
4596
+ createCommand: "import",
4597
+ includeUpload: false
4598
+ });
4599
+ }
4600
+ function buildToolCommand(config) {
4601
+ const kind = config.metadataType;
4602
+ const kindName = kind === "function" ? "Function Tools" : kind === "openapi" ? "OpenAPI Tools" : "Tools";
4603
+ const cmd = new Command14(config.name).description(
4604
+ kind ? `${kindName} in a toolbox: add one, enable it, and call it` : "Advanced tools in a box: add one from code or a spec, enable it, call it"
3595
4605
  );
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) => {
4606
+ cmd.command("list").description(kind ? `List ${kindName.toLowerCase()} in a toolbox` : "List tools in a toolbox").requiredOption("--toolbox <box-id>", "toolbox id").option(
4607
+ "--limit <n>",
4608
+ `page size, 1-${MAX_PAGE_SIZE} (backend default 10)`,
4609
+ positiveInt("--limit", MAX_PAGE_SIZE)
4610
+ ).option("--page <n>", "page (1-based; backend default 1)", positiveInt("--page")).option("--all", "return every tool, ignoring page size").option("--name <s>", "filter by tool name").option(
4611
+ "--status <s>",
4612
+ "enabled | disabled",
4613
+ oneOf("--status", ["enabled", "disabled"])
4614
+ ).option(
4615
+ "--sort-by <f>",
4616
+ "create_time | update_time | tool_name (service default create_time)",
4617
+ oneOf("--sort-by", ["create_time", "update_time", "tool_name"])
4618
+ ).option("--sort-order <o>", "asc | desc", oneOf("--sort-order", SORT_ORDERS)).option("--user-id <id>", "filter by creator").action(async (opts, cmd2) => {
3597
4619
  printJson(
3598
4620
  await clientFrom(cmd2).toolboxes.tools(opts.toolbox, {
3599
4621
  page: opts.page,
3600
4622
  pageSize: opts.limit,
3601
- all: opts.all
4623
+ all: opts.all,
4624
+ name: opts.name,
4625
+ status: opts.status,
4626
+ sortBy: opts.sortBy,
4627
+ sortOrder: opts.sortOrder,
4628
+ userId: opts.userId
3602
4629
  }),
3603
4630
  outputOptions(cmd2)
3604
4631
  );
3605
4632
  });
3606
- cmd.command("enable <tool-ids...>").description("Enable one or more tools").requiredOption("--toolbox <box-id>", "toolbox id").action(async (toolIds, opts, cmd2) => {
4633
+ cmd.command("enable <tool-ids...>").description(kind ? `Enable one or more ${kindName.toLowerCase()}` : "Enable one or more tools").requiredOption("--toolbox <box-id>", "toolbox id").action(async (toolIds, opts, cmd2) => {
3607
4634
  printJson(
3608
4635
  await clientFrom(cmd2).toolboxes.setToolStatus(opts.toolbox, toolIds, "enabled"),
3609
4636
  outputOptions(cmd2)
3610
4637
  );
3611
4638
  });
3612
- cmd.command("disable <tool-ids...>").description("Disable one or more tools").requiredOption("--toolbox <box-id>", "toolbox id").action(async (toolIds, opts, cmd2) => {
4639
+ cmd.command("disable <tool-ids...>").description(
4640
+ kind ? `Disable one or more ${kindName.toLowerCase()}` : "Disable one or more tools"
4641
+ ).requiredOption("--toolbox <box-id>", "toolbox id").action(async (toolIds, opts, cmd2) => {
3613
4642
  printJson(
3614
4643
  await clientFrom(cmd2).toolboxes.setToolStatus(opts.toolbox, toolIds, "disabled"),
3615
4644
  outputOptions(cmd2)
@@ -3618,40 +4647,65 @@ function toolCommand() {
3618
4647
  const invokeOpts = (c) => c.requiredOption("--toolbox <box-id>", "toolbox id").option(
3619
4648
  "--body <json>",
3620
4649
  "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);
4650
+ ).option("--header <json>", "headers map JSON").option("--query <json>", "query params JSON").option("--path <json>", "path params JSON").option(
4651
+ "--timeout <s>",
4652
+ "per-call timeout seconds; the client waits this long plus a margin (sandbox max when omitted)",
4653
+ positiveInt("--timeout")
4654
+ );
3622
4655
  const parseJson = (s, label) => {
3623
- if (!s) return void 0;
4656
+ if (s === void 0) return void 0;
4657
+ let parsed;
3624
4658
  try {
3625
- return parseBigIntJSON(s);
4659
+ parsed = parseBigIntJSON(s);
3626
4660
  } catch {
3627
4661
  throw new InputError(`--${label} must be valid JSON`);
3628
4662
  }
4663
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
4664
+ throw new InputError(`--${label} must be a JSON object`);
4665
+ }
4666
+ return parsed;
3629
4667
  };
3630
4668
  const buildEnvelope = (opts) => ({
3631
4669
  body: opts.body ? parseBigIntJSON(opts.body) : void 0,
3632
4670
  header: parseJson(opts.header, "header"),
3633
4671
  query: parseJson(opts.query, "query"),
3634
- path: parseJson(opts.path, "path"),
4672
+ path: pathParams(parseJson(opts.path, "path")),
3635
4673
  timeout: opts.timeout ? Number(opts.timeout) : void 0
3636
4674
  });
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
- );
3645
4675
  invokeOpts(
3646
- cmd.command("debug <tool-id>").description("Invoke a tool that is not enabled yet")
4676
+ cmd.command("execute <tool-id>").description(
4677
+ kind ? `Invoke an enabled ${kindName.slice(0, -1)} in a published toolbox` : "Invoke an enabled tool in a published toolbox"
4678
+ )
3647
4679
  ).action(async (toolId, opts, cmd2) => {
3648
- printJson(
3649
- await clientFrom(cmd2).toolboxes.debug(opts.toolbox, toolId, buildEnvelope(opts)),
3650
- outputOptions(cmd2)
4680
+ const result = await clientFrom(cmd2).toolboxes.execute(
4681
+ opts.toolbox,
4682
+ toolId,
4683
+ buildEnvelope(opts)
3651
4684
  );
4685
+ printJson(result, outputOptions(cmd2));
4686
+ if (toolCallFailed(result)) process.exitCode = 1;
4687
+ });
4688
+ invokeOpts(
4689
+ cmd.command("debug <tool-id>").description(
4690
+ kind ? `Invoke a ${kindName.slice(0, -1)} before it is enabled or its toolbox published` : "Invoke a tool before it is enabled or its toolbox published"
4691
+ )
4692
+ ).action(async (toolId, opts, cmd2) => {
4693
+ const result = await clientFrom(cmd2).toolboxes.debug(opts.toolbox, toolId, buildEnvelope(opts));
4694
+ printJson(result, outputOptions(cmd2));
4695
+ if (toolCallFailed(result)) process.exitCode = 1;
4696
+ });
4697
+ const commonToolFields = (opts) => ({
4698
+ useRule: opts.useRule,
4699
+ globalParameters: globalParameterOption(opts.globalParameter),
4700
+ extendInfo: parseJsonObjectOption(opts.extendInfo, "extend-info")
3652
4701
  });
4702
+ const toolExtraFlags = (c) => c.option("--use-rule <s>", "usage rule carried onto the tool").option(
4703
+ "--global-parameter <json>",
4704
+ "one parameter fixed onto every call: {name,description,in,type,required?,value?}"
4705
+ ).option("--extend-info <json>", "extra metadata as a JSON object");
3653
4706
  const toolFrom = (file, opts) => {
3654
- if (opts.type === "openapi") {
4707
+ const metadataType = kind ?? opts.type;
4708
+ if (metadataType === "openapi") {
3655
4709
  let data;
3656
4710
  try {
3657
4711
  data = yaml.load(readCode(file));
@@ -3660,19 +4714,23 @@ function toolCommand() {
3660
4714
  `${file} is not valid JSON or YAML: ${err instanceof Error ? err.message : err}`
3661
4715
  );
3662
4716
  }
3663
- return { metadataType: "openapi", data, useRule: opts.useRule };
4717
+ return { metadataType: "openapi", data, ...commonToolFields(opts) };
3664
4718
  }
3665
- if (opts.type !== "function") throw new InputError("--type must be function or openapi");
4719
+ if (metadataType !== "function") throw new InputError("--type must be function or openapi");
3666
4720
  return {
3667
4721
  metadataType: "function",
3668
4722
  function: functionDefinitionFrom(file, opts),
3669
- useRule: opts.useRule
4723
+ ...commonToolFields(opts)
3670
4724
  };
3671
4725
  };
3672
4726
  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")
4727
+ toolExtraFlags(
4728
+ cmd.command(`${config.createCommand} <file>`).description(
4729
+ kind === "function" ? "Register a Function Tool from code" : kind === "openapi" ? "Import OpenAPI Tools from a JSON or YAML specification" : "Create a tool from code (or a spec)"
4730
+ ).requiredOption("--toolbox <box-id>", "target toolbox id")
4731
+ ),
4732
+ !kind,
4733
+ kind === "openapi" ? "openapi-import" : "all"
3676
4734
  ).action(async (file, opts, cmd2) => {
3677
4735
  const result = await clientFrom(cmd2).toolboxes.createTool(
3678
4736
  opts.toolbox,
@@ -3681,11 +4739,19 @@ function toolCommand() {
3681
4739
  printJson(result, outputOptions(cmd2));
3682
4740
  if (result?.failure_count) process.exitCode = 1;
3683
4741
  });
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) => {
4742
+ cmd.command("get <tool-id>").description(
4743
+ kind ? `One ${kindName.slice(0, -1)} in full: metadata, parameters, usage rule` : "One tool in full: metadata, parameters, usage rule"
4744
+ ).requiredOption("--toolbox <box-id>", "toolbox id").action(async (toolId, opts, cmd2) => {
3685
4745
  printJson(await clientFrom(cmd2).toolboxes.getTool(opts.toolbox, toolId), outputOptions(cmd2));
3686
4746
  });
3687
4747
  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")
4748
+ toolExtraFlags(
4749
+ cmd.command("update <tool-id> <file>").description(
4750
+ kind ? `Replace a ${kindName.slice(0, -1)} definition; the id survives and an enabled tool stays enabled` : "Replace a tool's definition; the id survives and an enabled tool stays enabled"
4751
+ ).requiredOption("--toolbox <box-id>", "toolbox id")
4752
+ ),
4753
+ !kind,
4754
+ kind === "openapi" ? "openapi-update" : "all"
3689
4755
  ).action(async (toolId, file, opts, cmd2) => {
3690
4756
  if (!opts.name || !opts.description) {
3691
4757
  throw new InputError(
@@ -3701,32 +4767,72 @@ function toolCommand() {
3701
4767
  outputOptions(cmd2)
3702
4768
  );
3703
4769
  });
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) => {
4770
+ cmd.command("delete <tool-ids...>").description(
4771
+ kind ? `Delete ${kindName.toLowerCase()} from a toolbox` : "Delete tools from a toolbox"
4772
+ ).requiredOption("--toolbox <box-id>", "toolbox id").option("-y, --yes", "skip confirmation").action(async (toolIds, opts, cmd2) => {
3705
4773
  printJson(
3706
4774
  await clientFrom(cmd2).toolboxes.deleteTools(opts.toolbox, toolIds),
3707
4775
  outputOptions(cmd2)
3708
4776
  );
3709
4777
  });
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) => {
3711
- printJson(
3712
- await clientFrom(cmd2).toolboxes.upload(opts.toolbox, file, opts.metadataType),
3713
- outputOptions(cmd2)
3714
- );
3715
- });
4778
+ if (config.includeUpload) {
4779
+ 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(
4780
+ "--metadata-type <t>",
4781
+ "openapi | function",
4782
+ oneOf("--metadata-type", TOOL_METADATA_TYPES),
4783
+ "openapi"
4784
+ ).action(async (file, opts, cmd2) => {
4785
+ printJson(
4786
+ await clientFrom(cmd2).toolboxes.upload(opts.toolbox, file, opts.metadataType),
4787
+ outputOptions(cmd2)
4788
+ );
4789
+ });
4790
+ }
3716
4791
  groupChildren(cmd, {
3717
4792
  READ: ["list", "get"],
3718
4793
  RUN: ["execute", "debug"],
3719
- WRITE: ["create", "update", "delete", "enable", "disable", "upload"]
4794
+ WRITE: [
4795
+ config.createCommand,
4796
+ "update",
4797
+ "delete",
4798
+ "enable",
4799
+ "disable",
4800
+ ...config.includeUpload ? ["upload"] : []
4801
+ ]
3720
4802
  });
4803
+ if (kind) {
4804
+ guide(
4805
+ cmd,
4806
+ `WHAT THIS GROUP MANAGES
4807
+ ${kind === "function" ? "Function Tools keep Python handler code in a Toolbox." : "OpenAPI Tools keep operations imported from a JSON or YAML specification."}
4808
+ \`--toolbox\` is always the container id; create it with \`toolbox create\`.
4809
+
4810
+ ORDER OF WORK
4811
+ toolbox create --name "<n>"${kind === "openapi" ? " --service-url <url>" : " --type function"}
4812
+ ${config.name} ${config.createCommand} <file> --toolbox <box-id>
4813
+ ${config.name} debug <tool-id> --toolbox <box-id> try it before enable/publish
4814
+ ${config.name} enable <tool-id> --toolbox <box-id>
4815
+ toolbox publish <box-id>
4816
+ ${config.name} execute <tool-id> --toolbox <box-id>
4817
+
4818
+ \`execute\` needs the tool enabled and the box published; an unpublished or offline
4819
+ box answers 400 ToolNotAvailable. \`debug\` works before either.
4820
+ \`${config.name} list\` does not check the box: --toolbox must name a box whose
4821
+ metadata type is ${kind}.${kind === "function" ? `
4822
+ execute and debug go through the toolbox proxy, which cuts a function at about
4823
+ 30s whatever --timeout says, answering 200 with result: null. Run long jobs
4824
+ with \`sandbox run\`.` : ""}`
4825
+ );
4826
+ }
3721
4827
  return group(cmd, "TOOLS & SKILLS");
3722
4828
  }
3723
4829
 
3724
4830
  // src/commands/trace.ts
3725
- import { readFileSync as readFileSync6, writeFileSync } from "fs";
3726
- import { Command as Command14 } from "commander";
4831
+ import { readFileSync as readFileSync5, writeFileSync } from "fs";
4832
+ import { Command as Command15 } from "commander";
3727
4833
 
3728
4834
  // src/bkn-trace/schema-validate.ts
3729
- import { readFileSync as readFileSync5 } from "fs";
4835
+ import { readFileSync as readFileSync4 } from "fs";
3730
4836
  import { extname } from "path";
3731
4837
  import yaml2 from "js-yaml";
3732
4838
  import { z } from "zod";
@@ -3763,7 +4869,7 @@ var DiagnosisRule = z.object({
3763
4869
  params: z.record(z.string(), z.unknown()).optional()
3764
4870
  });
3765
4871
  function parseFile(file) {
3766
- const text = readFileSync5(file, "utf8");
4872
+ const text = readFileSync4(file, "utf8");
3767
4873
  const ext = extname(file).toLowerCase();
3768
4874
  if (ext === ".yaml" || ext === ".yml") return yaml2.load(text);
3769
4875
  return parseBigIntJSON(text);
@@ -3807,28 +4913,30 @@ function renderPayload(payload) {
3807
4913
  return `[omitted] ${payload.omitted_reason ?? "unknown"}`;
3808
4914
  }
3809
4915
  function renderTechnicalTraceDetail(detail) {
4916
+ const summary = detail?.summary;
3810
4917
  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}`
4918
+ `Trace: ${summary?.trace_id || detail?.graph?.trace_id || "-"}`,
4919
+ `Status: ${summary?.status || detail?.graph?.status || "-"}`,
4920
+ `Request: ${summary?.request_id || "-"}`,
4921
+ `Question: ${summary?.question_preview || "-"}`,
4922
+ `Result: ${summary?.result_preview || "-"}`,
4923
+ `Service: ${summary?.root_service || "-"}`,
4924
+ `Spans: ${detail?.graph?.data?.nodes?.length ?? 0}`
3818
4925
  ];
3819
- if (detail.partial) {
4926
+ if (detail?.partial) {
3820
4927
  lines.push(`Partial: ${(detail.partial_reasons ?? []).join(", ") || "yes"}`);
3821
4928
  }
3822
- for (const operation of detail.operations) {
4929
+ for (const operation of detail?.operations ?? []) {
4930
+ const fact = operation?.fact ?? {};
3823
4931
  lines.push(
3824
4932
  "",
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)}`
4933
+ `${fact.tool_name ?? "-"} \xB7 ${fact.operation_id ?? "-"} \xB7 attempt ${fact.attempt ?? "-"} \xB7 ${operation?.state ?? "-"}`,
4934
+ `Source: ${fact.protocol ?? "-"}/${fact.source_module ?? "-"}`,
4935
+ `Input: ${renderPayload(fact.input)}`
3828
4936
  );
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) {
4937
+ if (fact.output) lines.push(`Output: ${renderPayload(fact.output)}`);
4938
+ if (fact.error) lines.push(`Error: ${renderPayload(fact.error)}`);
4939
+ if (operation?.partial_reasons?.length) {
3832
4940
  lines.push(`Partial: ${operation.partial_reasons.join(", ")}`);
3833
4941
  }
3834
4942
  }
@@ -3836,7 +4944,7 @@ function renderTechnicalTraceDetail(detail) {
3836
4944
  `;
3837
4945
  }
3838
4946
  function traceCommand() {
3839
- const cmd = new Command14("trace").description(
4947
+ const cmd = new Command15("trace").description(
3840
4948
  "Inspect what an agent actually did, and diagnose bad answers"
3841
4949
  );
3842
4950
  cmd.command("graph <trace-id>").description("Fetch normalized trace graph by trace id").action(async (traceId, _opts, cmd2) => {
@@ -3984,7 +5092,8 @@ function traceCommand() {
3984
5092
  outputOptions(cmd2)
3985
5093
  );
3986
5094
  });
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) => {
5095
+ 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("--agent-or-app <name>", "agent or application").option("--trace-id <id>", "exact Trace ID").option("--keyword <text>", "trace, request, operation, or error keyword").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) => {
5096
+ const opts = cmd2.optsWithGlobals();
3988
5097
  printJson(
3989
5098
  await clientFrom(cmd2).trace.search({
3990
5099
  limit: opts.limit,
@@ -3994,7 +5103,9 @@ function traceCommand() {
3994
5103
  status: opts.status,
3995
5104
  service: opts.service,
3996
5105
  tool: opts.tool,
5106
+ agentOrApp: opts.agentOrApp,
3997
5107
  traceId: opts.traceId,
5108
+ keyword: opts.keyword,
3998
5109
  errorKeyword: opts.errorKeyword,
3999
5110
  conversationId: opts.conversationId,
4000
5111
  interactionId: opts.interactionId
@@ -4011,15 +5122,15 @@ function traceCommand() {
4011
5122
  else console.log(renderReportMarkdown(report));
4012
5123
  });
4013
5124
  cmd.command("scan <conversation-ids>").description("Batch-diagnose several conversations (comma-joined) + aggregate findings").option("--llm", "hybrid mode (rubric + synthesizer) per trace via local `claude`").action(async (ids, opts, cmd2) => {
4014
- const list = ids.split(",").map((s) => s.trim()).filter(Boolean);
5125
+ const list2 = ids.split(",").map((s) => s.trim()).filter(Boolean);
4015
5126
  printJson(
4016
- await clientFrom(cmd2).trace.scan(list, { llm: Boolean(opts.llm) }),
5127
+ await clientFrom(cmd2).trace.scan(list2, { llm: Boolean(opts.llm) }),
4017
5128
  outputOptions(cmd2)
4018
5129
  );
4019
5130
  });
4020
5131
  const evalSet = cmd.command("eval-set").description("Build trace eval sets");
4021
5132
  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) => {
4022
- const raw = parseBigIntJSON(readFileSync6(queriesFile, "utf8"));
5133
+ const raw = parseBigIntJSON(readFileSync5(queriesFile, "utf8"));
4023
5134
  const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
4024
5135
  if (opts.out) {
4025
5136
  writeFileSync(opts.out, stringifyBigIntJSON({ cases }, 2));
@@ -4064,14 +5175,117 @@ MANAGED LIFECYCLE
4064
5175
  }
4065
5176
 
4066
5177
  // src/commands/vega.ts
4067
- import { Command as Command15 } from "commander";
4068
- var int10 = (value) => {
5178
+ import { Command as Command16 } from "commander";
5179
+
5180
+ // src/utils/build-progress.ts
5181
+ var LOG_EVERY_MS = 1e4;
5182
+ function formatDuration(ms) {
5183
+ const s = Math.max(0, Math.round(ms / 1e3));
5184
+ if (s < 60) return `${s}s`;
5185
+ const m = Math.floor(s / 60);
5186
+ if (m < 60) return `${m}m${String(s % 60).padStart(2, "0")}s`;
5187
+ return `${Math.floor(m / 60)}h${String(m % 60).padStart(2, "0")}m`;
5188
+ }
5189
+ function describeBuildProgress(task, elapsedMs, etaMs) {
5190
+ const state = task.status ?? "unknown";
5191
+ const parts = [`BuildTask ${task.id}: ${state}`];
5192
+ const total = task.total_count;
5193
+ const synced = task.synced_count;
5194
+ if (typeof synced === "number" && typeof total === "number" && total > 0) {
5195
+ const pct = Math.min(100, Math.floor(synced / total * 100));
5196
+ parts.push(`${synced.toLocaleString("en-US")}/${total.toLocaleString("en-US")} rows (${pct}%)`);
5197
+ } else if (typeof synced === "number") {
5198
+ parts.push(`${synced.toLocaleString("en-US")} rows`);
5199
+ }
5200
+ parts.push(`${formatDuration(elapsedMs)} elapsed`);
5201
+ if (etaMs !== void 0) parts.push(`~${formatDuration(etaMs)} left`);
5202
+ return parts.join(" \xB7 ");
5203
+ }
5204
+ function buildProgressReporter(out = process.stderr, now = Date.now) {
5205
+ const start = now();
5206
+ let baseline;
5207
+ let lastState;
5208
+ let lastLine = "";
5209
+ let lastLoggedAt = Number.NEGATIVE_INFINITY;
5210
+ let open = false;
5211
+ const eta = (task, at) => {
5212
+ const synced = task.synced_count;
5213
+ const total = task.total_count;
5214
+ if (typeof synced !== "number" || typeof total !== "number" || total <= 0) return void 0;
5215
+ if (task.status !== "running") return void 0;
5216
+ if (!baseline) {
5217
+ baseline = { at, synced };
5218
+ return void 0;
5219
+ }
5220
+ const rate = (synced - baseline.synced) / (at - baseline.at);
5221
+ return rate > 0 ? Math.max(0, (total - synced) / rate) : void 0;
5222
+ };
5223
+ return {
5224
+ update(task) {
5225
+ const at = now();
5226
+ const state = task.status ?? "";
5227
+ const line = describeBuildProgress(task, at - start, eta(task, at));
5228
+ if (out.isTTY) {
5229
+ out.write(`\r\x1B[2K${line}`);
5230
+ open = true;
5231
+ } else if (state !== lastState || line !== lastLine && at - lastLoggedAt >= LOG_EVERY_MS) {
5232
+ out.write(`${line}
5233
+ `);
5234
+ lastLoggedAt = at;
5235
+ }
5236
+ lastState = state;
5237
+ lastLine = line;
5238
+ },
5239
+ end() {
5240
+ if (open) out.write("\n");
5241
+ open = false;
5242
+ }
5243
+ };
5244
+ }
5245
+
5246
+ // src/commands/vega.ts
5247
+ var int7 = (value) => {
4069
5248
  const parsed = Number(value);
4070
5249
  if (!Number.isSafeInteger(parsed)) {
4071
5250
  throw new InputError(`expected an integer, received "${value}"`);
4072
5251
  }
4073
5252
  return parsed;
4074
5253
  };
5254
+ var nonNegativeInt3 = (flag) => (value) => {
5255
+ const parsed = Number(value);
5256
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(parsed)) {
5257
+ throw new InputError(`${flag} must be an integer 0 or greater (got '${value}')`);
5258
+ }
5259
+ return parsed;
5260
+ };
5261
+ var seconds = (value) => {
5262
+ const parsed = Number(value);
5263
+ if (!Number.isSafeInteger(parsed) || parsed < 0) {
5264
+ throw new InputError(
5265
+ `--timeout must be a whole number of seconds (0 = no limit), received "${value}"`
5266
+ );
5267
+ }
5268
+ return parsed;
5269
+ };
5270
+ async function waitAndReport(cmd, wait) {
5271
+ const progress = buildProgressReporter();
5272
+ let task;
5273
+ try {
5274
+ task = await wait((t) => progress.update(t));
5275
+ } catch (err) {
5276
+ progress.end();
5277
+ if (err instanceof WaitTimeoutError) printJson(err.last, outputOptions(cmd));
5278
+ throw err;
5279
+ }
5280
+ progress.end();
5281
+ printJson(task, outputOptions(cmd));
5282
+ if (isBuildTaskUnsuccessful(task)) {
5283
+ const reason = task.error_msg;
5284
+ throw new Error(
5285
+ `BuildTask ${task.id} ended ${buildTaskState(task)}${typeof reason === "string" && reason ? `: ${reason}` : "."}`
5286
+ );
5287
+ }
5288
+ }
4075
5289
  var expectedUpdateTime = (value) => {
4076
5290
  const parsed = Number(value);
4077
5291
  if (!Number.isSafeInteger(parsed) || parsed <= 0) {
@@ -4115,6 +5329,12 @@ var parseJsonArray = (value, flag) => {
4115
5329
  }
4116
5330
  return parsed;
4117
5331
  };
5332
+ var parseResourceProperties = (value) => parseJsonArray(value, "--schema-definition").map((property) => {
5333
+ if (typeof property.name !== "string" || property.name.length === 0) {
5334
+ throw new InputError("--schema-definition properties must have a non-empty string name");
5335
+ }
5336
+ return property;
5337
+ });
4118
5338
  var healthCheckSchedule = (mode, cronExpr) => {
4119
5339
  if (!mode) {
4120
5340
  if (cronExpr) throw new InputError("a health-check cron expression requires enabled mode");
@@ -4255,7 +5475,7 @@ var semanticTaskSort = (raw) => {
4255
5475
  }
4256
5476
  return parsed.data;
4257
5477
  };
4258
- var sortDirection = (raw) => {
5478
+ var sortDirection2 = (raw) => {
4259
5479
  if (raw === void 0) return void 0;
4260
5480
  const parsed = SortDirection.safeParse(raw);
4261
5481
  if (!parsed.success) {
@@ -4265,12 +5485,48 @@ var sortDirection = (raw) => {
4265
5485
  }
4266
5486
  return parsed.data;
4267
5487
  };
5488
+ var connectorMode = (raw) => {
5489
+ if (raw === void 0) return void 0;
5490
+ const parsed = ConnectorMode.safeParse(raw);
5491
+ if (!parsed.success) {
5492
+ throw new InputError(
5493
+ `invalid connector mode "${raw}"; expected one of ${ConnectorMode.options.join(", ")}`
5494
+ );
5495
+ }
5496
+ return parsed.data;
5497
+ };
5498
+ var connectorCategory = (raw) => {
5499
+ if (raw === void 0) return void 0;
5500
+ const parsed = ConnectorCategory.safeParse(raw);
5501
+ if (!parsed.success) {
5502
+ throw new InputError(
5503
+ `invalid connector category "${raw}"; expected one of ${ConnectorCategory.options.join(", ")}`
5504
+ );
5505
+ }
5506
+ return parsed.data;
5507
+ };
4268
5508
  function vegaCommand() {
4269
- const vega = new Command15("vega").description(
5509
+ const vega = new Command16("vega").description(
4270
5510
  "Data sources: catalogs, connectors, SQL, index builds"
4271
5511
  );
4272
5512
  const catalog = vega.command("catalog").description("Catalog entries");
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) => {
5513
+ catalog.command("list").description("List catalog entries").option("--limit <n>", "page size", int7, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int7, 0).option("--name <s>", "filter by name").option("--tag <s>", "filter by tag").option(
5514
+ "--type <type>",
5515
+ "filter by catalog type: physical | logical",
5516
+ oneOf("--type", ["physical", "logical"])
5517
+ ).option("--connector-type <type>", "filter by connector type").option("--enabled <bool>", "filter by enabled state: true | false", bool).option(
5518
+ "--health-check-status <s>",
5519
+ `filter by health status: ${CatalogHealthCheckStatus.options.join(" | ")}`,
5520
+ oneOf("--health-check-status", CatalogHealthCheckStatus.options)
5521
+ ).option(
5522
+ "--sort <field>",
5523
+ "sort field: name | create_time | update_time",
5524
+ oneOf("--sort", ["name", "create_time", "update_time"])
5525
+ ).option(
5526
+ "--direction <dir>",
5527
+ "sort direction: asc | desc",
5528
+ oneOf("--direction", SortDirection.options)
5529
+ ).action(async (_opts, cmd) => {
4274
5530
  const o = cmd.optsWithGlobals();
4275
5531
  const data = await clientFrom(cmd).vega.catalogs({
4276
5532
  limit: o.limit,
@@ -4279,7 +5535,7 @@ function vegaCommand() {
4279
5535
  tag: o.tag,
4280
5536
  type: o.type,
4281
5537
  connectorType: o.connectorType,
4282
- enabled: o.enabled === void 0 ? void 0 : o.enabled === "true",
5538
+ enabled: o.enabled,
4283
5539
  healthCheckStatus: o.healthCheckStatus,
4284
5540
  sort: o.sort,
4285
5541
  direction: o.direction
@@ -4289,7 +5545,21 @@ function vegaCommand() {
4289
5545
  catalog.command("get <id>").description("Get a catalog by id").action(async (id, _opts, cmd) => {
4290
5546
  printJson(await clientFrom(cmd).vega.getCatalog(id), outputOptions(cmd));
4291
5547
  });
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) => {
5548
+ catalog.command("stats").description("Count visible catalogs by catalog and connector type").option("--name <s>", "filter by catalog name before aggregation").action(async (opts, cmd) => {
5549
+ printJson(
5550
+ await clientFrom(cmd).vega.catalogConnectorTypeStats({ name: opts.name }),
5551
+ outputOptions(cmd)
5552
+ );
5553
+ });
5554
+ catalog.command("resources <id>").description("List resources under a catalog").option(
5555
+ "--category <c>",
5556
+ `filter by category: ${ResourceCategory.options.join(" | ")}`,
5557
+ oneOf("--category", ResourceCategory.options)
5558
+ ).option(
5559
+ "--limit <n>",
5560
+ `page size (default ${DEFAULT_LIST_LIMIT}); page with --offset against total_count`,
5561
+ int7
5562
+ ).option("--offset <n>", "page offset", nonNegativeInt3("--offset"), 0).action(async (id, opts, cmd) => {
4293
5563
  printJson(
4294
5564
  await clientFrom(cmd).vega.catalogResources(id, opts.category, opts.limit, opts.offset),
4295
5565
  outputOptions(cmd)
@@ -4298,8 +5568,16 @@ function vegaCommand() {
4298
5568
  catalog.command("health <id>").description("Health-status for a catalog").action(async (id, _opts, cmd) => {
4299
5569
  printJson(await clientFrom(cmd).vega.catalogHealth(id), outputOptions(cmd));
4300
5570
  });
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) => {
4302
- const connectorConfig = parseJsonObject(opts.connectorConfig, "--connector-config");
5571
+ catalog.command("create").description("Create a catalog (data source)").requiredOption("--name <s>", "catalog name").option("--connector-type <s>", "connector type (e.g. mysql); required unless --built-in").option("--connector-config <json>", "connector config JSON; not allowed with --built-in").option("--id <id>", "explicit catalog id").option("--tags <t1,t2>", "comma-separated tags").option("--description <s>", "description").option("--enabled", "create enabled (default: disabled)").option("--built-in", "create a built-in (logical) catalog, with no connector").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) => {
5572
+ if (opts.builtIn && (opts.connectorType || opts.connectorConfig)) {
5573
+ throw new InputError(
5574
+ "--built-in creates a logical catalog: omit --connector-type and --connector-config"
5575
+ );
5576
+ }
5577
+ if (!opts.builtIn && !opts.connectorType) {
5578
+ throw new InputError("--connector-type is required unless --built-in");
5579
+ }
5580
+ const connectorConfig = opts.connectorConfig === void 0 ? void 0 : parseJsonObject(opts.connectorConfig, "--connector-config");
4303
5581
  printJson(
4304
5582
  await clientFrom(cmd).vega.createCatalog(
4305
5583
  {
@@ -4309,8 +5587,8 @@ function vegaCommand() {
4309
5587
  connectorConfig,
4310
5588
  tags: opts.tags ? String(opts.tags).split(",").map((t) => t.trim()).filter(Boolean) : void 0,
4311
5589
  description: opts.description,
4312
- enabled: opts.enabled ? true : void 0,
4313
- internal: opts.internal ? true : void 0,
5590
+ enabled: Boolean(opts.enabled),
5591
+ builtIn: opts.builtIn ? true : void 0,
4314
5592
  healthCheckSchedule: healthCheckSchedule(opts.healthCheckMode, opts.healthCheckCron)
4315
5593
  },
4316
5594
  { allowUnhealthy: opts.allowUnhealthy ? true : void 0 }
@@ -4318,11 +5596,22 @@ function vegaCommand() {
4318
5596
  outputOptions(cmd)
4319
5597
  );
4320
5598
  });
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(
5599
+ catalog.command("update <id>").description(
5600
+ "Update a catalog; fields you leave out keep their current values (read, then full PUT)"
5601
+ ).option("--name <s>", "catalog name").option("--connector-type <s>", "connector type").option("--enabled <bool>", "current enabled state (must match; use enable/disable)", bool).option("--connector-config <json>", "connector config JSON (sent only when given)").option("--tags <t1,t2>", "comma-separated tags").option("--description <s>", "description").option(
4322
5602
  "--expected-update-time <ms>",
4323
- "optimistic-lock update time",
5603
+ "optimistic-lock update time (default: the update_time read before the PUT)",
4324
5604
  expectedUpdateTime
4325
5605
  ).option("--allow-unhealthy", "save the update when its connection test fails").action(async (id, opts, cmd) => {
5606
+ const hasPatch = [
5607
+ opts.name,
5608
+ opts.connectorType,
5609
+ opts.enabled,
5610
+ opts.connectorConfig,
5611
+ opts.tags,
5612
+ opts.description
5613
+ ].some((value) => value !== void 0);
5614
+ if (!hasPatch) throw new InputError("provide at least one catalog field to update");
4326
5615
  const connectorConfig = opts.connectorConfig ? parseJsonObject(opts.connectorConfig, "--connector-config") : void 0;
4327
5616
  printJson(
4328
5617
  await clientFrom(cmd).vega.updateCatalog(
@@ -4390,7 +5679,7 @@ function vegaCommand() {
4390
5679
  );
4391
5680
  });
4392
5681
  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) => {
5682
+ 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", int7, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int7, 0).option("--sort <field>", "name | create_time | update_time | next_run").option("--direction <dir>", "asc | desc").action(async (opts, cmd) => {
4394
5683
  printJson(
4395
5684
  await clientFrom(cmd).vega.discoverSchedules({
4396
5685
  name: opts.name,
@@ -4399,7 +5688,7 @@ function vegaCommand() {
4399
5688
  limit: opts.limit,
4400
5689
  offset: opts.offset,
4401
5690
  sort: discoverScheduleSort(opts.sort),
4402
- direction: sortDirection(opts.direction)
5691
+ direction: sortDirection2(opts.direction)
4403
5692
  }),
4404
5693
  outputOptions(cmd)
4405
5694
  );
@@ -4407,7 +5696,7 @@ function vegaCommand() {
4407
5696
  discoverSchedule.command("get <id>").description("Get a discovery schedule").action(async (id, _opts, cmd) => {
4408
5697
  printJson(await clientFrom(cmd).vega.getDiscoverSchedule(id), outputOptions(cmd));
4409
5698
  });
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) => {
5699
+ 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 (0 = no lower bound)", nonNegativeInt3("--start-time")).option("--end-time <ms>", "end time (0 = no upper bound)", nonNegativeInt3("--end-time")).option("--enabled", "create enabled").option("--strategy <strategy>", `strategy: ${DiscoverStrategy.options.join(" | ")}`).action(async (opts, cmd) => {
4411
5700
  printJson(
4412
5701
  await clientFrom(cmd).vega.createDiscoverSchedule({
4413
5702
  name: opts.name,
@@ -4421,7 +5710,15 @@ function vegaCommand() {
4421
5710
  outputOptions(cmd)
4422
5711
  );
4423
5712
  });
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(
5713
+ 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(
5714
+ "--start-time <ms>",
5715
+ "start time (0 = no lower bound)",
5716
+ nonNegativeInt3("--start-time")
5717
+ ).requiredOption(
5718
+ "--end-time <ms>",
5719
+ "end time (0 = no upper bound)",
5720
+ nonNegativeInt3("--end-time")
5721
+ ).requiredOption("--strategy <strategy>", `strategy: ${DiscoverStrategy.options.join(" | ")}`).requiredOption(
4425
5722
  "--expected-update-time <ms>",
4426
5723
  "optimistic-lock update time",
4427
5724
  expectedUpdateTime
@@ -4448,7 +5745,7 @@ function vegaCommand() {
4448
5745
  });
4449
5746
  }
4450
5747
  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) => {
5748
+ 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", int7, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int7, 0).option("--sort <field>", "create_time | start_time | finish_time | last_progress_time").option("--direction <dir>", "asc | desc").action(async (opts, cmd) => {
4452
5749
  printJson(
4453
5750
  await clientFrom(cmd).vega.discoverTasks({
4454
5751
  catalogId: opts.catalogId,
@@ -4460,7 +5757,7 @@ function vegaCommand() {
4460
5757
  limit: opts.limit,
4461
5758
  offset: opts.offset,
4462
5759
  sort: discoverTaskSort(opts.sort),
4463
- direction: sortDirection(opts.direction)
5760
+ direction: sortDirection2(opts.direction)
4464
5761
  }),
4465
5762
  outputOptions(cmd)
4466
5763
  );
@@ -4480,7 +5777,7 @@ function vegaCommand() {
4480
5777
  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
5778
  "--apply-mode <mode>",
4482
5779
  `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) => {
5780
+ ).option("--applied <bool>", "filter by applied state", bool).option("--limit <n>", "page size", int7, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int7, 0).option("--sort <field>", "create_time | start_time | finish_time").option("--direction <dir>", "asc | desc").action(async (opts, cmd) => {
4484
5781
  printJson(
4485
5782
  await clientFrom(cmd).vega.semanticUnderstandingTasks({
4486
5783
  scope: semanticScope(opts.scope),
@@ -4492,12 +5789,12 @@ function vegaCommand() {
4492
5789
  limit: opts.limit,
4493
5790
  offset: opts.offset,
4494
5791
  sort: semanticTaskSort(opts.sort),
4495
- direction: sortDirection(opts.direction)
5792
+ direction: sortDirection2(opts.direction)
4496
5793
  }),
4497
5794
  outputOptions(cmd)
4498
5795
  );
4499
5796
  });
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) => {
5797
+ 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", int7).action(async (opts, cmd) => {
4501
5798
  const scope = semanticScope(opts.scope);
4502
5799
  if (!scope) throw new InputError("--scope is required");
4503
5800
  const applyMode = semanticApplyMode(opts.applyMode);
@@ -4547,18 +5844,44 @@ function vegaCommand() {
4547
5844
  );
4548
5845
  });
4549
5846
  const connector = vega.command("connector-type").description("Connector types");
4550
- connector.command("list").description("List connector types").action(async (_opts, cmd) => {
4551
- printJson(await clientFrom(cmd).vega.connectorTypes(), outputOptions(cmd));
5847
+ connector.command("list").description("List connector types").option("--name <s>", "filter by name").option("--tag <s>", "filter by tag").option("--mode <mode>", `runtime mode: ${ConnectorMode.options.join(" | ")}`).option("--category <category>", `category: ${ConnectorCategory.options.join(" | ")}`).option("--enabled <bool>", "filter by enabled state", bool).option("--available <bool>", "filter by runtime availability", bool).option("--limit <n>", "page size", int7, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int7, 0).option("--direction <dir>", `sort direction: ${SortDirection.options.join(" | ")}`).action(async (opts, cmd) => {
5848
+ printJson(
5849
+ await clientFrom(cmd).vega.connectorTypes({
5850
+ name: opts.name,
5851
+ tag: opts.tag,
5852
+ mode: connectorMode(opts.mode),
5853
+ category: connectorCategory(opts.category),
5854
+ enabled: opts.enabled,
5855
+ available: opts.available,
5856
+ limit: opts.limit,
5857
+ offset: opts.offset,
5858
+ direction: sortDirection2(opts.direction)
5859
+ }),
5860
+ outputOptions(cmd)
5861
+ );
4552
5862
  });
4553
5863
  connector.command("get <type>").description("Get a connector type").action(async (type, _opts, cmd) => {
4554
5864
  printJson(await clientFrom(cmd).vega.connectorType(type), outputOptions(cmd));
4555
5865
  });
5866
+ vega.command("index-capabilities").description(
5867
+ "List local-index analyzers and probe time (undocumented deploy extension; 404 where absent)"
5868
+ ).action(async (_opts, cmd) => {
5869
+ printJson(await clientFrom(cmd).vega.indexCapabilities(), outputOptions(cmd));
5870
+ });
4556
5871
  vega.command("sql").description(
4557
5872
  "Read-only SQL straight against a data source \u2014 no knowledge network, no Trace record"
4558
5873
  ).option(
4559
5874
  "--query <sql>",
4560
5875
  "SQL string; reference a resource with a {{<resource-id>}} placeholder"
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(
5876
+ ).option(
5877
+ "--input-dialect <dialect>",
5878
+ "SQL input dialect: postgres | mysql | trino | duckdb | tsql",
5879
+ oneOf("--input-dialect", ["postgres", "mysql", "trino", "duckdb", "tsql"])
5880
+ ).option(
5881
+ "--paging-mode <mode>",
5882
+ "paging mode: single | cursor",
5883
+ oneOf("--paging-mode", ["single", "cursor"])
5884
+ ).option("--limit <n>", "page size (cursor mode requires it)", int7).option("--offset <n>", "first-page offset", nonNegativeInt3("--offset")).option("--keep-alive-sec <s>", "cursor keep-alive in seconds (60\u20133600)", keepAliveSec).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)", int7).option(
4562
5885
  "-d, --data <json>",
4563
5886
  "full request body as JSON (advanced; wins over individual query flags)"
4564
5887
  ).action(async (opts, cmd) => {
@@ -4602,23 +5925,78 @@ function vegaCommand() {
4602
5925
  printJson(await clientFrom(cmd).vega.sql(body), outputOptions(cmd));
4603
5926
  });
4604
5927
  const resource = vega.command("resource").description("Vega-backend resources");
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) => {
5928
+ addResourceListOptions(resource.command("list").description("List resources"), "--type").action(
5929
+ async (opts, cmd) => {
5930
+ printJson(
5931
+ await clientFrom(cmd).resource.list(resourceListOptionsFrom(opts)),
5932
+ outputOptions(cmd)
5933
+ );
5934
+ }
5935
+ );
5936
+ resource.command("get <id>").description("Get a resource").action(async (id, _opts, cmd) => {
5937
+ printJson(await clientFrom(cmd).resource.get(id), outputOptions(cmd));
5938
+ });
5939
+ resource.command("create").description("Create a dataset or logic-view resource").requiredOption("--catalog-id <id>", "catalog id").requiredOption("--name <s>", "resource name").requiredOption("--category <category>", "resource category: dataset | logicview").option("--id <id>", "explicit resource id").option("--tags <t1,t2>", "comma-separated tags").option("--description <s>", "description").option("--schema-definition <json>", "schema_definition JSON array").option("--index-config <json>", "index_config JSON object").option("--logic-definition <json>", "logic_definition JSON array").action(async (opts, cmd) => {
5940
+ if (opts.category !== "dataset" && opts.category !== "logicview") {
5941
+ throw new InputError("--category must be dataset or logicview");
5942
+ }
4606
5943
  printJson(
4607
- await clientFrom(cmd).resource.list({
5944
+ await clientFrom(cmd).resource.create({
5945
+ id: opts.id,
4608
5946
  catalogId: opts.catalogId,
4609
- category: opts.type ?? opts.category,
4610
- status: opts.status,
4611
- schema: opts.schema,
4612
- limit: opts.limit,
4613
- offset: opts.offset,
4614
- sort: opts.sort,
4615
- direction: opts.direction
5947
+ name: opts.name,
5948
+ category: opts.category,
5949
+ ...opts.tags !== void 0 ? { tags: csv(opts.tags) ?? [] } : {},
5950
+ description: opts.description,
5951
+ ...opts.schemaDefinition !== void 0 ? {
5952
+ schemaDefinition: parseResourceProperties(opts.schemaDefinition)
5953
+ } : {},
5954
+ ...opts.indexConfig !== void 0 ? {
5955
+ indexConfig: parseJsonObject(
5956
+ opts.indexConfig,
5957
+ "--index-config"
5958
+ )
5959
+ } : {},
5960
+ ...opts.logicDefinition !== void 0 ? { logicDefinition: parseJsonArray(opts.logicDefinition, "--logic-definition") } : {}
4616
5961
  }),
4617
5962
  outputOptions(cmd)
4618
5963
  );
4619
5964
  });
4620
- resource.command("get <id>").description("Get a resource").action(async (id, _opts, cmd) => {
4621
- printJson(await clientFrom(cmd).resource.get(id), outputOptions(cmd));
5965
+ resource.command("update <id>").description("Update resource metadata, schema, features, or index configuration").option("--name <s>", "resource name").option("--tags <t1,t2>", "comma-separated tags").option("--description <s>", "description").option("--schema-definition <json>", "replacement schema_definition JSON array").option("--index-config <json>", "replacement index_config JSON object").option("--logic-definition <json>", "replacement logic_definition JSON array").option("--expected-update-time <ms>", "optimistic-lock update time", expectedUpdateTime).action(async (id, opts, cmd) => {
5966
+ const hasPatch = [
5967
+ opts.name,
5968
+ opts.tags,
5969
+ opts.description,
5970
+ opts.schemaDefinition,
5971
+ opts.indexConfig,
5972
+ opts.logicDefinition
5973
+ ].some((value) => value !== void 0);
5974
+ if (!hasPatch) throw new InputError("provide at least one resource field to update");
5975
+ printJson(
5976
+ await clientFrom(cmd).resource.update(id, {
5977
+ name: opts.name,
5978
+ ...opts.tags !== void 0 ? { tags: csv(opts.tags) ?? [] } : {},
5979
+ description: opts.description,
5980
+ ...opts.schemaDefinition !== void 0 ? {
5981
+ schemaDefinition: parseResourceProperties(opts.schemaDefinition)
5982
+ } : {},
5983
+ ...opts.indexConfig !== void 0 ? {
5984
+ indexConfig: parseJsonObject(
5985
+ opts.indexConfig,
5986
+ "--index-config"
5987
+ )
5988
+ } : {},
5989
+ ...opts.logicDefinition !== void 0 ? { logicDefinition: parseJsonArray(opts.logicDefinition, "--logic-definition") } : {},
5990
+ expectedUpdateTime: opts.expectedUpdateTime
5991
+ }),
5992
+ outputOptions(cmd)
5993
+ );
5994
+ });
5995
+ resource.command("delete <ids...>").description("Delete one or more resources").option("--ignore-missing", "ignore missing resource ids").action(async (ids, opts, cmd) => {
5996
+ printJson(
5997
+ await clientFrom(cmd).resource.delete(ids, { ignoreMissing: opts.ignoreMissing }),
5998
+ outputOptions(cmd)
5999
+ );
4622
6000
  });
4623
6001
  resource.command("discover <id>").description("Trigger metadata discovery for a resource").action(async (id, _opts, cmd) => {
4624
6002
  printJson(await clientFrom(cmd).vega.discoverResource(id), outputOptions(cmd));
@@ -4630,93 +6008,119 @@ function vegaCommand() {
4630
6008
  printJson(result, outputOptions(cmd));
4631
6009
  });
4632
6010
  }
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) => {
6011
+ addResourceQueryOptions(
6012
+ resource.command("query <id>").description(`Fetch data rows from a resource (default limit ${DEFAULT_QUERY_LIMIT})`)
6013
+ ).action(async (id, opts, cmd) => {
4634
6014
  printJson(
4635
- await clientFrom(cmd).resource.query(id, { limit: opts.limit, offset: opts.offset }),
6015
+ await clientFrom(cmd).resource.query(id, resourceQueryOptionsFrom(opts, DEFAULT_QUERY_LIMIT)),
4636
6016
  outputOptions(cmd)
4637
6017
  );
4638
6018
  });
4639
- resource.command("document-get <resource-id> <document-id>").description("Get one dataset document").action(async (resourceId, documentId, _opts, cmd) => {
6019
+ resource.command("document-get <resource-id> <document-ids...>").description("Get dataset documents by id").option("--ignore-missing", "Skip missing documents").action(async (resourceId, documentIds, opts, cmd) => {
4640
6020
  printJson(
4641
- await clientFrom(cmd).resource.getDocument(resourceId, documentId),
6021
+ await clientFrom(cmd).resource.getDocuments(resourceId, documentIds, {
6022
+ ignoreMissing: opts.ignoreMissing
6023
+ }),
4642
6024
  outputOptions(cmd)
4643
6025
  );
4644
6026
  });
4645
- resource.command("document-create <resource-id>").description("Create dataset documents").requiredOption(
6027
+ resource.command("document-create <resource-id>").description("Create one dataset document").requiredOption(
4646
6028
  "--data <json>",
4647
- "JSON array of documents \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (vega-backend)"
6029
+ "Document JSON object \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (vega-backend)"
4648
6030
  ).action(async (resourceId, opts, cmd) => {
4649
6031
  printJson(
4650
- await clientFrom(cmd).resource.createDocuments(
6032
+ await clientFrom(cmd).resource.createDocument(
4651
6033
  resourceId,
4652
- parseJsonArray(opts.data, "--data")
6034
+ parseJsonObject(opts.data, "--data")
4653
6035
  ),
4654
6036
  outputOptions(cmd)
4655
6037
  );
4656
6038
  });
4657
- resource.command("document-upsert <resource-id>").description("Upsert dataset documents; every document must have an id").requiredOption(
6039
+ resource.command("document-upsert <resource-id> <docid>").description("Upsert one dataset document").requiredOption(
4658
6040
  "--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
- }
6041
+ "Document JSON object \u2014 docs: https://openbkn-ai.github.io/bkn-foundry/ (vega-backend)"
6042
+ ).action(async (resourceId, docid, opts, cmd) => {
4665
6043
  printJson(
4666
- await clientFrom(cmd).resource.upsertDocuments(resourceId, documents),
6044
+ await clientFrom(cmd).resource.upsertDocument(
6045
+ resourceId,
6046
+ docid,
6047
+ parseJsonObject(opts.data, "--data")
6048
+ ),
4667
6049
  outputOptions(cmd)
4668
6050
  );
4669
6051
  });
4670
- resource.command("document-delete <resource-id> <document-ids...>").description("Delete dataset documents by id").action(async (resourceId, documentIds, _opts, cmd) => {
6052
+ resource.command("document-delete <resource-id> <document-ids...>").description("Delete dataset documents by id").option("--ignore-missing", "Skip missing documents").action(async (resourceId, documentIds, opts, cmd) => {
4671
6053
  printJson(
4672
- await clientFrom(cmd).resource.deleteDocuments(resourceId, documentIds),
6054
+ await clientFrom(cmd).resource.deleteDocuments(resourceId, documentIds, {
6055
+ ignoreMissing: opts.ignoreMissing
6056
+ }),
4673
6057
  outputOptions(cmd)
4674
6058
  );
4675
6059
  });
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");
6060
+ resource.command("document-delete-filter <resource-id>").description("Delete dataset documents by an equality selector or Vega filter condition").option("--filter <json>", "Vega filter_condition JSON object").option("--selector <json>", "JSON equality selector").action(async (resourceId, opts, cmd) => {
6061
+ if (Boolean(opts.filter) === Boolean(opts.selector)) {
6062
+ throw new InputError("exactly one of --filter or --selector is required");
6063
+ }
6064
+ const filter = parseJsonObject(
6065
+ opts.filter ?? opts.selector,
6066
+ opts.filter ? "--filter" : "--selector"
6067
+ );
6068
+ if (!Object.keys(filter).length) {
6069
+ throw new InputError(`${opts.filter ? "--filter" : "--selector"} must not be empty`);
6070
+ }
4679
6071
  printJson(
4680
- await clientFrom(cmd).resource.deleteDocumentsByFilter(resourceId, filter),
6072
+ await (opts.selector ? clientFrom(cmd).resource.deleteDocumentsBySelector(resourceId, filter) : clientFrom(cmd).resource.deleteDocumentsByFilter(resourceId, filter)),
4681
6073
  outputOptions(cmd)
4682
6074
  );
4683
6075
  });
4684
- const dataset = vega.command("dataset").description("Dataset index build tasks");
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(
4686
- "--build-key-fields <list>",
4687
- "comma-separated key fields (batch: time; streaming: row id)"
6076
+ resource.command("build <resource-id>").description("Create a batch BuildTask from the resource's saved index configuration").option("--execute-type <type>", "batch execution type: incremental | full").option(
6077
+ "--wait",
6078
+ "wait for the build to end, with progress on stderr; exits non-zero unless it completes"
4688
6079
  ).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) => {
6080
+ "--timeout <s>",
6081
+ "with --wait: give up after this many seconds (0 = no limit)",
6082
+ seconds,
6083
+ 300
6084
+ ).action(async (resourceId, _opts, cmd) => {
4692
6085
  const o = cmd.optsWithGlobals();
4693
- const embeddingFields = csv(o.embeddingFields);
4694
- const buildKeyFields = csv(o.buildKeyFields);
4695
- const fulltextFields = csv(o.fulltextFields);
4696
- if (embeddingFields || buildKeyFields || o.embeddingModel || fulltextFields || o.fulltextAnalyzer) {
4697
- await clientFrom(cmd).resource.configureIndex(resourceId, {
4698
- embeddingFields,
4699
- buildKeyFields,
4700
- embeddingModel: o.embeddingModel,
4701
- fulltextFields,
4702
- fulltextAnalyzer: o.fulltextAnalyzer
4703
- });
6086
+ const req = {
6087
+ resource_id: resourceId,
6088
+ mode: "batch",
6089
+ execute_type: buildTaskExecuteType(o.executeType)
6090
+ };
6091
+ if (!o.wait) {
6092
+ printJson(await clientFrom(cmd).vega.build(req), outputOptions(cmd));
6093
+ return;
4704
6094
  }
4705
- const task = await clientFrom(cmd).vega.build(
4706
- {
4707
- resource_id: resourceId,
4708
- mode: o.mode,
4709
- execute_type: o.executeType
4710
- },
4711
- { wait: Boolean(o.wait), timeoutMs: o.timeout * 1e3 }
6095
+ await waitAndReport(
6096
+ cmd,
6097
+ (onProgress) => clientFrom(cmd).vega.build(req, { wait: true, timeoutMs: o.timeout * 1e3, onProgress })
4712
6098
  );
4713
- printJson(task, outputOptions(cmd));
4714
6099
  });
4715
- dataset.command("build-status <task-id>").description("Show a BuildTask's state and progress").action(async (taskId, _opts, cmd) => {
4716
- const task = await clientFrom(cmd).vega.buildStatus(taskId);
4717
- printJson(task, outputOptions(cmd));
6100
+ const buildTask = vega.command("build-task").description("Resource index BuildTasks");
6101
+ buildTask.command("get <task-id>").description("Show a BuildTask's state and progress").option(
6102
+ "--wait",
6103
+ "wait for the task to end, with progress on stderr; exits non-zero unless it completes"
6104
+ ).option(
6105
+ "--timeout <s>",
6106
+ "with --wait: give up after this many seconds (0 = no limit)",
6107
+ seconds,
6108
+ 300
6109
+ ).action(async (taskId, opts, cmd) => {
6110
+ if (!opts.wait) {
6111
+ printJson(await clientFrom(cmd).vega.buildStatus(taskId), outputOptions(cmd));
6112
+ return;
6113
+ }
6114
+ await waitAndReport(
6115
+ cmd,
6116
+ (onProgress) => clientFrom(cmd).vega.waitForBuild(taskId, { timeoutMs: opts.timeout * 1e3, onProgress })
6117
+ );
4718
6118
  });
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(
6119
+ buildTask.command("list").description("List BuildTasks").option("--limit <n>", "page size", int7, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int7, 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(
6120
+ "--mode <mode>",
6121
+ `filter by mode: ${BuildMode.options.join(" | ")}`,
6122
+ oneOf("--mode", BuildMode.options)
6123
+ ).option(
4720
6124
  "--execute-type <type>",
4721
6125
  `filter by execution type: ${BuildTaskExecuteType.options.join(" | ")}`
4722
6126
  ).option("--sort <field>", `sort field: ${BuildTaskSort.options.join(" | ")}`).option("--direction <dir>", `sort direction: ${SortDirection.options.join(" | ")}`).action(async (opts, cmd) => {
@@ -4730,21 +6134,21 @@ function vegaCommand() {
4730
6134
  mode: opts.mode,
4731
6135
  executeType: buildTaskExecuteType(opts.executeType),
4732
6136
  sort: buildTaskSort(opts.sort),
4733
- direction: sortDirection(opts.direction)
6137
+ direction: sortDirection2(opts.direction)
4734
6138
  }),
4735
6139
  outputOptions(cmd)
4736
6140
  );
4737
6141
  });
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) => {
6142
+ buildTask.command("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) => {
4739
6143
  printJson(
4740
6144
  await clientFrom(cmd).vega.startBuildTask(taskId, { reset: opts.reset }),
4741
6145
  outputOptions(cmd)
4742
6146
  );
4743
6147
  });
4744
- dataset.command("build-stop <task-id>").description("Stop a BuildTask").action(async (taskId, _opts, cmd) => {
6148
+ buildTask.command("stop <task-id>").description("Stop a BuildTask").action(async (taskId, _opts, cmd) => {
4745
6149
  printJson(await clientFrom(cmd).vega.stopBuildTask(taskId), outputOptions(cmd));
4746
6150
  });
4747
- dataset.command("build-delete <ids...>").description("Delete one or more BuildTasks").option("--ignore-missing", "ignore missing task ids").action(async (ids, opts, cmd) => {
6151
+ buildTask.command("delete <ids...>").description("Delete one or more BuildTasks").option("--ignore-missing", "ignore missing task ids").action(async (ids, opts, cmd) => {
4748
6152
  printJson(
4749
6153
  await clientFrom(cmd).vega.deleteBuildTasks(ids, {
4750
6154
  ignoreMissing: opts.ignoreMissing
@@ -4756,18 +6160,22 @@ function vegaCommand() {
4756
6160
  GROUPS: [
4757
6161
  "catalog",
4758
6162
  "resource",
6163
+ "build-task",
4759
6164
  "connector-type",
4760
- "dataset",
4761
6165
  "discover-schedule",
4762
6166
  "discover-task",
4763
6167
  "semantic-task"
4764
6168
  ],
6169
+ READ: ["index-capabilities"],
4765
6170
  RUN: ["sql"]
4766
6171
  });
4767
6172
  groupChildren(resource, {
4768
6173
  READ: ["list", "get", "document-get"],
4769
- RUN: ["query", "discover"],
6174
+ RUN: ["query", "discover", "build"],
4770
6175
  WRITE: [
6176
+ "create",
6177
+ "update",
6178
+ "delete",
4771
6179
  "enable",
4772
6180
  "disable",
4773
6181
  "document-create",
@@ -4776,8 +6184,13 @@ function vegaCommand() {
4776
6184
  "document-delete-filter"
4777
6185
  ]
4778
6186
  });
6187
+ groupChildren(buildTask, {
6188
+ READ: ["list", "get"],
6189
+ RUN: ["start", "stop"],
6190
+ WRITE: ["delete"]
6191
+ });
4779
6192
  groupChildren(catalog, {
4780
- READ: ["list", "get", "resources", "health", "health-check-schedule"],
6193
+ READ: ["list", "get", "stats", "resources", "health", "health-check-schedule"],
4781
6194
  RUN: ["test-connection", "test-connection-config", "discover"],
4782
6195
  WRITE: ["create", "update", "enable", "disable", "delete", "set-health-check-schedule"]
4783
6196
  });
@@ -4790,41 +6203,51 @@ function vegaCommand() {
4790
6203
  QUERYING DIRECTLY
4791
6204
  sql --query "<sql>" runs against the source itself. Name a resource with a
4792
6205
  {{<resource-id>}} placeholder rather than the physical table it happens to have.
6206
+ resource query <id> reads rows through the resource: --filter, --sort, --output-fields,
6207
+ cursor paging, and --ignore-local-index to bypass a table's local index.
4793
6208
 
4794
6209
  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.`
6210
+ resource update <resource-id> saves schema_definition and index_config. Then resource build
6211
+ <resource-id> creates a BuildTask; build-task get / list follow it.
6212
+ --wait (on resource build, or build-task get for an existing task) shows progress on stderr
6213
+ and exits non-zero unless the task completes. --timeout 0 waits without a limit.`
4797
6214
  );
4798
6215
  return group(vega, "DATA & KNOWLEDGE");
4799
6216
  }
4800
6217
 
4801
6218
  // src/cli-program.ts
4802
6219
  function buildProgram() {
4803
- const program2 = new Command16();
4804
- program2.name("openbkn").description(
6220
+ const program = new Command17();
6221
+ program.name("openbkn").description(
4805
6222
  "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(
6223
+ ).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("--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
6224
  "--new-conversation",
4808
6225
  "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));
6226
+ ).option("-k, --insecure", "skip TLS verification (dev / self-signed only)").option(
6227
+ "--no-retry",
6228
+ "fail at once on a refused/dropped connection or a 429/502/503, instead of retrying 3 times"
6229
+ ).option("--dry-run", "print the request this command would send, and send nothing").showHelpAfterError();
6230
+ program.addCommand(authCommand());
6231
+ program.addCommand(configCommand());
6232
+ program.addCommand(appkeyCommand());
6233
+ program.addCommand(bknCommand());
6234
+ program.addCommand(vegaCommand());
6235
+ program.addCommand(resourceCommand());
6236
+ program.addCommand(contextCommand());
6237
+ program.addCommand(modelCommand());
6238
+ program.addCommand(skillCommand());
6239
+ program.addCommand(mcpCommand());
6240
+ program.addCommand(sandboxCommand());
6241
+ program.addCommand(toolboxCommand());
6242
+ program.addCommand(functionToolCommand());
6243
+ program.addCommand(apiToolCommand());
6244
+ program.addCommand(toolCommand());
6245
+ program.addCommand(traceCommand());
6246
+ program.addCommand(adminCommand());
6247
+ program.addCommand(callCommand());
6248
+ program.addCommand(describeCommand(program));
4826
6249
  guide(
4827
- program2,
6250
+ program,
4828
6251
  `FIRST STEPS
4829
6252
  openbkn auth login https://your-platform -u <user> -p <pass>
4830
6253
  openbkn bkn list # knowledge networks you can see
@@ -4837,13 +6260,16 @@ function buildProgram() {
4837
6260
  context query-object-instance --args '<json>'
4838
6261
  Look at the data vega catalog list -> resource find --name <t> -> resource query <id>
4839
6262
  Build from a catalog bkn create-from-catalog <catalog-id> --name "<n>" ->
4840
- vega dataset build <resource-id>
6263
+ vega resource build <resource-id>
4841
6264
  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>
6265
+ Ship an API toolbox create --name "<n>" --service-url <url> ->
6266
+ api import ./api.yaml --toolbox <box-id> ->
6267
+ api enable <tool-id> --toolbox <box-id> -> toolbox publish <box-id>
6268
+ -> api execute <tool-id> --toolbox <box-id>
6269
+ Ship some code sandbox run ./add.py -> toolbox create --name "<n>" --type function
6270
+ -> function create ./add.py --toolbox <box-id> --name add ->
6271
+ function enable <tool-id> --toolbox <box-id> ->
6272
+ toolbox publish <box-id> -> function execute <tool-id> --toolbox <box-id>
4847
6273
  Debug an answer trace conversations list -> trace diagnose <conversation-id> --llm
4848
6274
 
4849
6275
  GOOD TO KNOW
@@ -4855,12 +6281,31 @@ function buildProgram() {
4855
6281
  --full widens it). Most list commands answer {entries, total_count}; anything else says
4856
6282
  so in its own description. Ids come from list/search output \u2014 opaque, never guess one,
4857
6283
  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.
6284
+ Saved logins are selected with --user; access follows the account's platform grants.
4859
6285
  \`openbkn call\` reaches any endpoint a command does not cover, auth injected. Look the
4860
6286
  path up at https://openbkn-ai.github.io/bkn-foundry/ first \u2014 do not guess one.`
4861
6287
  );
4862
- installGroupedHelp(program2);
4863
- return program2;
6288
+ installGroupedHelp(program);
6289
+ return program;
6290
+ }
6291
+
6292
+ // src/cli-run.ts
6293
+ async function runCli(argv = process.argv) {
6294
+ const program = buildProgram();
6295
+ if (argv.includes("--dry-run")) enableDryRun();
6296
+ try {
6297
+ await program.parseAsync(argv);
6298
+ } catch (err) {
6299
+ if (err instanceof DryRunSignal) {
6300
+ process.stdout.write(`${JSON.stringify(err.request, null, 2)}
6301
+ `);
6302
+ return;
6303
+ }
6304
+ console.error(formatError(err));
6305
+ process.exitCode = toExitCode(err);
6306
+ } finally {
6307
+ await releaseLifecycleSessions();
6308
+ }
4864
6309
  }
4865
6310
 
4866
6311
  // src/cli.ts
@@ -4868,21 +6313,5 @@ process.stdout.on("error", (err) => {
4868
6313
  if (err.code === "EPIPE") process.exit(0);
4869
6314
  throw err;
4870
6315
  });
4871
- var program = buildProgram();
4872
- var argv = process.argv.map((a) => a === "-bd" ? "--biz-domain" : a);
4873
- if (argv.includes("--dry-run")) enableDryRun();
4874
- try {
4875
- await program.parseAsync(argv);
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
- }
4883
- console.error(formatError(err));
4884
- await releaseLifecycleSessions();
4885
- process.exit(toExitCode(err));
4886
- }
4887
- await releaseLifecycleSessions();
6316
+ await runCli();
4888
6317
  //# sourceMappingURL=cli.js.map