@openbkn/bkn-sdk 0.1.1-alpha.1 → 0.1.1-alpha.11

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,47 +1,56 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ DEFAULT_BUSINESS_DOMAIN,
3
4
  DEFAULT_LIST_LIMIT,
4
5
  DEFAULT_QUERY_LIMIT,
6
+ HttpError,
5
7
  InputError,
6
8
  activePlatform,
9
+ attachNoAuth,
7
10
  attachToken,
8
- changePassword,
9
11
  createClient,
12
+ credentialDeviceLogin,
10
13
  currentToken,
14
+ currentTokenFresh,
15
+ decodeJwt,
11
16
  deletePlatform,
17
+ deviceLogin,
12
18
  exportCreds,
19
+ fetchAuthStatus,
13
20
  formatError,
21
+ getUserSafe,
22
+ isHeadless,
14
23
  listPlatforms,
15
24
  logout,
25
+ openBrowser,
16
26
  parseEmbeddingFields,
17
27
  parsePkMap,
18
28
  rawCall,
19
29
  readPlatformConfig,
20
- renderOrgTree,
21
30
  renderReportMarkdown,
31
+ request,
22
32
  resolveContext,
23
33
  setActivePlatform,
24
34
  status,
25
35
  switchUser,
26
36
  toExitCode,
27
37
  use,
28
- usersOf,
29
38
  whoami,
30
39
  writePlatformConfig
31
- } from "./chunk-ADZ23DPF.js";
40
+ } from "./chunk-SEKM54NB.js";
32
41
 
33
42
  // src/cli.ts
34
- import { Command as Command16 } from "commander";
43
+ import { Command as Command17 } from "commander";
35
44
 
36
45
  // package.json
