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

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,34 +1,43 @@
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-GNL6Z5VF.js";
32
41
 
33
42
  // src/cli.ts
34
43
  import { Command as Command16 } from "commander";
@@ -36,12 +45,12 @@ import { Command as Command16 } from "commander";
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.10",
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,31 @@ 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
+ ];
175
220
  function toRows(value) {
176
221
  const isRowArray = (v) => Array.isArray(v) && v.length > 0 && v.every((x) => x !== null && typeof x === "object" && !Array.isArray(x));
177
222
  if (isRowArray(value)) return value;
@@ -255,6 +300,25 @@ function stringifyCell(v) {
255
300
  return s.length > CELL_MAX ? `${s.slice(0, CELL_MAX - 1)}\u2026` : s;
256
301
  }
257
302
 
303
+ // src/utils/prompt.ts
304
+ import { createInterface } from "readline";
305
+ function promptLine(query, hidden = false) {
306
+ return new Promise((resolve2) => {
307
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
308
+ if (hidden) {
309
+ const mutable = rl;
310
+ mutable._writeToOutput = (s) => {
311
+ if (s.startsWith(query)) process.stdout.write(query);
312
+ };
313
+ }
314
+ rl.question(query, (answer) => {
315
+ rl.close();
316
+ if (hidden) process.stdout.write("\n");
317
+ resolve2(answer.trim());
318
+ });
319
+ });
320
+ }
321
+
258
322
  // src/commands/_shared.ts
259
323
  import { readFileSync } from "fs";
260
324
  function clientFrom(cmd) {
@@ -286,350 +350,212 @@ function readBody(opts) {
286
350
  }
287
351
 
288
352
  // src/commands/auth.ts
289
- import { readFileSync as readFileSync2 } from "fs";
290
353
  import { Command } from "commander";
291
354
 
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`, {
355
+ // src/api/eacp-crypto.ts
356
+ import {
357
+ constants,
358
+ createPrivateKey,
359
+ createPublicKey,
360
+ publicEncrypt
361
+ } from "crypto";
362
+
363
+ // src/api/admin.ts
364
+ async function changePasswordSafe(ctx, account, oldPassword, newPassword) {
365
+ await request(ctx, "/api/safe/v1/auth/change-password", {
375
366
  method: "POST",
376
- headers,
377
- body: new URLSearchParams(params).toString()
367
+ body: { account, old_password: oldPassword, new_password: newPassword }
378
368
  });
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());
369
+ return { ok: true };
385
370
  }
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";
371
+
372
+ // src/commands/auth.ts
373
+ async function resolveAccount(baseUrl, accessToken, insecure, idToken) {
374
+ const sub = decodeJwt(idToken ?? accessToken)?.sub;
375
+ if (!sub) return void 0;
422
376
  try {
423
- spawn(cmd, [url], {
424
- stdio: "ignore",
425
- detached: true,
426
- shell: process.platform === "win32"
427
- }).unref();
377
+ const u = await getUserSafe(
378
+ { baseUrl, token: accessToken, businessDomain: DEFAULT_BUSINESS_DOMAIN, insecure },
379
+ sub
380
+ );
381
+ return u.account;
428
382
  } catch {
383
+ return void 0;
429
384
  }
430
385
  }
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);
386
+ function renderSessions(items) {
387
+ const byPlatform = /* @__PURE__ */ new Map();
388
+ for (const it of items) {
389
+ const arr = byPlatform.get(it.baseUrl) ?? [];
390
+ arr.push(it);
391
+ byPlatform.set(it.baseUrl, arr);
451
392
  }
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;
393
+ const lines = [];
394
+ for (const [platform, users] of byPlatform) {
395
+ lines.push(platform);
396
+ for (const u of users) lines.push(` ${u.active ? "*" : " "} ${u.username ?? u.userId}`);
507
397
  }
508
- throw new Error("Too many OAuth redirects.");
398
+ return lines.join("\n") || "(no saved sessions)";
509
399
  }
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
400
  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) => {
401
+ 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(
402
+ "--port <n>",
403
+ "loopback redirect port for the auth_code flow",
404
+ (v) => Number.parseInt(v, 10)
405
+ ).option("--device", "headless device-code login (RFC 8628) \u2014 no callback server, no password").option("--audience <aud>", "device-code token audience", "bkn-safe").option(
406
+ "--timeout <s>",
407
+ "device-login wait before timing out",
408
+ (v) => Number.parseInt(v, 10),
409
+ 120
410
+ ).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
411
  const g = cmd2.optsWithGlobals();
600
412
  if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
413
+ const out = outputOptions(cmd2);
414
+ const report = (r) => {
415
+ if (out.json || out.compact) {
416
+ printJson({ loggedIn: true, ...r }, out);
417
+ } else if (r.noAuth) {
418
+ process.stdout.write(`Registered ${r.baseUrl ?? url} (no authentication)
419
+ `);
420
+ } else {
421
+ process.stdout.write(`Logged in to ${r.baseUrl ?? url} as ${r.username ?? r.userId}
422
+ `);
423
+ }
424
+ };
601
425
  const token = opts.token ?? g.token;
602
426
  if (token) {
603
- const r2 = attachToken(url, token, { insecure: g.insecure });
604
- printJson({ loggedIn: true, ...r2 }, outputOptions(cmd2));
427
+ report(attachToken(url, token, { insecure: g.insecure }));
605
428
  return;
606
429
  }
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));
430
+ if (opts.auth === false) {
431
+ report(attachNoAuth(url, { insecure: g.insecure }));
432
+ return;
433
+ }
434
+ const authStatus = await fetchAuthStatus(url);
435
+ if (authStatus && !authStatus.enabled) {
436
+ process.stderr.write(
437
+ `Platform auth is disabled (stack: ${authStatus.stack ?? "none"}) \u2014 registering without auth.
438
+ `
439
+ );
440
+ report(attachNoAuth(url, { insecure: g.insecure }));
441
+ return;
442
+ }
443
+ let tokens;
444
+ let account;
445
+ try {
446
+ if (opts.username || opts.password) {
447
+ const username = opts.username ?? await promptLine("Username: ");
448
+ account = username;
449
+ const password = opts.password ?? await promptLine("Password: ", true);
450
+ tokens = await credentialDeviceLogin(url, username, password, {
451
+ clientId: opts.clientId,
452
+ audience: opts.audience,
453
+ timeoutMs: opts.timeout * 1e3
454
+ });
455
+ } else {
456
+ const headless = isHeadless();
457
+ const openInBrowser = !opts.device && opts.browser !== false && !headless;
458
+ tokens = await deviceLogin(url, {
459
+ clientId: opts.clientId,
460
+ audience: opts.audience,
461
+ timeoutMs: opts.timeout * 1e3,
462
+ onPrompt: ({ userCode, verificationUri, verificationUriComplete }) => {
463
+ const target = verificationUriComplete ?? verificationUri;
464
+ process.stderr.write(
465
+ `
466
+ Open this URL to sign in and authorize:
467
+ ${target}
468
+ User code: ${userCode}
469
+ `
470
+ );
471
+ if (openInBrowser) openBrowser(target);
472
+ else if (headless && !opts.device && opts.browser !== false)
473
+ process.stderr.write("(headless \u2014 approve on any machine with a browser)\n");
474
+ process.stderr.write("Waiting for authorization\u2026\n");
475
+ }
476
+ });
477
+ }
478
+ } catch (e) {
479
+ if (e instanceof Error && /Device auth failed \(404\)/.test(e.message)) {
480
+ process.stderr.write("No auth endpoint found \u2014 registering platform without auth.\n");
481
+ report(attachNoAuth(url, { insecure: g.insecure }));
482
+ return;
483
+ }
484
+ throw e;
485
+ }
486
+ if (!account) {
487
+ account = await resolveAccount(
488
+ url,
489
+ tokens.accessToken,
490
+ Boolean(g.insecure),
491
+ tokens.idToken
492
+ );
493
+ }
494
+ report(
495
+ attachToken(url, tokens.accessToken, {
496
+ refreshToken: tokens.refreshToken,
497
+ idToken: tokens.idToken,
498
+ insecure: g.insecure,
499
+ username: account
500
+ })
501
+ );
623
502
  });
624
503
  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()}
504
+ 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) => {
505
+ const g = cmd2.optsWithGlobals();
506
+ if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
507
+ const token = opts.refresh === false ? currentToken() : await currentTokenFresh();
508
+ process.stdout.write(`${token}
509
+ `);
510
+ });
511
+ 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) => {
512
+ const g = cmd2.optsWithGlobals();
513
+ if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
514
+ const me = whoami();
515
+ if (opts.lookup !== false && me.baseUrl && me.sub) {
516
+ try {
517
+ const u = await getUserSafe(
518
+ {
519
+ baseUrl: me.baseUrl,
520
+ token: currentToken(),
521
+ businessDomain: DEFAULT_BUSINESS_DOMAIN,
522
+ insecure: Boolean(g.insecure)
523
+ },
524
+ me.sub
525
+ );
526
+ if (u.account) me.username = u.account;
527
+ if (u.name) me.name = u.name;
528
+ } catch {
529
+ }
530
+ }
531
+ const out = outputOptions(cmd2);
532
+ if (out.json || out.compact || out.full) {
533
+ printJson(me, out);
534
+ return;
535
+ }
536
+ const expMs = typeof me.exp === "number" ? me.exp * 1e3 : void 0;
537
+ const expired = expMs !== void 0 && expMs < Date.now();
538
+ const rows = [
539
+ ["User", String(me.username ?? me.sub ?? "(unknown)")],
540
+ ...me.name && me.name !== me.username ? [["Name", String(me.name)]] : [],
541
+ ["ID", String(me.userId ?? me.sub ?? "-")],
542
+ ["Platform", String(me.baseUrl ?? "-")],
543
+ ...expMs !== void 0 ? [["Expires", `${new Date(expMs).toISOString()}${expired ? " (expired)" : ""}`]] : []
544
+ ];
545
+ const pad2 = Math.max(...rows.map(([k]) => k.length));
546
+ process.stdout.write(
547
+ `${rows.map(([k, v]) => `${k.padEnd(pad2)} ${v}`).join("\n")}
548
+ \u2026 use --full or --json for all claims
549
+ `
550
+ );
551
+ });
552
+ cmd.command("list").alias("ls").description("List saved sessions (platform \u2192 users; * = active)").action((_opts, cmd2) => {
553
+ const items = listPlatforms();
554
+ const out = outputOptions(cmd2);
555
+ if (out.json || out.compact) printJson(items, out);
556
+ else process.stdout.write(`${renderSessions(items)}
627
557
  `);
628
558
  });
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
559
  cmd.command("use <url>").description("Switch the active platform").action((url, _opts, cmd2) => {
634
560
  use(url);
635
561
  printJson(status(), outputOptions(cmd2));
@@ -638,28 +564,56 @@ function registerAuthLeaves(cmd) {
638
564
  cmd.command("delete <url>").description("Delete saved credentials for a platform").action(
639
565
  (url, _opts, cmd2) => printJson({ deleted: deletePlatform(url) }, outputOptions(cmd2))
640
566
  );
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));
567
+ cmd.command("switch <url> <user>").description("Switch the active user for a platform (by username or user id)").action((url, user, _opts, cmd2) => {
568
+ const r = switchUser(url, user);
569
+ const out = outputOptions(cmd2);
570
+ if (out.json || out.compact) printJson(r, out);
571
+ else process.stdout.write(`Switched to ${r.username ?? r.userId} on ${r.baseUrl}
572
+ `);
643
573
  });
644
- cmd.command("users <url>").description("List saved user profiles for a platform").action((url, _opts, cmd2) => {
645
- printJson(usersOf(url), outputOptions(cmd2));
574
+ cmd.command("users <url>").description("List saved users for a platform (* = active)").action((url, _opts, cmd2) => {
575
+ const norm = url.replace(/\/+$/, "");
576
+ const items = listPlatforms().filter((i) => i.baseUrl === norm);
577
+ const out = outputOptions(cmd2);
578
+ if (out.json || out.compact) printJson(items, out);
579
+ else process.stdout.write(`${renderSessions(items)}
580
+ `);
646
581
  });
647
582
  cmd.command("export").description("Export the active session's tokens (for a headless host)").action((_opts, cmd2) => {
648
583
  printJson(exportCreds(), outputOptions(cmd2));
649
584
  });
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) => {
585
+ 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
586
  const g = cmd2.optsWithGlobals();
587
+ if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
652
588
  const ctx = resolveContext({
653
- baseUrl: g.baseUrl,
589
+ baseUrl: url ?? g.baseUrl,
654
590
  token: g.token,
655
591
  user: g.user,
656
592
  businessDomain: g.bizDomain,
657
593
  insecure: g.insecure
658
594
  });
659
- printJson(
660
- await changePassword(ctx, opts.account, opts.oldPassword, opts.newPassword),
661
- outputOptions(cmd2)
662
- );
595
+ const account = opts.account ?? await promptLine("Account: ");
596
+ const oldPassword = opts.oldPassword ?? await promptLine("Current password: ", true);
597
+ let newPassword = opts.newPassword;
598
+ if (!newPassword) {
599
+ newPassword = await promptLine("New password: ", true);
600
+ const confirm = await promptLine("Confirm new password: ", true);
601
+ if (newPassword !== confirm) throw new Error("New passwords do not match.");
602
+ }
603
+ try {
604
+ printJson(
605
+ await changePasswordSafe(ctx, account, oldPassword, newPassword),
606
+ outputOptions(cmd2)
607
+ );
608
+ } catch (e) {
609
+ if (e instanceof HttpError && e.status === 401) {
610
+ throw new InputError("Wrong account or current password.");
611
+ }
612
+ if (e instanceof HttpError && e.status === 400) {
613
+ throw new InputError("New password must differ from the current one.");
614
+ }
615
+ throw e;
616
+ }
663
617
  });
664
618
  }
665
619
  function authCommand() {
@@ -670,6 +624,7 @@ function authCommand() {
670
624
 
671
625
  // src/commands/admin.ts
672
626
  var int = (v) => Number.parseInt(v, 10);
627
+ var DEFAULT_RESET_PASSWORD = "openbkn";
673
628
  function adminCommand() {
674
629
  const admin = new Command2("admin").description(
675
630
  "Operator CLI (kweaver-admin): org, user, role, models, audit"
@@ -802,12 +757,25 @@ function adminCommand() {
802
757
  user.command("delete <id>").description("Delete a user").action(async (id, _opts, cmd) => {
803
758
  printJson(await clientFrom(cmd).admin.userDelete(id), outputOptions(cmd));
804
759
  });
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) => {
760
+ 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
761
  const userId = id ?? opts.id ?? opts.user;
807
- const pwd = opts.password ?? opts.newPassword;
808
762
  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));
763
+ let explicit = opts.password ?? opts.newPassword;
764
+ if (!explicit && opts.promptPassword) {
765
+ explicit = await promptLine("New password: ", true);
766
+ if (!explicit) throw new InputError("No password entered.");
767
+ }
768
+ const pwd = explicit ?? DEFAULT_RESET_PASSWORD;
769
+ const r = await clientFrom(cmd).admin.userResetPassword(userId, pwd);
770
+ const out = outputOptions(cmd);
771
+ if (out.json || out.compact) printJson(r, out);
772
+ else if (explicit) process.stdout.write(`Password reset for ${userId}.
773
+ `);
774
+ else
775
+ process.stdout.write(
776
+ `Password reset for ${userId} to the initial password '${DEFAULT_RESET_PASSWORD}' (must change on next login).
777
+ `
778
+ );
811
779
  });
812
780
  const role = admin.command("role").description("Role management");
813
781
  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 +808,41 @@ function adminCommand() {
840
808
  outputOptions(cmd)
841
809
  );
842
810
  });
811
+ 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) => {
812
+ printJson(
813
+ await clientFrom(cmd).admin.roleCreate(opts.name, opts.description),
814
+ outputOptions(cmd)
815
+ );
816
+ });
817
+ 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) => {
818
+ printJson(
819
+ await clientFrom(cmd).admin.roleUpdate(roleId, {
820
+ name: opts.name,
821
+ description: opts.description
822
+ }),
823
+ outputOptions(cmd)
824
+ );
825
+ });
826
+ role.command("delete <role>").description("Delete a custom role (403 on built-in)").option("-y, --yes", "skip confirmation").action(async (roleId, _opts, cmd) => {
827
+ printJson(await clientFrom(cmd).admin.roleDelete(roleId), outputOptions(cmd));
828
+ });
829
+ for (const [verb, grant] of [
830
+ ["grant-perm", true],
831
+ ["revoke-perm", false]
832
+ ]) {
833
+ 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) => {
834
+ printJson(
835
+ await clientFrom(cmd).admin.rolePermission(
836
+ roleId,
837
+ grant,
838
+ opts.resourceType,
839
+ opts.resourceId,
840
+ csv(opts.operations) ?? []
841
+ ),
842
+ outputOptions(cmd)
843
+ );
844
+ });
845
+ }
843
846
  const modelBody = (opts) => {
844
847
  if (opts.body || opts.bodyFile) return readBody(opts);
845
848
  const mc = {};
@@ -1075,7 +1078,7 @@ function agentCommand() {
1075
1078
  import { Command as Command4 } from "commander";
1076
1079
 
1077
1080
  // src/utils/bkn-validate.ts
1078
- import { existsSync, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
1081
+ import { existsSync, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
1079
1082
  import { join, resolve } from "path";
1080
1083
  var BKN_OBJECT_NAME_MAX_LENGTH = 40;
1081
1084
  function parseFrontmatter(text) {
@@ -1128,7 +1131,7 @@ function validateBknDirectory(dirPath) {
1128
1131
  if (!existsSync(networkPath)) {
1129
1132
  errors.push("Missing network.bkn at the BKN root.");
1130
1133
  } else {
1131
- const fm = parseFrontmatter(readFileSync3(networkPath, "utf8"));
1134
+ const fm = parseFrontmatter(readFileSync2(networkPath, "utf8"));
1132
1135
  if (!fm) errors.push("network.bkn has no frontmatter block.");
1133
1136
  else {
1134
1137
  if (fm.type !== "knowledge_network")
@@ -1140,7 +1143,7 @@ function validateBknDirectory(dirPath) {
1140
1143
  const otIds = /* @__PURE__ */ new Set();
1141
1144
  const otFiles = bknFiles(join(dir, "object_types"));
1142
1145
  for (const file of otFiles) {
1143
- const fm = parseFrontmatter(readFileSync3(file, "utf8"));
1146
+ const fm = parseFrontmatter(readFileSync2(file, "utf8"));
1144
1147
  const rel = file.slice(dir.length + 1);
1145
1148
  if (!fm || fm.type !== "object_type") {
1146
1149
  errors.push(`${rel}: not a valid object_type (missing/wrong frontmatter type).`);
@@ -1160,7 +1163,7 @@ function validateBknDirectory(dirPath) {
1160
1163
  }
1161
1164
  const rtFiles = bknFiles(join(dir, "relation_types"));
1162
1165
  for (const file of rtFiles) {
1163
- const text = readFileSync3(file, "utf8");
1166
+ const text = readFileSync2(file, "utf8");
1164
1167
  const fm = parseFrontmatter(text);
1165
1168
  const rel = file.slice(dir.length + 1);
1166
1169
  if (!fm || fm.type !== "relation_type") {
@@ -1590,6 +1593,49 @@ function configCommand() {
1590
1593
  // src/commands/context.ts
1591
1594
  import { Command as Command7 } from "commander";
1592
1595
  var int4 = (v) => Number.parseInt(v, 10);
1596
+ var collectArg = (v, prev) => {
1597
+ prev.push(v);
1598
+ return prev;
1599
+ };
1600
+ function buildArgs(opts) {
1601
+ let out = {};
1602
+ if (opts.args) {
1603
+ try {
1604
+ out = JSON.parse(opts.args);
1605
+ } catch {
1606
+ throw new InputError("--args must be valid JSON");
1607
+ }
1608
+ }
1609
+ for (const pair of opts.arg ?? []) {
1610
+ const idx = pair.indexOf("=");
1611
+ if (idx <= 0) throw new InputError(`--arg must be key=value (got: ${pair})`);
1612
+ const key = pair.slice(0, idx);
1613
+ const raw = pair.slice(idx + 1);
1614
+ try {
1615
+ out[key] = JSON.parse(raw);
1616
+ } catch {
1617
+ out[key] = raw;
1618
+ }
1619
+ }
1620
+ return out;
1621
+ }
1622
+ function printToolList(res, out) {
1623
+ if (out.json || out.compact) {
1624
+ printJson(res, out);
1625
+ return;
1626
+ }
1627
+ const r = res ?? {};
1628
+ const arr = [res, r.tools, r.result?.tools, r.data].find(Array.isArray);
1629
+ if (!arr) {
1630
+ printJson(res, out);
1631
+ return;
1632
+ }
1633
+ const rows = arr.map((t) => ({
1634
+ name: t.name ?? t.tool_name ?? t.key ?? "",
1635
+ description: typeof t.description === "string" ? t.description : ""
1636
+ }));
1637
+ printJson(rows, out);
1638
+ }
1593
1639
  function contextCommand() {
1594
1640
  const cmd = new Command7("context").description(
1595
1641
  "Context loader (MCP) \u2014 schema discovery, instance query, skill recall"
@@ -1616,17 +1662,35 @@ function contextCommand() {
1616
1662
  outputOptions(cmd2)
1617
1663
  );
1618
1664
  });
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));
1665
+ cmd.command("info").description("List the deploy's MCP tool catalog (global \u2014 no KN needed)").action(async (_opts, cmd2) => {
1666
+ printToolList(await clientFrom(cmd2).context.info(), outputOptions(cmd2));
1621
1667
  });
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));
1668
+ cmd.command("tools <kn-id>").description("List MCP tools advertised for a KN session").action(async (knId, _opts, cmd2) => {
1669
+ printToolList(await clientFrom(cmd2).context.tools(knId), outputOptions(cmd2));
1670
+ });
1671
+ 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(
1672
+ "--arg <key=value>",
1673
+ "one argument (repeatable; value parsed as JSON, else string)",
1674
+ collectArg,
1675
+ []
1676
+ ).action(async (knId, name, opts, cmd2) => {
1677
+ printJson(
1678
+ await clientFrom(cmd2).context.toolCall(knId, name, buildArgs(opts)),
1679
+ outputOptions(cmd2)
1680
+ );
1681
+ });
1682
+ cmd.command("call-method <kn-id> <method>").description(
1683
+ "Call any MCP method by name (e.g. tools/list, resources/read) \u2014 current or future"
1684
+ ).option("--args <json>", "method params as JSON").option(
1685
+ "--arg <key=value>",
1686
+ "one param (repeatable; value parsed as JSON, else string)",
1687
+ collectArg,
1688
+ []
1689
+ ).action(async (knId, method, opts, cmd2) => {
1690
+ printJson(
1691
+ await clientFrom(cmd2).context.callMethod(knId, method, buildArgs(opts)),
1692
+ outputOptions(cmd2)
1693
+ );
1630
1694
  });
1631
1695
  cmd.command("resources <kn-id>").description("List MCP resources").action(async (knId, _opts, cmd2) => {
1632
1696
  printJson(await clientFrom(cmd2).context.resources(knId), outputOptions(cmd2));
@@ -1658,19 +1722,19 @@ function contextCommand() {
1658
1722
  throw new InputError("--args must be valid JSON");
1659
1723
  }
1660
1724
  };
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) => {
1725
+ 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
1726
  printJson(
1663
1727
  await clientFrom(cmd2).context.queryInstanceSubgraph(knId, jsonArgs(opts.args)),
1664
1728
  outputOptions(cmd2)
1665
1729
  );
1666
1730
  });
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) => {
1731
+ 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
1732
  printJson(
1669
1733
  await clientFrom(cmd2).context.logicProperties(knId, jsonArgs(opts.args)),
1670
1734
  outputOptions(cmd2)
1671
1735
  );
1672
1736
  });
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) => {
1737
+ 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
1738
  printJson(
1675
1739
  await clientFrom(cmd2).context.actionInfo(knId, jsonArgs(opts.args)),
1676
1740
  outputOptions(cmd2)
@@ -1754,7 +1818,7 @@ function dataflowCommand() {
1754
1818
  }
1755
1819
 
1756
1820
  // src/commands/explore.ts
1757
- import { createServer as createServer2 } from "http";
1821
+ import { createServer } from "http";
1758
1822
  import { Command as Command9 } from "commander";
1759
1823
  var int6 = (v) => Number.parseInt(v, 10);
1760
1824
  var ROUTES = {
@@ -1806,7 +1870,7 @@ function exploreCommand() {
1806
1870
  );
1807
1871
  cmd.option("--port <n>", "port to listen on", int6, 7777).option("--host <h>", "host to bind", "127.0.0.1").action(async (opts, command) => {
1808
1872
  const client = clientFrom(command);
1809
- const server = createServer2((reqMsg, res) => {
1873
+ const server = createServer((reqMsg, res) => {
1810
1874
  void handle(client, reqMsg, res);
1811
1875
  });
1812
1876
  server.listen(opts.port, opts.host, () => {
@@ -2147,11 +2211,11 @@ function toolCommand() {
2147
2211
  }
2148
2212
 
2149
2213
  // src/commands/trace.ts
2150
- import { readFileSync as readFileSync5, writeFileSync } from "fs";
2214
+ import { readFileSync as readFileSync4, writeFileSync } from "fs";
2151
2215
  import { Command as Command14 } from "commander";
2152
2216
 
2153
2217
  // src/trace-ai/schema-validate.ts
2154
- import { readFileSync as readFileSync4 } from "fs";
2218
+ import { readFileSync as readFileSync3 } from "fs";
2155
2219
  import { extname } from "path";
2156
2220
  import yaml from "js-yaml";
2157
2221
  import { z } from "zod";
@@ -2188,7 +2252,7 @@ var DiagnosisRule = z.object({
2188
2252
  params: z.record(z.string(), z.unknown()).optional()
2189
2253
  });
2190
2254
  function parseFile(file) {
2191
- const text = readFileSync4(file, "utf8");
2255
+ const text = readFileSync3(file, "utf8");
2192
2256
  const ext = extname(file).toLowerCase();
2193
2257
  if (ext === ".yaml" || ext === ".yml") return yaml.load(text);
2194
2258
  return JSON.parse(text);
@@ -2255,7 +2319,7 @@ function traceCommand() {
2255
2319
  });
2256
2320
  const evalSet = cmd.command("eval-set").description("Build + run trace eval sets");
2257
2321
  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"));
2322
+ const raw = JSON.parse(readFileSync4(queriesFile, "utf8"));
2259
2323
  const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
2260
2324
  if (opts.out) {
2261
2325
  writeFileSync(opts.out, JSON.stringify({ cases }, null, 2));
@@ -2265,7 +2329,7 @@ function traceCommand() {
2265
2329
  }
2266
2330
  });
2267
2331
  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"));
2332
+ const raw = JSON.parse(readFileSync4(casesFile, "utf8"));
2269
2333
  const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
2270
2334
  const result = await clientFrom(cmd2).trace.evalSetTest(opts.agent, cases, {
2271
2335
  version: opts.version,