37
46
  var package_default = {
38
47
  name: "@openbkn/bkn-sdk",
39
- version: "0.1.1-alpha.1",
48
+ version: "0.1.1-alpha.11",
40
49
  description: "Unified TypeScript SDK + CLI for the BKN (Business Knowledge Network) platform.",
41
50
  type: "module",
42
51
  license: "Apache-2.0",
43
52
  engines: {
44
- node: ">=22"
53
+ node: ">=18"
45
54
  },
46
55
  bin: {
47
56
  openbkn: "./dist/cli.js"
@@ -76,6 +85,7 @@ var package_default = {
76
85
  },
77
86
  dependencies: {
78
87
  "@clack/prompts": "^0.9.1",
88
+ "@openbkn/bkn-sdk": "^0.1.1-alpha.3",
79
89
  chalk: "^5.4.1",
80
90
  commander: "^13.1.0",
81
91
  "csv-parse": "^6.2.1",
@@ -148,6 +158,19 @@ function installGroupedHelp(root) {
148
158
  apply(root);
149
159
  }
150
160
 
161
+ // src/utils/org-tree.ts
162
+ function renderOrgTree(nodes, prefix = "") {
163
+ const lines = [];
164
+ nodes.forEach((node, i) => {
165
+ const last = i === nodes.length - 1;
166
+ lines.push(`${prefix}${last ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}${node.name} (id: ${node.id})`);
167
+ if (node.children.length) {
168
+ lines.push(renderOrgTree(node.children, `${prefix}${last ? " " : "\u2502 "}`));
169
+ }
170
+ });
171
+ return lines.join("\n");
172
+ }
173
+
151
174
  // src/utils/output.ts
152
175
  function printJson(value, opts = {}) {
153
176
  if (opts.json || opts.compact) {
@@ -157,10 +180,11 @@ function printJson(value, opts = {}) {
157
180
  }
158
181
  const rows = toRows(value);
159
182
  if (rows) {
160
- const columns = opts.full ? columnsOf(rows).filter((c) => rows.some((r) => stringifyCell(r[c]) !== "")) : selectColumns(rows);
183
+ const fullColumns = columnsOf(rows).filter((c) => rows.some((r) => stringifyCell(r[c]) !== ""));
184
+ const columns = opts.full ? fullColumns : selectColumns(rows);
161
185
  if (columns.length > 0) {
162
186
  printTable(rows, columns);
163
- const hidden = columnsOf(rows).length - columns.length;
187
+ const hidden = fullColumns.length - columns.length;
164
188
  if (hidden > 0 && !opts.full) {
165
189
  process.stdout.write(`\u2026 ${hidden} more column(s); use --full or --json for everything
166
190
  `);
@@ -168,10 +192,32 @@ function printJson(value, opts = {}) {
168
192
  return;
169
193
  }
170
194
  }
195
+ if (isEmptyEnvelope(value)) {
196
+ process.stdout.write("(no results)\n");
197
+ return;
198
+ }
171
199
  process.stdout.write(`${JSON.stringify(value, null, 2)}
172
200
  `);
173
201
  }
174
- var ROW_ENVELOPES = ["entries", "data", "cases", "reports", "results", "list", "recurringRules"];
202
+ function isEmptyEnvelope(value) {
203
+ if (!value || typeof value !== "object") return false;
204
+ const o = value;
205
+ return ROW_ENVELOPES.some((k) => Array.isArray(o[k]) && o[k].length === 0);
206
+ }
207
+ var ROW_ENVELOPES = [
208
+ "entries",
209
+ "data",
210
+ "cases",
211
+ "reports",
212
+ "results",
213
+ "list",
214
+ "recurringRules",
215
+ "users",
216
+ "roles",
217
+ "departments",
218
+ "members",
219
+ "keys"
220
+ ];
175
221
  function toRows(value) {
176
222
  const isRowArray = (v) => Array.isArray(v) && v.length > 0 && v.every((x) => x !== null && typeof x === "object" && !Array.isArray(x));
177
223
  if (isRowArray(value)) return value;
@@ -255,6 +301,25 @@ function stringifyCell(v) {
255
301
  return s.length > CELL_MAX ? `${s.slice(0, CELL_MAX - 1)}\u2026` : s;
256
302
  }
257
303
 
304
+ // src/utils/prompt.ts
305
+ import { createInterface } from "readline";
306
+ function promptLine(query, hidden = false) {
307
+ return new Promise((resolve2) => {
308
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
309
+ if (hidden) {
310
+ const mutable = rl;
311
+ mutable._writeToOutput = (s) => {
312
+ if (s.startsWith(query)) process.stdout.write(query);
313
+ };
314
+ }
315
+ rl.question(query, (answer) => {
316
+ rl.close();
317
+ if (hidden) process.stdout.write("\n");
318
+ resolve2(answer.trim());
319
+ });
320
+ });
321
+ }
322
+
258
323
  // src/commands/_shared.ts
259
324
  import { readFileSync } from "fs";
260
325
  function clientFrom(cmd) {
@@ -286,350 +351,212 @@ function readBody(opts) {
286
351
  }
287
352
 
288
353
  // src/commands/auth.ts
289
- import { readFileSync as readFileSync2 } from "fs";
290
354
  import { Command } from "commander";
291
355
 
292
- // src/auth/oauth.ts
293
- import { spawn } from "child_process";
294
- import { createHash, constants as cryptoConstants, publicEncrypt, randomBytes } from "crypto";
295
- import { createServer } from "http";
296
- var DEFAULT_REDIRECT_PORT = 9010;
297
- var DEFAULT_SCOPE = "openid offline all";
298
- var STUDIOWEB_LOGIN_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
299
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyOstgbYuubBi2PUqeVj
300
- GKlkwVUY6w1Y8d4k116dI2SkZI8fxcjHALv77kItO4jYLVplk9gO4HAtsisnNE2o
301
- wlYIqdmyEPMwupaeFFFcg751oiTXJiYbtX7ABzU5KQYPjRSEjMq6i5qu/mL67XTk
302
- hvKwrC83zme66qaKApmKupDODPb0RRkutK/zHfd1zL7sciBQ6psnNadh8pE24w8O
303
- 2XVy1v2bgSNkGHABgncR7seyIg81JQ3c/Axxd6GsTztjLnlvGAlmT1TphE84mi99
304
- fUaGD2A1u1qdIuNc+XuisFeNcUW6fct0+x97eS2eEGRr/7qxWmO/P20sFVzXc2bF
305
- 1QIDAQAB
306
- -----END PUBLIC KEY-----`;
307
- function normalizeBaseUrl(value) {
308
- return value.replace(/\/+$/, "");
309
- }
310
- function generatePkce() {
311
- const verifier = randomBytes(48).toString("base64url");
312
- return { verifier, challenge: createHash("sha256").update(verifier).digest("base64url") };
313
- }
314
- function buildAuthorizeUrl(base, clientId, redirectUri, state, codeChallenge, scope = DEFAULT_SCOPE) {
315
- const params = new URLSearchParams({
316
- response_type: "code",
317
- client_id: clientId,
318
- redirect_uri: redirectUri,
319
- scope,
320
- state,
321
- "x-forwarded-prefix": "",
322
- lang: "zh-cn",
323
- product: "adp",
324
- code_challenge: codeChallenge,
325
- code_challenge_method: "S256"
326
- });
327
- return `${base}/oauth2/auth?${params.toString()}`;
328
- }
329
- function mapToken(data) {
330
- return {
331
- accessToken: data.access_token,
332
- refreshToken: data.refresh_token,
333
- idToken: data.id_token
334
- };
335
- }
336
- async function registerClient(base, redirectUri, scope = DEFAULT_SCOPE) {
337
- const res = await fetch(`${base}/oauth2/clients`, {
338
- method: "POST",
339
- headers: { "Content-Type": "application/json", Accept: "application/json" },
340
- body: JSON.stringify({
341
- client_name: "openbkn-cli",
342
- grant_types: ["authorization_code", "implicit", "refresh_token"],
343
- response_types: ["token id_token", "code", "token"],
344
- scope,
345
- redirect_uris: [redirectUri],
346
- post_logout_redirect_uris: [redirectUri.replace("/callback", "/successful-logout")],
347
- metadata: { device: { name: "openbkn-cli", client_type: "web", description: "openbkn CLI" } }
348
- })
349
- });
350
- if (!res.ok) {
351
- throw new Error(
352
- `Client registration failed (${res.status}): ${await res.text() || res.statusText}`
353
- );
354
- }
355
- const data = await res.json();
356
- return { clientId: data.client_id, clientSecret: data.client_secret };
357
- }
358
- async function exchangeCode(base, code, redirectUri, client, codeVerifier) {
359
- const params = {
360
- grant_type: "authorization_code",
361
- code,
362
- redirect_uri: redirectUri,
363
- code_verifier: codeVerifier
364
- };
365
- const headers = {
366
- "Content-Type": "application/x-www-form-urlencoded",
367
- Accept: "application/json"
368
- };
369
- if (client.clientSecret) {
370
- headers.Authorization = `Basic ${Buffer.from(`${client.clientId}:${client.clientSecret}`).toString("base64")}`;
371
- } else {
372
- params.client_id = client.clientId;
373
- }
374
- const res = await fetch(`${base}/oauth2/token`, {
356
+ // src/api/eacp-crypto.ts
357
+ import {
358
+ constants,
359
+ createPrivateKey,
360
+ createPublicKey,
361
+ publicEncrypt
362
+ } from "crypto";
363
+
364
+ // src/api/admin.ts
365
+ async function changePasswordSafe(ctx, account, oldPassword, newPassword) {
366
+ await request(ctx, "/api/safe/v1/auth/change-password", {
375
367
  method: "POST",
376
- headers,
377
- body: new URLSearchParams(params).toString()
368
+ body: { account, old_password: oldPassword, new_password: newPassword }
378
369
  });
379
- if (!res.ok) {
380
- throw new Error(
381
- `Token exchange failed (${res.status}): ${await res.text() || res.statusText}`
382
- );
383
- }
384
- return mapToken(await res.json());
370
+ return { ok: true };
385
371
  }
386
- function startCallbackServer(port) {
387
- return new Promise((resolve2, reject) => {
388
- const server = createServer((req2, res) => {
389
- const u = new URL(req2.url ?? "/", `http://127.0.0.1:${port}`);
390
- if (u.pathname !== "/callback") {
391
- res.writeHead(404);
392
- res.end();
393
- return;
394
- }
395
- const code = u.searchParams.get("code");
396
- const error = u.searchParams.get("error");
397
- if (error) {
398
- res.writeHead(400, { "content-type": "text/html" });
399
- res.end(`<h1>Login failed</h1><p>${error}</p>`);
400
- server.close(() => reject(new Error(`OAuth error: ${error}`)));
401
- return;
402
- }
403
- if (!code) {
404
- res.writeHead(400);
405
- res.end("missing code");
406
- return;
407
- }
408
- res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
409
- res.end("<h1>Login successful</h1><p>You can close this window.</p>");
410
- resolve2({
411
- code,
412
- state: u.searchParams.get("state") ?? void 0,
413
- close: () => server.close()
414
- });
415
- });
416
- server.on("error", reject);
417
- server.listen(port, "127.0.0.1");
418
- });
419
- }
420
- function openBrowser(url) {
421
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
372
+
373
+ // src/commands/auth.ts
374
+ async function resolveAccount(baseUrl, accessToken, insecure, idToken) {
375
+ const sub = decodeJwt(idToken ?? accessToken)?.sub;
376
+ if (!sub) return void 0;
422
377
  try {
423
- spawn(cmd, [url], {
424
- stdio: "ignore",
425
- detached: true,
426
- shell: process.platform === "win32"
427
- }).unref();
378
+ const u = await getUserSafe(
379
+ { baseUrl, token: accessToken, businessDomain: DEFAULT_BUSINESS_DOMAIN, insecure },
380
+ sub
381
+ );
382
+ return u.account;
428
383
  } catch {
384
+ return void 0;
429
385
  }
430
386
  }
431
- async function browserLogin(baseUrl, opts = {}) {
432
- const base = normalizeBaseUrl(baseUrl);
433
- const port = opts.port ?? DEFAULT_REDIRECT_PORT;
434
- const redirectUri = `http://127.0.0.1:${port}/callback`;
435
- const scope = opts.scope ?? DEFAULT_SCOPE;
436
- const client = opts.clientId ? { clientId: opts.clientId } : await registerClient(base, redirectUri, scope);
437
- const { verifier, challenge } = generatePkce();
438
- const state = randomBytes(12).toString("hex");
439
- const authUrl = buildAuthorizeUrl(base, client.clientId, redirectUri, state, challenge, scope);
440
- const waiter = startCallbackServer(port);
441
- if (opts.noBrowser) {
442
- process.stderr.write(`Open this URL to log in:
443
- ${authUrl}
444
- `);
445
- } else {
446
- process.stderr.write(`Opening browser for login\u2026
447
- If it doesn't open, visit:
448
- ${authUrl}
449
- `);
450
- openBrowser(authUrl);
387
+ function renderSessions(items) {
388
+ const byPlatform = /* @__PURE__ */ new Map();
389
+ for (const it of items) {
390
+ const arr = byPlatform.get(it.baseUrl) ?? [];
391
+ arr.push(it);
392
+ byPlatform.set(it.baseUrl, arr);
451
393
  }
452
- const { code, state: returned, close } = await waiter;
453
- close();
454
- if (returned && returned !== state) throw new Error("OAuth state mismatch \u2014 possible CSRF.");
455
- return exchangeCode(base, code, redirectUri, client, verifier);
456
- }
457
- function mergeCookies(existing, res) {
458
- const setCookies = typeof res.headers.getSetCookie === "function" ? res.headers.getSetCookie() : res.headers.get("set-cookie") ? [res.headers.get("set-cookie")] : [];
459
- const map = /* @__PURE__ */ new Map();
460
- const add = (pair) => {
461
- const eq = pair.indexOf("=");
462
- if (eq > 0) map.set(pair.slice(0, eq), pair.slice(eq + 1));
463
- };
464
- for (const p of existing.split(";").map((s) => s.trim()).filter(Boolean))
465
- add(p);
466
- for (const sc of setCookies) add(sc.split(";")[0]?.trim() ?? "");
467
- return [...map.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
468
- }
469
- function parseSigninProps(html) {
470
- const m = html.match(/<script[^>]*\bid=["']__NEXT_DATA__["'][^>]*>([\s\S]*?)<\/script>/i);
471
- if (!m?.[1]) throw new Error("Could not find __NEXT_DATA__ on /oauth2/signin.");
472
- const data = JSON.parse(m[1]);
473
- const pp = data.props?.pageProps;
474
- const csrftoken = pp?.csrftoken ?? pp?._csrf;
475
- if (typeof csrftoken !== "string") throw new Error("Sign-in page did not expose csrftoken.");
476
- return {
477
- csrftoken,
478
- challenge: typeof pp?.challenge === "string" ? pp.challenge : void 0,
479
- remember: pp?.remember === true || pp?.remember === "true"
480
- };
481
- }
482
- async function followToCallback(startUrl, jar0, state, redirectUri) {
483
- let url = startUrl;
484
- let jar = jar0;
485
- const cb = new URL(redirectUri);
486
- for (let hop = 0; hop < 40; hop++) {
487
- const resp = await fetch(url, {
488
- headers: { Cookie: jar, Accept: "text/html,*/*;q=0.8" },
489
- redirect: "manual"
490
- });
491
- jar = mergeCookies(jar, resp);
492
- if (![302, 303, 307, 308].includes(resp.status)) {
493
- throw new Error(`Unexpected OAuth response (HTTP ${resp.status}).`);
494
- }
495
- const loc = resp.headers.get("location");
496
- if (!loc) throw new Error(`OAuth redirect missing Location (HTTP ${resp.status}).`);
497
- const next = new URL(loc, url);
498
- if (next.origin === cb.origin && next.pathname === cb.pathname) {
499
- const err = next.searchParams.get("error");
500
- if (err) throw new Error(`Authorization failed: ${err}`);
501
- const code = next.searchParams.get("code");
502
- if (next.searchParams.get("state") !== state) throw new Error("OAuth state mismatch.");
503
- if (!code) throw new Error("Callback missing authorization code.");
504
- return code;
505
- }
506
- url = next.href;
394
+ const lines = [];
395
+ for (const [platform, users] of byPlatform) {
396
+ lines.push(platform);
397
+ for (const u of users) lines.push(` ${u.active ? "*" : " "} ${u.username ?? u.userId}`);
507
398
  }
508
- throw new Error("Too many OAuth redirects.");
399
+ return lines.join("\n") || "(no saved sessions)";
509
400
  }
510
- async function passwordLogin(baseUrl, username, password, opts = {}) {
511
- const base = normalizeBaseUrl(baseUrl);
512
- const port = opts.port ?? DEFAULT_REDIRECT_PORT;
513
- const redirectUri = `http://127.0.0.1:${port}/callback`;
514
- const scope = opts.scope ?? DEFAULT_SCOPE;
515
- const client = opts.clientId ? { clientId: opts.clientId } : await registerClient(base, redirectUri, scope);
516
- const { verifier, challenge } = generatePkce();
517
- const state = randomBytes(12).toString("hex");
518
- let jar = "";
519
- const authResp = await fetch(
520
- buildAuthorizeUrl(base, client.clientId, redirectUri, state, challenge, scope),
521
- { redirect: "manual" }
522
- );
523
- jar = mergeCookies(jar, authResp);
524
- const authLoc = authResp.headers.get("location");
525
- if (!authLoc) throw new Error(`/oauth2/auth did not redirect (HTTP ${authResp.status}).`);
526
- const signinUrl = new URL(authLoc, base);
527
- if (!signinUrl.pathname.includes("signin")) {
528
- throw new Error(`Expected a sign-in redirect, got: ${authLoc}`);
529
- }
530
- const pageResp = await fetch(signinUrl.href, {
531
- headers: { Cookie: jar, Accept: "text/html,*/*;q=0.8" },
532
- redirect: "manual"
533
- });
534
- jar = mergeCookies(jar, pageResp);
535
- const props = parseSigninProps(await pageResp.text());
536
- const loginChallenge = signinUrl.searchParams.get("login_challenge")?.trim() || props.challenge?.trim();
537
- if (!loginChallenge) throw new Error("Could not resolve the login challenge.");
538
- const cipher = publicEncrypt(
539
- {
540
- key: opts.signinPublicKeyPem ?? STUDIOWEB_LOGIN_PUBLIC_KEY_PEM,
541
- padding: cryptoConstants.RSA_PKCS1_PADDING
542
- },
543
- Buffer.from(password, "utf8")
544
- ).toString("base64");
545
- const postResp = await fetch(`${base}/oauth2/signin`, {
546
- method: "POST",
547
- headers: {
548
- Cookie: jar,
549
- "Content-Type": "application/json",
550
- Accept: "application/json, text/plain, */*",
551
- Origin: new URL(base).origin,
552
- Referer: signinUrl.href
553
- },
554
- body: JSON.stringify({
555
- _csrf: props.csrftoken,
556
- challenge: loginChallenge,
557
- account: username,
558
- password: cipher,
559
- vcode: { id: "", content: "" },
560
- dualfactorauthinfo: { validcode: { vcode: "" }, OTP: { OTP: "" } },
561
- remember: props.remember ?? false,
562
- device: { name: "", description: "", client_type: "console_web", udids: [] }
563
- }),
564
- redirect: "manual"
565
- });
566
- jar = mergeCookies(jar, postResp);
567
- let code;
568
- if ([302, 303, 307].includes(postResp.status)) {
569
- const loc = postResp.headers.get("location");
570
- if (!loc) throw new Error("Sign-in response missing Location.");
571
- code = await followToCallback(new URL(loc, base).href, jar, state, redirectUri);
572
- } else if (postResp.status === 200) {
573
- const text = await postResp.text();
574
- let json = null;
575
- try {
576
- json = JSON.parse(text);
577
- } catch {
578
- }
579
- const redir = json && typeof json.redirect === "string" ? json.redirect : "";
580
- if (!redir) {
581
- const msg = json && typeof json.message === "string" ? json.message : text.slice(0, 300);
582
- throw new InputError(`Sign-in failed: ${msg}`);
583
- }
584
- code = await followToCallback(new URL(redir, base).href, jar, state, redirectUri);
585
- } else {
586
- throw new InputError(
587
- `Sign-in failed (HTTP ${postResp.status}): ${(await postResp.text()).slice(0, 300)}`
588
- );
589
- }
590
- return exchangeCode(base, code, redirectUri, client, verifier);
591
- }
592
-
593
- // src/commands/auth.ts
594
401
  function registerAuthLeaves(cmd) {
595
- cmd.command("login <url>").description("Log in to a platform (attach a token, or browser/password OAuth)").option("-u, --username <name>", "username for password signin").option("-p, --password <pwd>", "password for password signin").option("--token <token>", "provide a token directly (CI / headless)").option("--client-id <id>", "use a fixed OAuth2 client id (skip dynamic registration)").option("--client-secret <secret>", "OAuth2 client secret (omit for public/PKCE)").option("--port <n>", "local callback port", (v) => Number.parseInt(v, 10)).option(
596
- "--signin-public-key-file <path>",
597
- "override the RSA public key (PEM) for password signin"
598
- ).option("--product <name>", "OAuth product query (default 'adp')").option("--no-browser", "headless: print the authorize URL instead of opening a browser").action(async (url, opts, cmd2) => {
402
+ cmd.command("login <url>").description("Log in to a platform (attach a token, or browser/password OAuth)").option("-u, --username <name>", "username for password signin").option("-p, --password <pwd>", "password for password signin").option("--token <token>", "provide a token directly (CI / headless)").option("--client-id <id>", "use a fixed OAuth2 client id (skip dynamic registration)").option("--client-secret <secret>", "OAuth2 client secret (omit for public/PKCE)").option(
403
+ "--port <n>",
404
+ "loopback redirect port for the auth_code flow",
405
+ (v) => Number.parseInt(v, 10)
406
+ ).option("--device", "headless device-code login (RFC 8628) \u2014 no callback server, no password").option("--audience <aud>", "device-code token audience", "bkn-safe").option(
407
+ "--timeout <s>",
408
+ "device-login wait before timing out",
409
+ (v) => Number.parseInt(v, 10),
410
+ 120
411
+ ).option("--no-browser", "(legacy) print the URL instead of opening a browser").option("--product <name>", "(legacy) ISF OAuth product query").option("--signin-public-key-file <path>", "(legacy) RSA public key for ISF /oauth2/signin").option("--no-auth", "register the platform with no authentication (no bkn-safe)").action(async (url, opts, cmd2) => {
599
412
  const g = cmd2.optsWithGlobals();
600
413
  if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
414
+ const out = outputOptions(cmd2);
415
+ const report = (r) => {
416
+ if (out.json || out.compact) {
417
+ printJson({ loggedIn: true, ...r }, out);
418
+ } else if (r.noAuth) {
419
+ process.stdout.write(`Registered ${r.baseUrl ?? url} (no authentication)
420
+ `);
421
+ } else {
422
+ process.stdout.write(`Logged in to ${r.baseUrl ?? url} as ${r.username ?? r.userId}
423
+ `);
424
+ }
425
+ };
601
426
  const token = opts.token ?? g.token;
602
427
  if (token) {
603
- const r2 = attachToken(url, token, { insecure: g.insecure });
604
- printJson({ loggedIn: true, ...r2 }, outputOptions(cmd2));
428
+ report(attachToken(url, token, { insecure: g.insecure }));
605
429
  return;
606
430
  }
607
- const signinKey = opts.signinPublicKeyFile ? readFileSync2(opts.signinPublicKeyFile, "utf8") : void 0;
608
- const tokens = opts.username ? await passwordLogin(url, opts.username, opts.password ?? "", {
609
- clientId: opts.clientId,
610
- port: opts.port,
611
- signinPublicKeyPem: signinKey
612
- }) : await browserLogin(url, {
613
- clientId: opts.clientId,
614
- port: opts.port,
615
- noBrowser: opts.browser === false
616
- });
617
- const r = attachToken(url, tokens.accessToken, {
618
- refreshToken: tokens.refreshToken,
619
- idToken: tokens.idToken,
620
- insecure: g.insecure
621
- });
622
- printJson({ loggedIn: true, ...r }, outputOptions(cmd2));
431
+ if (opts.auth === false) {
432
+ report(attachNoAuth(url, { insecure: g.insecure }));
433
+ return;
434
+ }
435
+ const authStatus = await fetchAuthStatus(url);
436
+ if (authStatus && !authStatus.enabled) {
437
+ process.stderr.write(
438
+ `Platform auth is disabled (stack: ${authStatus.stack ?? "none"}) \u2014 registering without auth.
439
+ `
440
+ );
441
+ report(attachNoAuth(url, { insecure: g.insecure }));
442
+ return;
443
+ }
444
+ let tokens;
445
+ let account;
446
+ try {
447
+ if (opts.username || opts.password) {
448
+ const username = opts.username ?? await promptLine("Username: ");
449
+ account = username;
450
+ const password = opts.password ?? await promptLine("Password: ", true);
451
+ tokens = await credentialDeviceLogin(url, username, password, {
452
+ clientId: opts.clientId,
453
+ audience: opts.audience,
454
+ timeoutMs: opts.timeout * 1e3
455
+ });
456
+ } else {
457
+ const headless = isHeadless();
458
+ const openInBrowser = !opts.device && opts.browser !== false && !headless;
459
+ tokens = await deviceLogin(url, {
460
+ clientId: opts.clientId,
461
+ audience: opts.audience,
462
+ timeoutMs: opts.timeout * 1e3,
463
+ onPrompt: ({ userCode, verificationUri, verificationUriComplete }) => {
464
+ const target = verificationUriComplete ?? verificationUri;
465
+ process.stderr.write(
466
+ `
467
+ Open this URL to sign in and authorize:
468
+ ${target}
469
+ User code: ${userCode}
470
+ `
471
+ );
472
+ if (openInBrowser) openBrowser(target);
473
+ else if (headless && !opts.device && opts.browser !== false)
474
+ process.stderr.write("(headless \u2014 approve on any machine with a browser)\n");
475
+ process.stderr.write("Waiting for authorization\u2026\n");
476
+ }
477
+ });
478
+ }
479
+ } catch (e) {
480
+ if (e instanceof Error && /Device auth failed \(404\)/.test(e.message)) {
481
+ process.stderr.write("No auth endpoint found \u2014 registering platform without auth.\n");
482
+ report(attachNoAuth(url, { insecure: g.insecure }));
483
+ return;
484
+ }
485
+ throw e;
486
+ }
487
+ if (!account) {
488
+ account = await resolveAccount(
489
+ url,
490
+ tokens.accessToken,
491
+ Boolean(g.insecure),
492
+ tokens.idToken
493
+ );
494
+ }
495
+ report(
496
+ attachToken(url, tokens.accessToken, {
497
+ refreshToken: tokens.refreshToken,
498
+ idToken: tokens.idToken,
499
+ insecure: g.insecure,
500
+ username: account
501
+ })
502
+ );
623
503
  });
624
504
  cmd.command("status").description("Show base URL and whether a token is configured").action((_opts, cmd2) => printJson(status(), outputOptions(cmd2)));
625
- cmd.command("token").description("Print the current access token (keep secret)").action(() => {
626
- process.stdout.write(`${currentToken()}
505
+ cmd.command("token").description("Print the current access token, refreshing it if expired (keep secret)").option("--no-refresh", "print the stored token as-is, without refreshing").action(async (opts, cmd2) => {
506
+ const g = cmd2.optsWithGlobals();
507
+ if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
508
+ const token = opts.refresh === false ? currentToken() : await currentTokenFresh();
509
+ process.stdout.write(`${token}
510
+ `);
511
+ });
512
+ 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) => {
513
+ const g = cmd2.optsWithGlobals();
514
+ if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
515
+ const me = whoami();
516
+ if (opts.lookup !== false && me.baseUrl && me.sub) {
517
+ try {
518
+ const u = await getUserSafe(
519
+ {
520
+ baseUrl: me.baseUrl,
521
+ token: currentToken(),
522
+ businessDomain: DEFAULT_BUSINESS_DOMAIN,
523
+ insecure: Boolean(g.insecure)
524
+ },
525
+ me.sub
526
+ );
527
+ if (u.account) me.username = u.account;
528
+ if (u.name) me.name = u.name;
529
+ } catch {
530
+ }
531
+ }
532
+ const out = outputOptions(cmd2);
533
+ if (out.json || out.compact || out.full) {
534
+ printJson(me, out);
535
+ return;
536
+ }
537
+ const expMs = typeof me.exp === "number" ? me.exp * 1e3 : void 0;
538
+ const expired = expMs !== void 0 && expMs < Date.now();
539
+ const rows = [
540
+ ["User", String(me.username ?? me.sub ?? "(unknown)")],
541
+ ...me.name && me.name !== me.username ? [["Name", String(me.name)]] : [],
542
+ ["ID", String(me.userId ?? me.sub ?? "-")],
543
+ ["Platform", String(me.baseUrl ?? "-")],
544
+ ...expMs !== void 0 ? [["Expires", `${new Date(expMs).toISOString()}${expired ? " (expired)" : ""}`]] : []
545
+ ];
546
+ const pad2 = Math.max(...rows.map(([k]) => k.length));
547
+ process.stdout.write(
548
+ `${rows.map(([k, v]) => `${k.padEnd(pad2)} ${v}`).join("\n")}
549
+ \u2026 use --full or --json for all claims
550
+ `
551
+ );
552
+ });
553
+ cmd.command("list").alias("ls").description("List saved sessions (platform \u2192 users; * = active)").action((_opts, cmd2) => {
554
+ const items = listPlatforms();
555
+ const out = outputOptions(cmd2);
556
+ if (out.json || out.compact) printJson(items, out);
557
+ else process.stdout.write(`${renderSessions(items)}
627
558
  `);
628
559
  });
629
- cmd.command("whoami [url]").description("Show current user identity (from the token)").option("--no-lookup", "skip the backend identity fallback (eacp/user/get)").action(
630
- (_url, _opts, cmd2) => printJson(whoami(), outputOptions(cmd2))
631
- );
632
- cmd.command("list").alias("ls").description("List platforms with a saved session").action((_opts, cmd2) => printJson(listPlatforms(), outputOptions(cmd2)));
633
560
  cmd.command("use <url>").description("Switch the active platform").action((url, _opts, cmd2) => {
634
561
  use(url);
635
562
  printJson(status(), outputOptions(cmd2));
@@ -638,28 +565,56 @@ function registerAuthLeaves(cmd) {
638
565
  cmd.command("delete <url>").description("Delete saved credentials for a platform").action(
639
566
  (url, _opts, cmd2) => printJson({ deleted: deletePlatform(url) }, outputOptions(cmd2))
640
567
  );
641
- cmd.command("switch <url> <user-id>").description("Switch the active user for a platform").action((url, userId, _opts, cmd2) => {
642
- printJson(switchUser(url, userId), outputOptions(cmd2));
568
+ cmd.command("switch <url> <user>").description("Switch the active user for a platform (by username or user id)").action((url, user, _opts, cmd2) => {
569
+ const r = switchUser(url, user);
570
+ const out = outputOptions(cmd2);
571
+ if (out.json || out.compact) printJson(r, out);
572
+ else process.stdout.write(`Switched to ${r.username ?? r.userId} on ${r.baseUrl}
573
+ `);
643
574
  });
644
- cmd.command("users <url>").description("List saved user profiles for a platform").action((url, _opts, cmd2) => {
645
- printJson(usersOf(url), outputOptions(cmd2));
575
+ cmd.command("users <url>").description("List saved users for a platform (* = active)").action((url, _opts, cmd2) => {
576
+ const norm = url.replace(/\/+$/, "");
577
+ const items = listPlatforms().filter((i) => i.baseUrl === norm);
578
+ const out = outputOptions(cmd2);
579
+ if (out.json || out.compact) printJson(items, out);
580
+ else process.stdout.write(`${renderSessions(items)}
581
+ `);
646
582
  });
647
583
  cmd.command("export").description("Export the active session's tokens (for a headless host)").action((_opts, cmd2) => {
648
584
  printJson(exportCreds(), outputOptions(cmd2));
649
585
  });
650
- cmd.command("change-password [url]").description("Change your account password (EACP, RSA-encrypted in transit)").requiredOption("-a, --account <name>", "account / login name").requiredOption("--old-password <pwd>", "current password").requiredOption("--new-password <pwd>", "new password").option("--public-key-file <path>", "override the RSA public key (PEM) for password encryption").action(async (_url, opts, cmd2) => {
586
+ cmd.command("change-password [url]").description("Change your account password (bkn-safe self-service; no browser)").option("-a, --account <name>", "account / login name (the login column, e.g. admin)").option("--old-password <pwd>", "current password").option("--new-password <pwd>", "new password").option("--public-key-file <path>", "(legacy) RSA public key for ISF password encryption").action(async (url, opts, cmd2) => {
651
587
  const g = cmd2.optsWithGlobals();
588
+ if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
652
589
  const ctx = resolveContext({
653
- baseUrl: g.baseUrl,
590
+ baseUrl: url ?? g.baseUrl,
654
591
  token: g.token,
655
592
  user: g.user,
656
593
  businessDomain: g.bizDomain,
657
594
  insecure: g.insecure
658
595
  });
659
- printJson(
660
- await changePassword(ctx, opts.account, opts.oldPassword, opts.newPassword),
661
- outputOptions(cmd2)
662
- );
596
+ const account = opts.account ?? await promptLine("Account: ");
597
+ const oldPassword = opts.oldPassword ?? await promptLine("Current password: ", true);
598
+ let newPassword = opts.newPassword;
599
+ if (!newPassword) {
600
+ newPassword = await promptLine("New password: ", true);
601
+ const confirm = await promptLine("Confirm new password: ", true);
602
+ if (newPassword !== confirm) throw new Error("New passwords do not match.");
603
+ }
604
+ try {
605
+ printJson(
606
+ await changePasswordSafe(ctx, account, oldPassword, newPassword),
607
+ outputOptions(cmd2)
608
+ );
609
+ } catch (e) {
610
+ if (e instanceof HttpError && e.status === 401) {
611
+ throw new InputError("Wrong account or current password.");
612
+ }
613
+ if (e instanceof HttpError && e.status === 400) {
614
+ throw new InputError("New password must differ from the current one.");
615
+ }
616
+ throw e;
617
+ }
663
618
  });
664
619
  }
665
620
  function authCommand() {
@@ -670,6 +625,7 @@ function authCommand() {
670
625
 
671
626
  // src/commands/admin.ts
672
627
  var int = (v) => Number.parseInt(v, 10);
628
+ var DEFAULT_RESET_PASSWORD = "openbkn";
673
629
  function adminCommand() {
674
630
  const admin = new Command2("admin").description(
675
631
  "Operator CLI (kweaver-admin): org, user, role, models, audit"
@@ -802,12 +758,25 @@ function adminCommand() {
802
758
  user.command("delete <id>").description("Delete a user").action(async (id, _opts, cmd) => {
803
759
  printJson(await clientFrom(cmd).admin.userDelete(id), outputOptions(cmd));
804
760
  });
805
- user.command("reset-password [id]").description("Reset a user's password (RSA-encrypted in transit)").option("--id <userId>", "explicit user UUID (alt to the positional id)").option("--user <account>", "resolve the user by account / login name").option("--password <s>", "the new password").option("--new-password <s>", "the new password (alias of --password)").option("--prompt-password", "prompt for the new password interactively").option("-y, --yes", "skip confirmation").action(async (id, opts, cmd) => {
761
+ user.command("reset-password [id]").description("Reset a user's password (defaults to the platform initial password)").option("--id <userId>", "explicit user UUID (alt to the positional id)").option("--user <account>", "resolve the user by account / login name").option("--password <s>", "the new password (default: platform initial 'openbkn')").option("--new-password <s>", "the new password (alias of --password)").option("--prompt-password", "type the new password interactively (input hidden)").option("-y, --yes", "skip confirmation").action(async (id, opts, cmd) => {
806
762
  const userId = id ?? opts.id ?? opts.user;
807
- const pwd = opts.password ?? opts.newPassword;
808
763
  if (!userId) throw new Error("Provide a user id (positional or --id).");
809
- if (!pwd) throw new Error("Provide --password / --new-password.");
810
- printJson(await clientFrom(cmd).admin.userResetPassword(userId, pwd), outputOptions(cmd));
764
+ let explicit = opts.password ?? opts.newPassword;
765
+ if (!explicit && opts.promptPassword) {
766
+ explicit = await promptLine("New password: ", true);
767
+ if (!explicit) throw new InputError("No password entered.");
768
+ }
769
+ const pwd = explicit ?? DEFAULT_RESET_PASSWORD;
770
+ const r = await clientFrom(cmd).admin.userResetPassword(userId, pwd);
771
+ const out = outputOptions(cmd);
772
+ if (out.json || out.compact) printJson(r, out);
773
+ else if (explicit) process.stdout.write(`Password reset for ${userId}.
774
+ `);
775
+ else
776
+ process.stdout.write(
777
+ `Password reset for ${userId} to the initial password '${DEFAULT_RESET_PASSWORD}' (must change on next login).
778
+ `
779
+ );
811
780
  });
812
781
  const role = admin.command("role").description("Role management");
813
782
  role.command("list").description("List roles").option("--keyword <s>", "filter by keyword").option("--limit <n>", "page size", int, 100).option("--offset <n>", "page offset", int, 0).option("--source <s>", "role source filter (business | user)").action(async (opts, cmd) => {
@@ -840,6 +809,41 @@ function adminCommand() {
840
809
  outputOptions(cmd)
841
810
  );
842
811
  });
812
+ role.command("create").description("Create a custom role (bkn-safe; built-in roles are read-only)").requiredOption("--name <name>", "role name").option("--description <text>", "role description").action(async (opts, cmd) => {
813
+ printJson(
814
+ await clientFrom(cmd).admin.roleCreate(opts.name, opts.description),
815
+ outputOptions(cmd)
816
+ );
817
+ });
818
+ role.command("update <role>").description("Update a custom role's name/description (403 on built-in)").option("--name <name>", "new name").option("--description <text>", "new description").action(async (roleId, opts, cmd) => {
819
+ printJson(
820
+ await clientFrom(cmd).admin.roleUpdate(roleId, {
821
+ name: opts.name,
822
+ description: opts.description
823
+ }),
824
+ outputOptions(cmd)
825
+ );
826
+ });
827
+ role.command("delete <role>").description("Delete a custom role (403 on built-in)").option("-y, --yes", "skip confirmation").action(async (roleId, _opts, cmd) => {
828
+ printJson(await clientFrom(cmd).admin.roleDelete(roleId), outputOptions(cmd));
829
+ });
830
+ for (const [verb, grant] of [
831
+ ["grant-perm", true],
832
+ ["revoke-perm", false]
833
+ ]) {
834
+ 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) => {
835
+ printJson(
836
+ await clientFrom(cmd).admin.rolePermission(
837
+ roleId,
838
+ grant,
839
+ opts.resourceType,
840
+ opts.resourceId,
841
+ csv(opts.operations) ?? []
842
+ ),
843
+ outputOptions(cmd)
844
+ );
845
+ });
846
+ }
843
847
  const modelBody = (opts) => {
844
848
  if (opts.body || opts.bodyFile) return readBody(opts);
845
849
  const mc = {};
@@ -1071,11 +1075,78 @@ function agentCommand() {
1071
1075
  return group(cmd, "DECISION AGENT");
1072
1076
  }
1073
1077
 
1074
- // src/commands/bkn.ts
1078
+ // src/commands/appkey.ts
1075
1079
  import { Command as Command4 } from "commander";
1080
+ var int3 = (v) => Number.parseInt(v, 10);
1081
+ var DAY_MS = 864e5;
1082
+ function printNewKey(created, out) {
1083
+ if (out.json || out.compact) {
1084
+ printJson(created, out);
1085
+ return;
1086
+ }
1087
+ process.stderr.write(
1088
+ "\u26A0\uFE0F Copy this key now \u2014 the plaintext is shown only once and cannot be retrieved again.\n\n"
1089
+ );
1090
+ const fields = [
1091
+ ["key", created.key],
1092
+ ["name", created.name],
1093
+ ["id", `${created.id} (use this to revoke/regenerate)`],
1094
+ ["key_id", created.key_id],
1095
+ ["expires_at", created.expires_at ?? "never"]
1096
+ ];
1097
+ const w = Math.max(...fields.map(([k]) => k.length));
1098
+ for (const [k, v] of fields) process.stdout.write(` ${k.padEnd(w)} ${v}
1099
+ `);
1100
+ }
1101
+ function appkeyCommand() {
1102
+ const appkey = new Command4("appkey").description(
1103
+ "AppKeys \u2014 user-issued long-lived credentials (bak_) for the Context Loader"
1104
+ );
1105
+ appkey.command("list").description("List your own AppKeys (no secrets)").action(async (_opts, cmd) => {
1106
+ printJson(await clientFrom(cmd).appKeys.list(), outputOptions(cmd));
1107
+ });
1108
+ appkey.command("create").description("Issue an AppKey \u2014 the plaintext key is shown ONCE, on create").requiredOption("--name <s>", "display name (to tell keys apart)").option("--expires-at <rfc3339>", "expiry as RFC3339 (e.g. 2027-01-01T00:00:00Z)").option("--expire-days <n>", "expiry in N days from now (alternative to --expires-at)", int3).option("--never-expire", "never expire (wins over --expires-at/--expire-days)").action(async (opts, cmd) => {
1109
+ let expiresAt = opts.expiresAt;
1110
+ if (opts.expireDays !== void 0) {
1111
+ if (!Number.isFinite(opts.expireDays) || opts.expireDays <= 0) {
1112
+ throw new InputError("--expire-days must be a positive integer.");
1113
+ }
1114
+ if (expiresAt) {
1115
+ throw new InputError("Use either --expires-at or --expire-days, not both.");
1116
+ }
1117
+ expiresAt = new Date(Date.now() + opts.expireDays * DAY_MS).toISOString();
1118
+ }
1119
+ const created = await clientFrom(cmd).appKeys.create({
1120
+ name: opts.name,
1121
+ expiresAt,
1122
+ neverExpire: Boolean(opts.neverExpire)
1123
+ });
1124
+ printNewKey(created, outputOptions(cmd));
1125
+ });
1126
+ appkey.command("regenerate <id>").alias("rotate").description("Rotate a key in place \u2014 mints a new plaintext; the old bak_ dies immediately").action(async (id, _opts, cmd) => {
1127
+ const created = await clientFrom(cmd).appKeys.regenerate(id);
1128
+ printNewKey(created, outputOptions(cmd));
1129
+ });
1130
+ appkey.command("revoke <id>").alias("delete").alias("rm").description("Revoke one of your AppKeys (immediate; use the `id`, not `key_id`)").action(async (id, _opts, cmd) => {
1131
+ await clientFrom(cmd).appKeys.revoke(id);
1132
+ printJson({ revoked: id }, outputOptions(cmd));
1133
+ });
1134
+ const admin = appkey.command("admin").description("Admin governance over all AppKeys");
1135
+ admin.command("list").description("List all AppKeys, or one owner's (adds owner_user_id)").option("--owner-id <id>", "filter to a single owner").action(async (opts, cmd) => {
1136
+ printJson(await clientFrom(cmd).appKeys.adminList(opts.ownerId), outputOptions(cmd));
1137
+ });
1138
+ admin.command("revoke <id>").alias("delete").alias("rm").description("Revoke any AppKey by id").action(async (id, _opts, cmd) => {
1139
+ await clientFrom(cmd).appKeys.adminRevoke(id);
1140
+ printJson({ revoked: id }, outputOptions(cmd));
1141
+ });
1142
+ return group(appkey, "AUTHENTICATION & CONFIG");
1143
+ }
1144
+
1145
+ // src/commands/bkn.ts
1146
+ import { Command as Command5 } from "commander";
1076
1147
 
1077
1148
  // src/utils/bkn-validate.ts
1078
- import { existsSync, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
1149
+ import { existsSync, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
1079
1150
  import { join, resolve } from "path";
1080
1151
  var BKN_OBJECT_NAME_MAX_LENGTH = 40;
1081
1152
  function parseFrontmatter(text) {
@@ -1128,7 +1199,7 @@ function validateBknDirectory(dirPath) {
1128
1199
  if (!existsSync(networkPath)) {
1129
1200
  errors.push("Missing network.bkn at the BKN root.");
1130
1201
  } else {
1131
- const fm = parseFrontmatter(readFileSync3(networkPath, "utf8"));
1202
+ const fm = parseFrontmatter(readFileSync2(networkPath, "utf8"));
1132
1203
  if (!fm) errors.push("network.bkn has no frontmatter block.");
1133
1204
  else {
1134
1205
  if (fm.type !== "knowledge_network")
@@ -1140,7 +1211,7 @@ function validateBknDirectory(dirPath) {
1140
1211
  const otIds = /* @__PURE__ */ new Set();
1141
1212
  const otFiles = bknFiles(join(dir, "object_types"));
1142
1213
  for (const file of otFiles) {
1143
- const fm = parseFrontmatter(readFileSync3(file, "utf8"));
1214
+ const fm = parseFrontmatter(readFileSync2(file, "utf8"));
1144
1215
  const rel = file.slice(dir.length + 1);
1145
1216
  if (!fm || fm.type !== "object_type") {
1146
1217
  errors.push(`${rel}: not a valid object_type (missing/wrong frontmatter type).`);
@@ -1160,7 +1231,7 @@ function validateBknDirectory(dirPath) {
1160
1231
  }
1161
1232
  const rtFiles = bknFiles(join(dir, "relation_types"));
1162
1233
  for (const file of rtFiles) {
1163
- const text = readFileSync3(file, "utf8");
1234
+ const text = readFileSync2(file, "utf8");
1164
1235
  const fm = parseFrontmatter(text);
1165
1236
  const rel = file.slice(dir.length + 1);
1166
1237
  if (!fm || fm.type !== "relation_type") {
@@ -1190,10 +1261,10 @@ function validateBknDirectory(dirPath) {
1190
1261
  }
1191
1262
 
1192
1263
  // src/commands/bkn.ts
1193
- var int3 = (v) => Number.parseInt(v, 10);
1264
+ var int4 = (v) => Number.parseInt(v, 10);
1194
1265
  function bknCommand() {
1195
- const bkn = new Command4("bkn").description("Knowledge networks \u2014 list, query, schema, instances");
1196
- 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) => {
1266
+ const bkn = new Command5("bkn").description("Knowledge networks \u2014 list, query, schema, instances");
1267
+ bkn.command("list").description("List knowledge networks").option("--limit <n>", "page size", int4, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int4, 0).option("--name-pattern <s>", "filter by name pattern").option("--tag <s>", "filter by tag").option("--sort <field>", "sort field", "update_time").option("--direction <dir>", "asc | desc", "desc").action(async (_opts, cmd) => {
1197
1268
  const o = cmd.optsWithGlobals();
1198
1269
  const data = await clientFrom(cmd).kn.list({
1199
1270
  limit: o.limit,
@@ -1212,7 +1283,7 @@ function bknCommand() {
1212
1283
  });
1213
1284
  printJson(data, outputOptions(cmd));
1214
1285
  });
1215
- bkn.command("search <kn-id> <query>").description("Semantic search within a knowledge network").option("--max-concepts <n>", "max concepts to return", int3, 10).option("--mode <mode>", "retrieval mode", "keyword_vector_retrieval").action(async (knId, query, opts, cmd) => {
1286
+ bkn.command("search <kn-id> <query>").description("Semantic search within a knowledge network").option("--max-concepts <n>", "max concepts to return", int4, 10).option("--mode <mode>", "retrieval mode", "keyword_vector_retrieval").action(async (knId, query, opts, cmd) => {
1216
1287
  const data = await clientFrom(cmd).kn.search(knId, query, {
1217
1288
  maxConcepts: opts.maxConcepts,
1218
1289
  mode: opts.mode
@@ -1301,7 +1372,7 @@ function bknCommand() {
1301
1372
  printJson(await clientFrom(cmd).kn.subgraph(knId, readBody(opts)), outputOptions(cmd));
1302
1373
  });
1303
1374
  const actionLog = bkn.command("action-log").description("Action logs \u2014 list/get/cancel");
1304
- 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) => {
1375
+ actionLog.command("list <kn-id>").description("List action logs").option("--status <s>", "filter by status").option("--action-type-id <id>", "filter by action type").option("--limit <n>", "page size", int4, DEFAULT_LIST_LIMIT).action(async (knId, opts, cmd) => {
1305
1376
  printJson(
1306
1377
  await clientFrom(cmd).kn.actionLogs(knId, {
1307
1378
  status: opts.status,
@@ -1481,7 +1552,7 @@ function bknCommand() {
1481
1552
  printJson(result, outputOptions(cmd));
1482
1553
  if (!result.valid) process.exitCode = 1;
1483
1554
  });
1484
- bkn.command("create-from-csv <catalog-id>").description("Import CSV files into a Vega catalog, then build a KN from them").requiredOption("--files <glob>", "CSV paths (comma-separated or glob)").requiredOption("--name <name>", "knowledge network name").option("--table-prefix <s>", "prefix for derived table names", "").option("--batch-size <n>", "rows per insert batch", int3, 500).option("--tables <list>", "subset of imported tables to include in the KN").option("--pk-map <map>", "explicit primary keys: '<table>:<col>[,...]'").option("--build", "submit a Vega build task per resource after creation").option(
1555
+ bkn.command("create-from-csv <catalog-id>").description("Import CSV files into a Vega catalog, then build a KN from them").requiredOption("--files <glob>", "CSV paths (comma-separated or glob)").requiredOption("--name <name>", "knowledge network name").option("--table-prefix <s>", "prefix for derived table names", "").option("--batch-size <n>", "rows per insert batch", int4, 500).option("--tables <list>", "subset of imported tables to include in the KN").option("--pk-map <map>", "explicit primary keys: '<table>:<col>[,...]'").option("--build", "submit a Vega build task per resource after creation").option(
1485
1556
  "--embedding-fields <map>",
1486
1557
  "columns to vectorize per table (with --build): '<table>:<col>[+<col>...][,...]'"
1487
1558
  ).option("--embedding-model <id>", "embedding model id for the vector index (with --build)").option("--no-rollback", "keep a partially-created KN on failure").action(async (catalogId, opts, cmd) => {
@@ -1507,13 +1578,13 @@ function bknCommand() {
1507
1578
  }
1508
1579
 
1509
1580
  // src/commands/call.ts
1510
- import { Command as Command5 } from "commander";
1581
+ import { Command as Command6 } from "commander";
1511
1582
  function collect(value, prev) {
1512
1583
  prev.push(value);
1513
1584
  return prev;
1514
1585
  }
1515
1586
  function callCommand() {
1516
- const cmd = new Command5("call").alias("curl").description("Call an API with curl-style flags and auto-injected auth headers").argument("<url>", "API path (e.g. /api/...) or absolute URL").option("-X, --request <method>", "HTTP method").option("-H, --header <header>", 'extra header "Name: value" (repeatable)', collect, []).option("-d, --data <body>", "request body (sets JSON content-type if unset)").option("--data-raw <body>", "alias for --data").option(
1587
+ const cmd = new Command6("call").alias("curl").description("Call an API with curl-style flags and auto-injected auth headers").argument("<url>", "API path (e.g. /api/...) or absolute URL").option("-X, --request <method>", "HTTP method").option("-H, --header <header>", 'extra header "Name: value" (repeatable)', collect, []).option("-d, --data <body>", "request body (sets JSON content-type if unset)").option("--data-raw <body>", "alias for --data").option(
1517
1588
  "-F, --form <field>",
1518
1589
  "multipart field key=value or key=@file (repeatable)",
1519
1590
  collect,
@@ -1548,14 +1619,14 @@ function callCommand() {
1548
1619
  }
1549
1620
 
1550
1621
  // src/commands/config.ts
1551
- import { Command as Command6 } from "commander";
1622
+ import { Command as Command7 } from "commander";
1552
1623
  function requireActive() {
1553
1624
  const baseUrl = activePlatform();
1554
1625
  if (!baseUrl) throw new InputError("No active platform. Run `openbkn auth login <url>` first.");
1555
1626
  return baseUrl;
1556
1627
  }
1557
1628
  function configCommand() {
1558
- const config = new Command6("config").description("Per-platform CLI configuration");
1629
+ const config = new Command7("config").description("Per-platform CLI configuration");
1559
1630
  config.command("show").description("Show the active platform and business domain").action((_opts, cmd) => {
1560
1631
  const baseUrl = activePlatform();
1561
1632
  printJson(
@@ -1588,13 +1659,56 @@ function configCommand() {
1588
1659
  }
1589
1660
 
1590
1661
  // src/commands/context.ts
1591
- import { Command as Command7 } from "commander";
1592
- var int4 = (v) => Number.parseInt(v, 10);
1662
+ import { Command as Command8 } from "commander";
1663
+ var int5 = (v) => Number.parseInt(v, 10);
1664
+ var collectArg = (v, prev) => {
1665
+ prev.push(v);
1666
+ return prev;
1667
+ };
1668
+ function buildArgs(opts) {
1669
+ let out = {};
1670
+ if (opts.args) {
1671
+ try {
1672
+ out = JSON.parse(opts.args);
1673
+ } catch {
1674
+ throw new InputError("--args must be valid JSON");
1675
+ }
1676
+ }
1677
+ for (const pair of opts.arg ?? []) {
1678
+ const idx = pair.indexOf("=");
1679
+ if (idx <= 0) throw new InputError(`--arg must be key=value (got: ${pair})`);
1680
+ const key = pair.slice(0, idx);
1681
+ const raw = pair.slice(idx + 1);
1682
+ try {
1683
+ out[key] = JSON.parse(raw);
1684
+ } catch {
1685
+ out[key] = raw;
1686
+ }
1687
+ }
1688
+ return out;
1689
+ }
1690
+ function printToolList(res, out) {
1691
+ if (out.json || out.compact) {
1692
+ printJson(res, out);
1693
+ return;
1694
+ }
1695
+ const r = res ?? {};
1696
+ const arr = [res, r.tools, r.result?.tools, r.data].find(Array.isArray);
1697
+ if (!arr) {
1698
+ printJson(res, out);
1699
+ return;
1700
+ }
1701
+ const rows = arr.map((t) => ({
1702
+ name: t.name ?? t.tool_name ?? t.key ?? "",
1703
+ description: typeof t.description === "string" ? t.description : ""
1704
+ }));
1705
+ printJson(rows, out);
1706
+ }
1593
1707
  function contextCommand() {
1594
- const cmd = new Command7("context").description(
1708
+ const cmd = new Command8("context").description(
1595
1709
  "Context loader (MCP) \u2014 schema discovery, instance query, skill recall"
1596
1710
  );
1597
- cmd.command("search-schema <kn-id> <query>").description("Search object/relation/action/metric schemas").option("--scope <list>", "comma-separated scopes (object,relation,action,metric)").option("--max <n>", "max concepts", int4).action(async (knId, query, opts, cmd2) => {
1711
+ cmd.command("search-schema <kn-id> <query>").description("Search object/relation/action/metric schemas").option("--scope <list>", "comma-separated scopes (object,relation,action,metric)").option("--max <n>", "max concepts", int5).action(async (knId, query, opts, cmd2) => {
1598
1712
  const data = await clientFrom(cmd2).context.searchSchema(knId, query, {
1599
1713
  searchScope: opts.scope ? String(opts.scope).split(",") : void 0,
1600
1714
  maxConcepts: opts.max
@@ -1610,23 +1724,41 @@ function contextCommand() {
1610
1724
  }
1611
1725
  printJson(await clientFrom(cmd2).context.queryObjectInstance(knId, args), outputOptions(cmd2));
1612
1726
  });
1613
- 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) => {
1727
+ cmd.command("find-skills <kn-id> <object-type-id>").description("Recall skills for an object type").option("--top-k <n>", "max skills (1-20)", int5).action(async (knId, otId, opts, cmd2) => {
1614
1728
  printJson(
1615
1729
  await clientFrom(cmd2).context.findSkills(knId, otId, opts.topK),
1616
1730
  outputOptions(cmd2)
1617
1731
  );
1618
1732
  });
1619
- cmd.command("tools <kn-id>").description("List MCP tools").action(async (knId, _opts, cmd2) => {
1620
- printJson(await clientFrom(cmd2).context.tools(knId), outputOptions(cmd2));
1733
+ cmd.command("info").description("List the deploy's MCP tool catalog (global \u2014 no KN needed)").action(async (_opts, cmd2) => {
1734
+ printToolList(await clientFrom(cmd2).context.info(), outputOptions(cmd2));
1621
1735
  });
1622
- cmd.command("tool-call <kn-id> <name>").description("Call any MCP tool directly (--args JSON)").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, name, opts, cmd2) => {
1623
- let args;
1624
- try {
1625
- args = JSON.parse(opts.args);
1626
- } catch {
1627
- throw new InputError("--args must be valid JSON");
1628
- }
1629
- printJson(await clientFrom(cmd2).context.toolCall(knId, name, args), outputOptions(cmd2));
1736
+ cmd.command("tools <kn-id>").description("List MCP tools advertised for a KN session").action(async (knId, _opts, cmd2) => {
1737
+ printToolList(await clientFrom(cmd2).context.tools(knId), outputOptions(cmd2));
1738
+ });
1739
+ cmd.command("tool-call <kn-id> <name>").description("Call any MCP tool by name \u2014 current or future (use `tools` to discover)").option("--args <json>", "tool arguments as JSON").option(
1740
+ "--arg <key=value>",
1741
+ "one argument (repeatable; value parsed as JSON, else string)",
1742
+ collectArg,
1743
+ []
1744
+ ).action(async (knId, name, opts, cmd2) => {
1745
+ printJson(
1746
+ await clientFrom(cmd2).context.toolCall(knId, name, buildArgs(opts)),
1747
+ outputOptions(cmd2)
1748
+ );
1749
+ });
1750
+ cmd.command("call-method <kn-id> <method>").description(
1751
+ "Call any MCP method by name (e.g. tools/list, resources/read) \u2014 current or future"
1752
+ ).option("--args <json>", "method params as JSON").option(
1753
+ "--arg <key=value>",
1754
+ "one param (repeatable; value parsed as JSON, else string)",
1755
+ collectArg,
1756
+ []
1757
+ ).action(async (knId, method, opts, cmd2) => {
1758
+ printJson(
1759
+ await clientFrom(cmd2).context.callMethod(knId, method, buildArgs(opts)),
1760
+ outputOptions(cmd2)
1761
+ );
1630
1762
  });
1631
1763
  cmd.command("resources <kn-id>").description("List MCP resources").action(async (knId, _opts, cmd2) => {
1632
1764
  printJson(await clientFrom(cmd2).context.resources(knId), outputOptions(cmd2));
@@ -1658,19 +1790,19 @@ function contextCommand() {
1658
1790
  throw new InputError("--args must be valid JSON");
1659
1791
  }
1660
1792
  };
1661
- cmd.command("query-instance-subgraph <kn-id>").description("Layer-2: query an instance subgraph across relation-type paths").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, opts, cmd2) => {
1793
+ cmd.command("query-instance-subgraph <kn-id>").description("Query an instance subgraph across relation-type paths").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, opts, cmd2) => {
1662
1794
  printJson(
1663
1795
  await clientFrom(cmd2).context.queryInstanceSubgraph(knId, jsonArgs(opts.args)),
1664
1796
  outputOptions(cmd2)
1665
1797
  );
1666
1798
  });
1667
- cmd.command("get-logic-properties <kn-id>").description("Layer-3: compute logic-property values for instances").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, opts, cmd2) => {
1799
+ cmd.command("get-logic-properties <kn-id>").description("Compute logic-property values for instances").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, opts, cmd2) => {
1668
1800
  printJson(
1669
1801
  await clientFrom(cmd2).context.logicProperties(knId, jsonArgs(opts.args)),
1670
1802
  outputOptions(cmd2)
1671
1803
  );
1672
1804
  });
1673
- cmd.command("get-action-info <kn-id>").description("Layer-3: fetch action info / dynamic tools for an instance").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, opts, cmd2) => {
1805
+ cmd.command("get-action-info <kn-id>").description("Fetch action info / dynamic tools for an instance").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, opts, cmd2) => {
1674
1806
  printJson(
1675
1807
  await clientFrom(cmd2).context.actionInfo(knId, jsonArgs(opts.args)),
1676
1808
  outputOptions(cmd2)
@@ -1680,10 +1812,10 @@ function contextCommand() {
1680
1812
  }
1681
1813
 
1682
1814
  // src/commands/dataflow.ts
1683
- import { Command as Command8 } from "commander";
1684
- var int5 = (v) => Number.parseInt(v, 10);
1815
+ import { Command as Command9 } from "commander";
1816
+ var int6 = (v) => Number.parseInt(v, 10);
1685
1817
  function dataflowCommand() {
1686
- const cmd = new Command8("dataflow").description("Dataflow document workflows \u2014 list, runs, logs");
1818
+ const cmd = new Command9("dataflow").description("Dataflow document workflows \u2014 list, runs, logs");
1687
1819
  cmd.command("list").description("List all dataflows").action(async (_opts, cmd2) => {
1688
1820
  printJson(await clientFrom(cmd2).dataflows.list(), outputOptions(cmd2));
1689
1821
  });
@@ -1693,7 +1825,7 @@ function dataflowCommand() {
1693
1825
  outputOptions(cmd2)
1694
1826
  );
1695
1827
  });
1696
- cmd.command("logs <dagId> <instanceId>").description("Show logs for one run").option("--page <n>", "page", int5, 0).option("--limit <n>", "page size", int5, DEFAULT_LIST_LIMIT).action(async (dagId, instanceId, opts, cmd2) => {
1828
+ cmd.command("logs <dagId> <instanceId>").description("Show logs for one run").option("--page <n>", "page", int6, 0).option("--limit <n>", "page size", int6, DEFAULT_LIST_LIMIT).action(async (dagId, instanceId, opts, cmd2) => {
1697
1829
  printJson(
1698
1830
  await clientFrom(cmd2).dataflows.logs(dagId, instanceId, {
1699
1831
  page: opts.page,
@@ -1754,9 +1886,9 @@ function dataflowCommand() {
1754
1886
  }
1755
1887
 
1756
1888
  // src/commands/explore.ts
1757
- import { createServer as createServer2 } from "http";
1758
- import { Command as Command9 } from "commander";
1759
- var int6 = (v) => Number.parseInt(v, 10);
1889
+ import { createServer } from "http";
1890
+ import { Command as Command10 } from "commander";
1891
+ var int7 = (v) => Number.parseInt(v, 10);
1760
1892
  var ROUTES = {
1761
1893
  "GET /api/bkn/meta": (c, q) => c.kn.get(req(q, "knId")),
1762
1894
  "POST /api/bkn/search": (c, _q, b) => c.kn.search(str(b.knId), str(b.query), {
@@ -1801,12 +1933,12 @@ var INDEX = `<!doctype html><meta charset="utf-8"><title>openbkn explore</title>
1801
1933
  <p>Read-only JSON endpoints for bkn + vega:</p>
1802
1934
  <ul>${Object.keys(ROUTES).map((r) => `<li><code>${r}</code></li>`).join("")}</ul>`;
1803
1935
  function exploreCommand() {
1804
- const cmd = new Command9("explore").description(
1936
+ const cmd = new Command10("explore").description(
1805
1937
  "Start a local web server with read-only bkn + vega JSON endpoints"
1806
1938
  );
1807
- cmd.option("--port <n>", "port to listen on", int6, 7777).option("--host <h>", "host to bind", "127.0.0.1").action(async (opts, command) => {
1939
+ cmd.option("--port <n>", "port to listen on", int7, 7777).option("--host <h>", "host to bind", "127.0.0.1").action(async (opts, command) => {
1808
1940
  const client = clientFrom(command);
1809
- const server = createServer2((reqMsg, res) => {
1941
+ const server = createServer((reqMsg, res) => {
1810
1942
  void handle(client, reqMsg, res);
1811
1943
  });
1812
1944
  server.listen(opts.port, opts.host, () => {
@@ -1842,8 +1974,8 @@ async function handle(client, reqMsg, res) {
1842
1974
  }
1843
1975
 
1844
1976
  // src/commands/model.ts
1845
- import { Command as Command10 } from "commander";
1846
- var int7 = (v) => Number.parseInt(v, 10);
1977
+ import { Command as Command11 } from "commander";
1978
+ var int8 = (v) => Number.parseInt(v, 10);
1847
1979
  function addManagementCommands(parent, kind) {
1848
1980
  parent.command("add").description("Register a model (definition JSON via --body / --body-file)").option("--body <json>", "model definition JSON").option("--body-file <path>", "read model definition JSON from a file").action(async (opts, cmd) => {
1849
1981
  printJson(await clientFrom(cmd).models[kind].add(readBody(opts)), outputOptions(cmd));
@@ -1859,9 +1991,9 @@ function addManagementCommands(parent, kind) {
1859
1991
  });
1860
1992
  }
1861
1993
  function modelCommand() {
1862
- const model = new Command10("model").description("Model factory \u2014 LLM / small-model CRUD + chat");
1994
+ const model = new Command11("model").description("Model factory \u2014 LLM / small-model CRUD + chat");
1863
1995
  const llm = model.command("llm").description("Large language models");
1864
- llm.command("list").description("List LLM models").option("--name <s>", "filter by name").option("--type <t>", "model type filter").option("--limit <n>", "page size", int7, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int7, 1).action(async (opts, cmd) => {
1996
+ llm.command("list").description("List LLM models").option("--name <s>", "filter by name").option("--type <t>", "model type filter").option("--limit <n>", "page size", int8, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int8, 1).action(async (opts, cmd) => {
1865
1997
  printJson(
1866
1998
  await clientFrom(cmd).models.llm.list({
1867
1999
  name: opts.name,
@@ -1886,7 +2018,7 @@ function modelCommand() {
1886
2018
  });
1887
2019
  addManagementCommands(llm, "llm");
1888
2020
  const small = model.command("small").description("Small models (embedding / reranker)");
1889
- small.command("list").description("List small models").option("--name <s>", "filter by name").option("--type <t>", "model type filter").option("--limit <n>", "page size", int7, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int7, 1).action(async (opts, cmd) => {
2021
+ small.command("list").description("List small models").option("--name <s>", "filter by name").option("--type <t>", "model type filter").option("--limit <n>", "page size", int8, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int8, 1).action(async (opts, cmd) => {
1890
2022
  printJson(
1891
2023
  await clientFrom(cmd).models.small.list({
1892
2024
  name: opts.name,
@@ -1917,11 +2049,11 @@ function modelCommand() {
1917
2049
  }
1918
2050
 
1919
2051
  // src/commands/resource.ts
1920
- import { Command as Command11 } from "commander";
1921
- var int8 = (v) => Number.parseInt(v, 10);
2052
+ import { Command as Command12 } from "commander";
2053
+ var int9 = (v) => Number.parseInt(v, 10);
1922
2054
  function resourceCommand() {
1923
- const cmd = new Command11("resource").alias("res").description("Resources \u2014 list, find, get, query, delete");
1924
- cmd.command("list").description("List resources under a catalog").option("--catalog-id <id>", "filter by catalog id").option("--datasource-id <id>", "alias of --catalog-id").option("--category <c>", "resource category (table | logicview | dataset)").option("--type <c>", "alias of --category").option("--limit <n>", "page size", int8, DEFAULT_LIST_LIMIT).action(async (opts, cmd2) => {
2055
+ const cmd = new Command12("resource").alias("res").description("Resources \u2014 list, find, get, query, delete");
2056
+ cmd.command("list").description("List resources under a catalog").option("--catalog-id <id>", "filter by catalog id").option("--datasource-id <id>", "alias of --catalog-id").option("--category <c>", "resource category (table | logicview | dataset)").option("--type <c>", "alias of --category").option("--limit <n>", "page size", int9, DEFAULT_LIST_LIMIT).action(async (opts, cmd2) => {
1925
2057
  const data = await clientFrom(cmd2).resource.list({
1926
2058
  datasourceId: opts.catalogId ?? opts.datasourceId,
1927
2059
  category: opts.category ?? opts.type,
@@ -1939,7 +2071,7 @@ function resourceCommand() {
1939
2071
  cmd.command("get <id>").description("Get resource details").action(async (id, _opts, cmd2) => {
1940
2072
  printJson(await clientFrom(cmd2).resource.get(id), outputOptions(cmd2));
1941
2073
  });
1942
- cmd.command("query <id>").description("Fetch data rows from a resource").option("--limit <n>", "row limit", int8, DEFAULT_QUERY_LIMIT).option("--offset <n>", "row offset", int8, 0).option("--need-total", "include total count").action(async (id, opts, cmd2) => {
2074
+ cmd.command("query <id>").description("Fetch data rows from a resource").option("--limit <n>", "row limit", int9, DEFAULT_QUERY_LIMIT).option("--offset <n>", "row offset", int9, 0).option("--need-total", "include total count").action(async (id, opts, cmd2) => {
1943
2075
  const data = await clientFrom(cmd2).resource.query(id, {
1944
2076
  limit: opts.limit,
1945
2077
  offset: opts.offset,
@@ -1954,11 +2086,11 @@ function resourceCommand() {
1954
2086
  }
1955
2087
 
1956
2088
  // src/commands/skill.ts
1957
- import { Command as Command12 } from "commander";
1958
- var int9 = (v) => Number.parseInt(v, 10);
2089
+ import { Command as Command13 } from "commander";
2090
+ var int10 = (v) => Number.parseInt(v, 10);
1959
2091
  function skillCommand() {
1960
- const cmd = new Command12("skill").description("Skill registry and market");
1961
- 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", int9, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int9, 1);
2092
+ const cmd = new Command13("skill").description("Skill registry and market");
2093
+ const listOpts = (c) => c.option("--name <s>", "filter by name").option("--source <s>", "filter by source").option("--status <s>", "filter by status").option("--limit <n>", "page size", int10, DEFAULT_LIST_LIMIT).option("--page <n>", "page", int10, 1);
1962
2094
  listOpts(cmd.command("list").description("List skills")).option("--create-user <s>", "filter by creator").action(async (opts, cmd2) => {
1963
2095
  printJson(
1964
2096
  await clientFrom(cmd2).skills.list({
@@ -2044,11 +2176,11 @@ function skillCommand() {
2044
2176
  }
2045
2177
 
2046
2178
  // src/commands/toolbox.ts
2047
- import { Command as Command13 } from "commander";
2048
- var int10 = (v) => Number.parseInt(v, 10);
2179
+ import { Command as Command14 } from "commander";
2180
+ var int11 = (v) => Number.parseInt(v, 10);
2049
2181
  function toolboxCommand() {
2050
- const cmd = new Command13("toolbox").description("Agent toolbox lifecycle");
2051
- cmd.command("list").description("List toolboxes").option("--keyword <s>", "filter by keyword").option("--limit <n>", "page size", int10, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int10, 0).action(async (opts, cmd2) => {
2182
+ const cmd = new Command14("toolbox").description("Agent toolbox lifecycle");
2183
+ cmd.command("list").description("List toolboxes").option("--keyword <s>", "filter by keyword").option("--limit <n>", "page size", int11, DEFAULT_LIST_LIMIT).option("--offset <n>", "page offset", int11, 0).action(async (opts, cmd2) => {
2052
2184
  printJson(
2053
2185
  await clientFrom(cmd2).toolboxes.list({
2054
2186
  keyword: opts.keyword,
@@ -2089,7 +2221,7 @@ function toolboxCommand() {
2089
2221
  return group(cmd, "DECISION AGENT");
2090
2222
  }
2091
2223
  function toolCommand() {
2092
- const cmd = new Command13("tool").description("Tools inside a toolbox");
2224
+ const cmd = new Command14("tool").description("Tools inside a toolbox");
2093
2225
  cmd.command("list").description("List tools in a toolbox").requiredOption("--toolbox <box-id>", "toolbox id").action(async (opts, cmd2) => {
2094
2226
  printJson(await clientFrom(cmd2).toolboxes.tools(opts.toolbox), outputOptions(cmd2));
2095
2227
  });
@@ -2105,7 +2237,7 @@ function toolCommand() {
2105
2237
  outputOptions(cmd2)
2106
2238
  );
2107
2239
  });
2108
- const invokeOpts = (c) => c.requiredOption("--toolbox <box-id>", "toolbox id").option("--body <json>", "request body JSON").option("--header <json>", "headers map JSON").option("--query <json>", "query params JSON").option("--path <json>", "path params JSON").option("--timeout <s>", "per-call timeout seconds", int10);
2240
+ const invokeOpts = (c) => c.requiredOption("--toolbox <box-id>", "toolbox id").option("--body <json>", "request body JSON").option("--header <json>", "headers map JSON").option("--query <json>", "query params JSON").option("--path <json>", "path params JSON").option("--timeout <s>", "per-call timeout seconds", int11);
2109
2241
  const parseJson = (s, label) => {
2110
2242
  if (!s) return void 0;
2111
2243
  try {
@@ -2147,11 +2279,11 @@ function toolCommand() {
2147
2279
  }
2148
2280
 
2149
2281
  // src/commands/trace.ts
2150
- import { readFileSync as readFileSync5, writeFileSync } from "fs";
2151
- import { Command as Command14 } from "commander";
2282
+ import { readFileSync as readFileSync4, writeFileSync } from "fs";
2283
+ import { Command as Command15 } from "commander";
2152
2284
 
2153
2285
  // src/trace-ai/schema-validate.ts
2154
- import { readFileSync as readFileSync4 } from "fs";
2286
+ import { readFileSync as readFileSync3 } from "fs";
2155
2287
  import { extname } from "path";
2156
2288
  import yaml from "js-yaml";
2157
2289
  import { z } from "zod";
@@ -2188,7 +2320,7 @@ var DiagnosisRule = z.object({
2188
2320
  params: z.record(z.string(), z.unknown()).optional()
2189
2321
  });
2190
2322
  function parseFile(file) {
2191
- const text = readFileSync4(file, "utf8");
2323
+ const text = readFileSync3(file, "utf8");
2192
2324
  const ext = extname(file).toLowerCase();
2193
2325
  if (ext === ".yaml" || ext === ".yml") return yaml.load(text);
2194
2326
  return JSON.parse(text);
@@ -2226,7 +2358,7 @@ function validateSchemaFile(file, kind) {
2226
2358
 
2227
2359
  // src/commands/trace.ts
2228
2360
  function traceCommand() {
2229
- const cmd = new Command14("trace").description(
2361
+ const cmd = new Command15("trace").description(
2230
2362
  "Trace AI \u2014 fetch spans, diagnose (symbolic + LLM rubric), scan, eval-set, schema validate"
2231
2363
  );
2232
2364
  cmd.command("get <conversation-id>").description("Fetch all trace spans for a conversation").option("--max-spans <n>", "max spans", (v) => Number.parseInt(v, 10)).action(async (conversationId, opts, cmd2) => {
@@ -2255,7 +2387,7 @@ function traceCommand() {
2255
2387
  });
2256
2388
  const evalSet = cmd.command("eval-set").description("Build + run trace eval sets");
2257
2389
  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) => {
2258
- const raw = JSON.parse(readFileSync5(queriesFile, "utf8"));
2390
+ const raw = JSON.parse(readFileSync4(queriesFile, "utf8"));
2259
2391
  const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
2260
2392
  if (opts.out) {
2261
2393
  writeFileSync(opts.out, JSON.stringify({ cases }, null, 2));
@@ -2265,7 +2397,7 @@ function traceCommand() {
2265
2397
  }
2266
2398
  });
2267
2399
  evalSet.command("test <cases-file>").description("Run an eval set against an agent (--llm enables semantic_match)").requiredOption("--agent <id>", "agent id to run the queries against").option("--version <v>", "agent version", "v0").option("--llm", "enable semantic_match assertions via the local `claude` CLI").action(async (casesFile, opts, cmd2) => {
2268
- const raw = JSON.parse(readFileSync5(casesFile, "utf8"));
2400
+ const raw = JSON.parse(readFileSync4(casesFile, "utf8"));
2269
2401
  const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
2270
2402
  const result = await clientFrom(cmd2).trace.evalSetTest(opts.agent, cases, {
2271
2403
  version: opts.version,
@@ -2284,9 +2416,10 @@ function traceCommand() {
2284
2416
  }
2285
2417
 
2286
2418
  // src/commands/vega.ts
2287
- import { Command as Command15 } from "commander";
2419
+ import { Command as Command16 } from "commander";
2420
+ var int12 = (v) => Number.parseInt(v, 10);
2288
2421
  function vegaCommand() {
2289
- const vega = new Command15("vega").description(
2422
+ const vega = new Command16("vega").description(
2290
2423
  "Vega observability \u2014 catalog, resources, index build tasks"
2291
2424
  );
2292
2425
  const catalog = vega.command("catalog").description("Catalog entries");
@@ -2339,6 +2472,38 @@ function vegaCommand() {
2339
2472
  connector.command("get <type>").description("Get a connector type").action(async (type, _opts, cmd) => {
2340
2473
  printJson(await clientFrom(cmd).vega.connectorType(type), outputOptions(cmd));
2341
2474
  });
2475
+ vega.command("sql").description("Run SQL / OpenSearch DSL directly against a vega-backend data source").option(
2476
+ "--resource-type <type>",
2477
+ "source type (mysql | postgresql | opensearch | \u2026); optional \u2014 inferred from the {{<id>}} placeholder"
2478
+ ).option(
2479
+ "--query <sql>",
2480
+ "SQL string; reference a resource with a {{<resource-id>}} placeholder"
2481
+ ).option("--stream-size <n>", "streaming batch size (100\u201310000)", int12).option("--query-timeout <s>", "query timeout in seconds (1\u20133600)", int12).option(
2482
+ "-d, --data <json>",
2483
+ "full request body as JSON (advanced; wins over --query/--resource-type)"
2484
+ ).action(async (opts, cmd) => {
2485
+ let body;
2486
+ if (opts.data) {
2487
+ try {
2488
+ body = JSON.parse(opts.data);
2489
+ } catch {
2490
+ throw new InputError("--data must be valid JSON");
2491
+ }
2492
+ } else {
2493
+ if (!opts.query) {
2494
+ throw new InputError("Provide --query (and optionally --resource-type), or --data.");
2495
+ }
2496
+ body = {
2497
+ query: opts.query,
2498
+ // Optional — the backend infers the type from the {{<id>}} placeholder's
2499
+ // catalog connector when omitted.
2500
+ ...opts.resourceType ? { resource_type: opts.resourceType } : {},
2501
+ ...opts.streamSize !== void 0 ? { stream_size: opts.streamSize } : {},
2502
+ ...opts.queryTimeout !== void 0 ? { query_timeout: opts.queryTimeout } : {}
2503
+ };
2504
+ }
2505
+ printJson(await clientFrom(cmd).vega.sql(body), outputOptions(cmd));
2506
+ });
2342
2507
  const resource = vega.command("resource").description("Vega-backend resources");
2343
2508
  resource.command("list").description("List resources").option("--datasource-id <id>", "filter by catalog/datasource id").option("--catalog-id <id>", "alias of --datasource-id").option("--type <category>", "resource category").option("--category <category>", "alias of --type").option("--limit <n>", "page size", (v) => Number.parseInt(v, 10), DEFAULT_LIST_LIMIT).action(async (opts, cmd) => {
2344
2509
  printJson(
@@ -2386,11 +2551,12 @@ function vegaCommand() {
2386
2551
  }
2387
2552
 
2388
2553
  // src/cli.ts
2389
- var program = new Command16();
2554
+ var program = new Command17();
2390
2555
  program.name("openbkn").description("Operate the BKN platform from the CLI").version(package_default.version, "-V, --version", "output the version number").option("--base-url <url>", "platform base URL (env: BKN_BASE_URL)").option("--token <value>", "access token (env: BKN_TOKEN)").option("--user <id|name>", "use specific user credentials (env: BKN_USER)").option("--json", "machine-readable JSON output").option("--compact", "single-line JSON output").option("--full", "human view: show all columns (default trims to the key ones)").option("--biz-domain <s>", "business domain (alias: -bd)").option("-k, --insecure", "skip TLS verification (dev / self-signed only)").showHelpAfterError();
2391
2556
  program.addCommand(authCommand());
2392
2557
  program.addCommand(callCommand());
2393
2558
  program.addCommand(configCommand());
2559
+ program.addCommand(appkeyCommand());
2394
2560
  program.addCommand(vegaCommand());
2395
2561
  program.addCommand(bknCommand());
2396
2562
  program.addCommand(resourceCommand());