@openbkn/bkn-sdk 0.1.1-alpha.0 → 0.1.1-alpha.2

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,39 @@
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,
7
9
  attachToken,
8
- changePassword,
9
10
  createClient,
11
+ credentialDeviceLogin,
10
12
  currentToken,
13
+ decodeJwt,
11
14
  deletePlatform,
15
+ deviceLogin,
12
16
  exportCreds,
13
17
  formatError,
18
+ getUserSafe,
14
19
  listPlatforms,
15
20
  logout,
21
+ openBrowser,
16
22
  parseEmbeddingFields,
17
23
  parsePkMap,
18
24
  rawCall,
19
25
  readPlatformConfig,
20
- renderOrgTree,
21
26
  renderReportMarkdown,
27
+ request,
22
28
  resolveContext,
23
29
  setActivePlatform,
24
30
  status,
25
31
  switchUser,
26
32
  toExitCode,
27
33
  use,
28
- usersOf,
29
34
  whoami,
30
35
  writePlatformConfig
31
- } from "./chunk-DXA44XWY.js";
36
+ } from "./chunk-5MOIXIMJ.js";
32
37
 
33
38
  // src/cli.ts
34
39
  import { Command as Command16 } from "commander";
@@ -36,12 +41,12 @@ import { Command as Command16 } from "commander";
36
41
  // package.json
37
42
  var package_default = {
38
43
  name: "@openbkn/bkn-sdk",
39
- version: "0.1.1-alpha.0",
44
+ version: "0.1.1-alpha.2",
40
45
  description: "Unified TypeScript SDK + CLI for the BKN (Business Knowledge Network) platform.",
41
46
  type: "module",
42
47
  license: "Apache-2.0",
43
48
  engines: {
44
- node: ">=22"
49
+ node: ">=18"
45
50
  },
46
51
  bin: {
47
52
  openbkn: "./dist/cli.js"
@@ -77,7 +82,6 @@ var package_default = {
77
82
  dependencies: {
78
83
  "@clack/prompts": "^0.9.1",
79
84
  chalk: "^5.4.1",
80
- "cli-table3": "^0.6.5",
81
85
  commander: "^13.1.0",
82
86
  "csv-parse": "^6.2.1",
83
87
  "js-yaml": "^4.2.0",
@@ -149,8 +153,20 @@ function installGroupedHelp(root) {
149
153
  apply(root);
150
154
  }
151
155
 
156
+ // src/utils/org-tree.ts
157
+ function renderOrgTree(nodes, prefix = "") {
158
+ const lines = [];
159
+ nodes.forEach((node, i) => {
160
+ const last = i === nodes.length - 1;
161
+ lines.push(`${prefix}${last ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}${node.name} (id: ${node.id})`);
162
+ if (node.children.length) {
163
+ lines.push(renderOrgTree(node.children, `${prefix}${last ? " " : "\u2502 "}`));
164
+ }
165
+ });
166
+ return lines.join("\n");
167
+ }
168
+
152
169
  // src/utils/output.ts
153
- import Table from "cli-table3";
154
170
  function printJson(value, opts = {}) {
155
171
  if (opts.json || opts.compact) {
156
172
  process.stdout.write(`${JSON.stringify(value, null, opts.compact ? 0 : 2)}
@@ -159,16 +175,43 @@ function printJson(value, opts = {}) {
159
175
  }
160
176
  const rows = toRows(value);
161
177
  if (rows) {
162
- const columns = columnsOf(rows);
178
+ const fullColumns = columnsOf(rows).filter((c) => rows.some((r) => stringifyCell(r[c]) !== ""));
179
+ const columns = opts.full ? fullColumns : selectColumns(rows);
163
180
  if (columns.length > 0) {
164
181
  printTable(rows, columns);
182
+ const hidden = fullColumns.length - columns.length;
183
+ if (hidden > 0 && !opts.full) {
184
+ process.stdout.write(`\u2026 ${hidden} more column(s); use --full or --json for everything
185
+ `);
186
+ }
165
187
  return;
166
188
  }
167
189
  }
190
+ if (isEmptyEnvelope(value)) {
191
+ process.stdout.write("(no results)\n");
192
+ return;
193
+ }
168
194
  process.stdout.write(`${JSON.stringify(value, null, 2)}
169
195
  `);
170
196
  }
171
- var ROW_ENVELOPES = ["entries", "data", "cases", "reports", "results", "list", "recurringRules"];
197
+ function isEmptyEnvelope(value) {
198
+ if (!value || typeof value !== "object") return false;
199
+ const o = value;
200
+ return ROW_ENVELOPES.some((k) => Array.isArray(o[k]) && o[k].length === 0);
201
+ }
202
+ var ROW_ENVELOPES = [
203
+ "entries",
204
+ "data",
205
+ "cases",
206
+ "reports",
207
+ "results",
208
+ "list",
209
+ "recurringRules",
210
+ "users",
211
+ "roles",
212
+ "departments",
213
+ "members"
214
+ ];
172
215
  function toRows(value) {
173
216
  const isRowArray = (v) => Array.isArray(v) && v.length > 0 && v.every((x) => x !== null && typeof x === "object" && !Array.isArray(x));
174
217
  if (isRowArray(value)) return value;
@@ -187,22 +230,68 @@ function columnsOf(rows) {
187
230
  }
188
231
  return seen;
189
232
  }
233
+ var MAX_COLS = 8;
234
+ var NOISE_COLS = /* @__PURE__ */ new Set([
235
+ "creator",
236
+ "updater",
237
+ "create_by",
238
+ "update_by",
239
+ "create_user",
240
+ "update_user",
241
+ "operations",
242
+ "status_message",
243
+ "last_check_time",
244
+ "last_discover_status",
245
+ "health_check_result",
246
+ "health_check_enabled"
247
+ ]);
248
+ var isNoiseCol = (c) => NOISE_COLS.has(c) || /_time$/.test(c);
249
+ var isKeyCol = (c) => /^(id|name|key|title|label)$/i.test(c) || /_(id|name|key)$/i.test(c) || /^(status|state|type|category|mode|enabled|version|branch)$/i.test(c);
250
+ function selectColumns(rows) {
251
+ const isObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
252
+ const kept = columnsOf(rows).filter((c) => {
253
+ if (isNoiseCol(c)) return false;
254
+ const vals = rows.map((r) => r[c]);
255
+ if (!vals.some((v) => stringifyCell(v) !== "")) return false;
256
+ if (vals.every((v) => v === null || v === void 0 || isObj(v))) return false;
257
+ return true;
258
+ });
259
+ const isLongText = (c) => rows.every((r) => {
260
+ const s = stringifyCell(r[c]);
261
+ return s === "" || s.length >= CELL_MAX - 1;
262
+ });
263
+ const rank = (c) => isKeyCol(c) ? 0 : isLongText(c) ? 2 : 1;
264
+ const ordered = kept.map((c, i) => ({ c, i, r: rank(c) })).sort((a, b) => a.r - b.r || a.i - b.i).map((x) => x.c);
265
+ return ordered.slice(0, MAX_COLS);
266
+ }
190
267
  function printTable(rows, columns, opts = {}) {
191
268
  if (opts.json || opts.compact) {
192
269
  printJson(rows, opts);
193
270
  return;
194
271
  }
195
- const table = new Table({ head: columns });
196
- for (const row of rows) {
197
- table.push(columns.map((c) => stringifyCell(row[c])));
198
- }
199
- process.stdout.write(`${table.toString()}
272
+ const cells = rows.map((row) => columns.map((c) => stringifyCell(row[c])));
273
+ const widths = columns.map(
274
+ (col, i) => Math.max(displayWidth(col), ...cells.map((r) => displayWidth(r[i] ?? "")))
275
+ );
276
+ const fmt = (parts) => parts.map((p, i) => i === parts.length - 1 ? p : pad(p, widths[i] ?? 0)).join(" ").trimEnd();
277
+ const lines = [fmt(columns), ...cells.map(fmt)];
278
+ process.stdout.write(`${lines.join("\n")}
200
279
  `);
201
280
  }
202
281
  var CELL_MAX = 48;
282
+ function displayWidth(s) {
283
+ let w = 0;
284
+ for (const ch of s) w += /[ᄀ-ᅟ⺀-꓏가-힣豈-﫿︰-﹏＀-⦆¢-₩]/.test(ch) ? 2 : 1;
285
+ return w;
286
+ }
287
+ function pad(s, width) {
288
+ const gap = width - displayWidth(s);
289
+ return gap > 0 ? s + " ".repeat(gap) : s;
290
+ }
203
291
  function stringifyCell(v) {
204
292
  if (v === null || v === void 0) return "";
205
- const s = (typeof v === "object" ? JSON.stringify(v) : String(v)).replace(/\s+/g, " ").trim();
293
+ const raw = Array.isArray(v) && v.every((x) => x === null || typeof x !== "object") ? v.join(",") : typeof v === "object" ? JSON.stringify(v) : String(v);
294
+ const s = raw.replace(/\s+/g, " ").trim();
206
295
  return s.length > CELL_MAX ? `${s.slice(0, CELL_MAX - 1)}\u2026` : s;
207
296
  }
208
297
 
@@ -220,7 +309,7 @@ function clientFrom(cmd) {
220
309
  }
221
310
  function outputOptions(cmd) {
222
311
  const o = cmd.optsWithGlobals();
223
- return { json: Boolean(o.json), compact: Boolean(o.compact) };
312
+ return { json: Boolean(o.json), compact: Boolean(o.compact), full: Boolean(o.full) };
224
313
  }
225
314
  function csv(value) {
226
315
  if (!value) return void 0;
@@ -237,340 +326,144 @@ function readBody(opts) {
237
326
  }
238
327
 
239
328
  // src/commands/auth.ts
240
- import { readFileSync as readFileSync2 } from "fs";
329
+ import { createInterface } from "readline";
241
330
  import { Command } from "commander";
242
331
 
243
- // src/auth/oauth.ts
244
- import { spawn } from "child_process";
245
- import { createHash, constants as cryptoConstants, publicEncrypt, randomBytes } from "crypto";
246
- import { createServer } from "http";
247
- var DEFAULT_REDIRECT_PORT = 9010;
248
- var DEFAULT_SCOPE = "openid offline all";
249
- var STUDIOWEB_LOGIN_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
250
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyOstgbYuubBi2PUqeVj
251
- GKlkwVUY6w1Y8d4k116dI2SkZI8fxcjHALv77kItO4jYLVplk9gO4HAtsisnNE2o
252
- wlYIqdmyEPMwupaeFFFcg751oiTXJiYbtX7ABzU5KQYPjRSEjMq6i5qu/mL67XTk
253
- hvKwrC83zme66qaKApmKupDODPb0RRkutK/zHfd1zL7sciBQ6psnNadh8pE24w8O
254
- 2XVy1v2bgSNkGHABgncR7seyIg81JQ3c/Axxd6GsTztjLnlvGAlmT1TphE84mi99
255
- fUaGD2A1u1qdIuNc+XuisFeNcUW6fct0+x97eS2eEGRr/7qxWmO/P20sFVzXc2bF
256
- 1QIDAQAB
257
- -----END PUBLIC KEY-----`;
258
- function normalizeBaseUrl(value) {
259
- return value.replace(/\/+$/, "");
260
- }
261
- function generatePkce() {
262
- const verifier = randomBytes(48).toString("base64url");
263
- return { verifier, challenge: createHash("sha256").update(verifier).digest("base64url") };
264
- }
265
- function buildAuthorizeUrl(base, clientId, redirectUri, state, codeChallenge, scope = DEFAULT_SCOPE) {
266
- const params = new URLSearchParams({
267
- response_type: "code",
268
- client_id: clientId,
269
- redirect_uri: redirectUri,
270
- scope,
271
- state,
272
- "x-forwarded-prefix": "",
273
- lang: "zh-cn",
274
- product: "adp",
275
- code_challenge: codeChallenge,
276
- code_challenge_method: "S256"
277
- });
278
- return `${base}/oauth2/auth?${params.toString()}`;
279
- }
280
- function mapToken(data) {
281
- return {
282
- accessToken: data.access_token,
283
- refreshToken: data.refresh_token,
284
- idToken: data.id_token
285
- };
286
- }
287
- async function registerClient(base, redirectUri, scope = DEFAULT_SCOPE) {
288
- const res = await fetch(`${base}/oauth2/clients`, {
289
- method: "POST",
290
- headers: { "Content-Type": "application/json", Accept: "application/json" },
291
- body: JSON.stringify({
292
- client_name: "openbkn-cli",
293
- grant_types: ["authorization_code", "implicit", "refresh_token"],
294
- response_types: ["token id_token", "code", "token"],
295
- scope,
296
- redirect_uris: [redirectUri],
297
- post_logout_redirect_uris: [redirectUri.replace("/callback", "/successful-logout")],
298
- metadata: { device: { name: "openbkn-cli", client_type: "web", description: "openbkn CLI" } }
299
- })
300
- });
301
- if (!res.ok) {
302
- throw new Error(
303
- `Client registration failed (${res.status}): ${await res.text() || res.statusText}`
304
- );
305
- }
306
- const data = await res.json();
307
- return { clientId: data.client_id, clientSecret: data.client_secret };
308
- }
309
- async function exchangeCode(base, code, redirectUri, client, codeVerifier) {
310
- const params = {
311
- grant_type: "authorization_code",
312
- code,
313
- redirect_uri: redirectUri,
314
- code_verifier: codeVerifier
315
- };
316
- const headers = {
317
- "Content-Type": "application/x-www-form-urlencoded",
318
- Accept: "application/json"
319
- };
320
- if (client.clientSecret) {
321
- headers.Authorization = `Basic ${Buffer.from(`${client.clientId}:${client.clientSecret}`).toString("base64")}`;
322
- } else {
323
- params.client_id = client.clientId;
324
- }
325
- const res = await fetch(`${base}/oauth2/token`, {
332
+ // src/api/eacp-crypto.ts
333
+ import {
334
+ constants,
335
+ createPrivateKey,
336
+ createPublicKey,
337
+ publicEncrypt
338
+ } from "crypto";
339
+
340
+ // src/api/admin.ts
341
+ async function changePasswordSafe(ctx, account, oldPassword, newPassword) {
342
+ await request(ctx, "/api/safe/v1/auth/change-password", {
326
343
  method: "POST",
327
- headers,
328
- body: new URLSearchParams(params).toString()
329
- });
330
- if (!res.ok) {
331
- throw new Error(
332
- `Token exchange failed (${res.status}): ${await res.text() || res.statusText}`
333
- );
334
- }
335
- return mapToken(await res.json());
336
- }
337
- function startCallbackServer(port) {
338
- return new Promise((resolve2, reject) => {
339
- const server = createServer((req2, res) => {
340
- const u = new URL(req2.url ?? "/", `http://127.0.0.1:${port}`);
341
- if (u.pathname !== "/callback") {
342
- res.writeHead(404);
343
- res.end();
344
- return;
345
- }
346
- const code = u.searchParams.get("code");
347
- const error = u.searchParams.get("error");
348
- if (error) {
349
- res.writeHead(400, { "content-type": "text/html" });
350
- res.end(`<h1>Login failed</h1><p>${error}</p>`);
351
- server.close(() => reject(new Error(`OAuth error: ${error}`)));
352
- return;
353
- }
354
- if (!code) {
355
- res.writeHead(400);
356
- res.end("missing code");
357
- return;
358
- }
359
- res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
360
- res.end("<h1>Login successful</h1><p>You can close this window.</p>");
361
- resolve2({
362
- code,
363
- state: u.searchParams.get("state") ?? void 0,
364
- close: () => server.close()
365
- });
366
- });
367
- server.on("error", reject);
368
- server.listen(port, "127.0.0.1");
344
+ body: { account, old_password: oldPassword, new_password: newPassword }
369
345
  });
346
+ return { ok: true };
370
347
  }
371
- function openBrowser(url) {
372
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
348
+
349
+ // src/commands/auth.ts
350
+ async function resolveAccount(baseUrl, accessToken, insecure, idToken) {
351
+ const sub = decodeJwt(idToken ?? accessToken)?.sub;
352
+ if (!sub) return void 0;
373
353
  try {
374
- spawn(cmd, [url], {
375
- stdio: "ignore",
376
- detached: true,
377
- shell: process.platform === "win32"
378
- }).unref();
354
+ const u = await getUserSafe(
355
+ { baseUrl, token: accessToken, businessDomain: DEFAULT_BUSINESS_DOMAIN, insecure },
356
+ sub
357
+ );
358
+ return u.account;
379
359
  } catch {
360
+ return void 0;
380
361
  }
381
362
  }
382
- async function browserLogin(baseUrl, opts = {}) {
383
- const base = normalizeBaseUrl(baseUrl);
384
- const port = opts.port ?? DEFAULT_REDIRECT_PORT;
385
- const redirectUri = `http://127.0.0.1:${port}/callback`;
386
- const scope = opts.scope ?? DEFAULT_SCOPE;
387
- const client = opts.clientId ? { clientId: opts.clientId } : await registerClient(base, redirectUri, scope);
388
- const { verifier, challenge } = generatePkce();
389
- const state = randomBytes(12).toString("hex");
390
- const authUrl = buildAuthorizeUrl(base, client.clientId, redirectUri, state, challenge, scope);
391
- const waiter = startCallbackServer(port);
392
- if (opts.noBrowser) {
393
- process.stderr.write(`Open this URL to log in:
394
- ${authUrl}
395
- `);
396
- } else {
397
- process.stderr.write(`Opening browser for login\u2026
398
- If it doesn't open, visit:
399
- ${authUrl}
400
- `);
401
- openBrowser(authUrl);
402
- }
403
- const { code, state: returned, close } = await waiter;
404
- close();
405
- if (returned && returned !== state) throw new Error("OAuth state mismatch \u2014 possible CSRF.");
406
- return exchangeCode(base, code, redirectUri, client, verifier);
407
- }
408
- function mergeCookies(existing, res) {
409
- const setCookies = typeof res.headers.getSetCookie === "function" ? res.headers.getSetCookie() : res.headers.get("set-cookie") ? [res.headers.get("set-cookie")] : [];
410
- const map = /* @__PURE__ */ new Map();
411
- const add = (pair) => {
412
- const eq = pair.indexOf("=");
413
- if (eq > 0) map.set(pair.slice(0, eq), pair.slice(eq + 1));
414
- };
415
- for (const p of existing.split(";").map((s) => s.trim()).filter(Boolean))
416
- add(p);
417
- for (const sc of setCookies) add(sc.split(";")[0]?.trim() ?? "");
418
- return [...map.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
419
- }
420
- function parseSigninProps(html) {
421
- const m = html.match(/<script[^>]*\bid=["']__NEXT_DATA__["'][^>]*>([\s\S]*?)<\/script>/i);
422
- if (!m?.[1]) throw new Error("Could not find __NEXT_DATA__ on /oauth2/signin.");
423
- const data = JSON.parse(m[1]);
424
- const pp = data.props?.pageProps;
425
- const csrftoken = pp?.csrftoken ?? pp?._csrf;
426
- if (typeof csrftoken !== "string") throw new Error("Sign-in page did not expose csrftoken.");
427
- return {
428
- csrftoken,
429
- challenge: typeof pp?.challenge === "string" ? pp.challenge : void 0,
430
- remember: pp?.remember === true || pp?.remember === "true"
431
- };
432
- }
433
- async function followToCallback(startUrl, jar0, state, redirectUri) {
434
- let url = startUrl;
435
- let jar = jar0;
436
- const cb = new URL(redirectUri);
437
- for (let hop = 0; hop < 40; hop++) {
438
- const resp = await fetch(url, {
439
- headers: { Cookie: jar, Accept: "text/html,*/*;q=0.8" },
440
- redirect: "manual"
441
- });
442
- jar = mergeCookies(jar, resp);
443
- if (![302, 303, 307, 308].includes(resp.status)) {
444
- throw new Error(`Unexpected OAuth response (HTTP ${resp.status}).`);
445
- }
446
- const loc = resp.headers.get("location");
447
- if (!loc) throw new Error(`OAuth redirect missing Location (HTTP ${resp.status}).`);
448
- const next = new URL(loc, url);
449
- if (next.origin === cb.origin && next.pathname === cb.pathname) {
450
- const err = next.searchParams.get("error");
451
- if (err) throw new Error(`Authorization failed: ${err}`);
452
- const code = next.searchParams.get("code");
453
- if (next.searchParams.get("state") !== state) throw new Error("OAuth state mismatch.");
454
- if (!code) throw new Error("Callback missing authorization code.");
455
- return code;
363
+ function promptLine(query, hidden = false) {
364
+ return new Promise((resolve2) => {
365
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
366
+ if (hidden) {
367
+ const mutable = rl;
368
+ mutable._writeToOutput = (s) => {
369
+ if (s.startsWith(query)) process.stdout.write(query);
370
+ };
456
371
  }
457
- url = next.href;
458
- }
459
- throw new Error("Too many OAuth redirects.");
372
+ rl.question(query, (answer) => {
373
+ rl.close();
374
+ if (hidden) process.stdout.write("\n");
375
+ resolve2(answer.trim());
376
+ });
377
+ });
460
378
  }
461
- async function passwordLogin(baseUrl, username, password, opts = {}) {
462
- const base = normalizeBaseUrl(baseUrl);
463
- const port = opts.port ?? DEFAULT_REDIRECT_PORT;
464
- const redirectUri = `http://127.0.0.1:${port}/callback`;
465
- const scope = opts.scope ?? DEFAULT_SCOPE;
466
- const client = opts.clientId ? { clientId: opts.clientId } : await registerClient(base, redirectUri, scope);
467
- const { verifier, challenge } = generatePkce();
468
- const state = randomBytes(12).toString("hex");
469
- let jar = "";
470
- const authResp = await fetch(
471
- buildAuthorizeUrl(base, client.clientId, redirectUri, state, challenge, scope),
472
- { redirect: "manual" }
473
- );
474
- jar = mergeCookies(jar, authResp);
475
- const authLoc = authResp.headers.get("location");
476
- if (!authLoc) throw new Error(`/oauth2/auth did not redirect (HTTP ${authResp.status}).`);
477
- const signinUrl = new URL(authLoc, base);
478
- if (!signinUrl.pathname.includes("signin")) {
479
- throw new Error(`Expected a sign-in redirect, got: ${authLoc}`);
379
+ function renderSessions(items) {
380
+ const byPlatform = /* @__PURE__ */ new Map();
381
+ for (const it of items) {
382
+ const arr = byPlatform.get(it.baseUrl) ?? [];
383
+ arr.push(it);
384
+ byPlatform.set(it.baseUrl, arr);
480
385
  }
481
- const pageResp = await fetch(signinUrl.href, {
482
- headers: { Cookie: jar, Accept: "text/html,*/*;q=0.8" },
483
- redirect: "manual"
484
- });
485
- jar = mergeCookies(jar, pageResp);
486
- const props = parseSigninProps(await pageResp.text());
487
- const loginChallenge = signinUrl.searchParams.get("login_challenge")?.trim() || props.challenge?.trim();
488
- if (!loginChallenge) throw new Error("Could not resolve the login challenge.");
489
- const cipher = publicEncrypt(
490
- {
491
- key: opts.signinPublicKeyPem ?? STUDIOWEB_LOGIN_PUBLIC_KEY_PEM,
492
- padding: cryptoConstants.RSA_PKCS1_PADDING
493
- },
494
- Buffer.from(password, "utf8")
495
- ).toString("base64");
496
- const postResp = await fetch(`${base}/oauth2/signin`, {
497
- method: "POST",
498
- headers: {
499
- Cookie: jar,
500
- "Content-Type": "application/json",
501
- Accept: "application/json, text/plain, */*",
502
- Origin: new URL(base).origin,
503
- Referer: signinUrl.href
504
- },
505
- body: JSON.stringify({
506
- _csrf: props.csrftoken,
507
- challenge: loginChallenge,
508
- account: username,
509
- password: cipher,
510
- vcode: { id: "", content: "" },
511
- dualfactorauthinfo: { validcode: { vcode: "" }, OTP: { OTP: "" } },
512
- remember: props.remember ?? false,
513
- device: { name: "", description: "", client_type: "console_web", udids: [] }
514
- }),
515
- redirect: "manual"
516
- });
517
- jar = mergeCookies(jar, postResp);
518
- let code;
519
- if ([302, 303, 307].includes(postResp.status)) {
520
- const loc = postResp.headers.get("location");
521
- if (!loc) throw new Error("Sign-in response missing Location.");
522
- code = await followToCallback(new URL(loc, base).href, jar, state, redirectUri);
523
- } else if (postResp.status === 200) {
524
- const text = await postResp.text();
525
- let json = null;
526
- try {
527
- json = JSON.parse(text);
528
- } catch {
529
- }
530
- const redir = json && typeof json.redirect === "string" ? json.redirect : "";
531
- if (!redir) {
532
- const msg = json && typeof json.message === "string" ? json.message : text.slice(0, 300);
533
- throw new InputError(`Sign-in failed: ${msg}`);
534
- }
535
- code = await followToCallback(new URL(redir, base).href, jar, state, redirectUri);
536
- } else {
537
- throw new InputError(
538
- `Sign-in failed (HTTP ${postResp.status}): ${(await postResp.text()).slice(0, 300)}`
539
- );
386
+ const lines = [];
387
+ for (const [platform, users] of byPlatform) {
388
+ lines.push(platform);
389
+ for (const u of users) lines.push(` ${u.active ? "*" : " "} ${u.username ?? u.userId}`);
540
390
  }
541
- return exchangeCode(base, code, redirectUri, client, verifier);
391
+ return lines.join("\n") || "(no saved sessions)";
542
392
  }
543
-
544
- // src/commands/auth.ts
545
393
  function registerAuthLeaves(cmd) {
546
- 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(
547
- "--signin-public-key-file <path>",
548
- "override the RSA public key (PEM) for password signin"
549
- ).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) => {
394
+ 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(
395
+ "--port <n>",
396
+ "loopback redirect port for the auth_code flow",
397
+ (v) => Number.parseInt(v, 10)
398
+ ).option("--device", "headless device-code login (RFC 8628) \u2014 no callback server, no password").option("--audience <aud>", "device-code token audience", "bkn-safe").option(
399
+ "--timeout <s>",
400
+ "device-login wait before timing out",
401
+ (v) => Number.parseInt(v, 10),
402
+ 120
403
+ ).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").action(async (url, opts, cmd2) => {
550
404
  const g = cmd2.optsWithGlobals();
551
405
  if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
406
+ const out = outputOptions(cmd2);
407
+ const report = (r) => {
408
+ if (out.json || out.compact) {
409
+ printJson({ loggedIn: true, ...r }, out);
410
+ } else {
411
+ process.stdout.write(`Logged in to ${r.baseUrl ?? url} as ${r.username ?? r.userId}
412
+ `);
413
+ }
414
+ };
552
415
  const token = opts.token ?? g.token;
553
416
  if (token) {
554
- const r2 = attachToken(url, token, { insecure: g.insecure });
555
- printJson({ loggedIn: true, ...r2 }, outputOptions(cmd2));
417
+ report(attachToken(url, token, { insecure: g.insecure }));
556
418
  return;
557
419
  }
558
- const signinKey = opts.signinPublicKeyFile ? readFileSync2(opts.signinPublicKeyFile, "utf8") : void 0;
559
- const tokens = opts.username ? await passwordLogin(url, opts.username, opts.password ?? "", {
560
- clientId: opts.clientId,
561
- port: opts.port,
562
- signinPublicKeyPem: signinKey
563
- }) : await browserLogin(url, {
564
- clientId: opts.clientId,
565
- port: opts.port,
566
- noBrowser: opts.browser === false
567
- });
568
- const r = attachToken(url, tokens.accessToken, {
569
- refreshToken: tokens.refreshToken,
570
- idToken: tokens.idToken,
571
- insecure: g.insecure
572
- });
573
- printJson({ loggedIn: true, ...r }, outputOptions(cmd2));
420
+ let tokens;
421
+ let account;
422
+ if (opts.username || opts.password) {
423
+ const username = opts.username ?? await promptLine("Username: ");
424
+ account = username;
425
+ const password = opts.password ?? await promptLine("Password: ", true);
426
+ tokens = await credentialDeviceLogin(url, username, password, {
427
+ clientId: opts.clientId,
428
+ audience: opts.audience,
429
+ timeoutMs: opts.timeout * 1e3
430
+ });
431
+ } else {
432
+ const openInBrowser = !opts.device && opts.browser !== false;
433
+ tokens = await deviceLogin(url, {
434
+ clientId: opts.clientId,
435
+ audience: opts.audience,
436
+ timeoutMs: opts.timeout * 1e3,
437
+ onPrompt: ({ userCode, verificationUri, verificationUriComplete }) => {
438
+ const target = verificationUriComplete ?? verificationUri;
439
+ process.stderr.write(
440
+ `
441
+ Open this URL to sign in and authorize:
442
+ ${target}
443
+ User code: ${userCode}
444
+ `
445
+ );
446
+ if (openInBrowser) openBrowser(target);
447
+ process.stderr.write("Waiting for authorization\u2026\n");
448
+ }
449
+ });
450
+ }
451
+ if (!account) {
452
+ account = await resolveAccount(
453
+ url,
454
+ tokens.accessToken,
455
+ Boolean(g.insecure),
456
+ tokens.idToken
457
+ );
458
+ }
459
+ report(
460
+ attachToken(url, tokens.accessToken, {
461
+ refreshToken: tokens.refreshToken,
462
+ idToken: tokens.idToken,
463
+ insecure: g.insecure,
464
+ username: account
465
+ })
466
+ );
574
467
  });
575
468
  cmd.command("status").description("Show base URL and whether a token is configured").action((_opts, cmd2) => printJson(status(), outputOptions(cmd2)));
576
469
  cmd.command("token").description("Print the current access token (keep secret)").action(() => {
@@ -580,7 +473,13 @@ function registerAuthLeaves(cmd) {
580
473
  cmd.command("whoami [url]").description("Show current user identity (from the token)").option("--no-lookup", "skip the backend identity fallback (eacp/user/get)").action(
581
474
  (_url, _opts, cmd2) => printJson(whoami(), outputOptions(cmd2))
582
475
  );
583
- cmd.command("list").alias("ls").description("List platforms with a saved session").action((_opts, cmd2) => printJson(listPlatforms(), outputOptions(cmd2)));
476
+ cmd.command("list").alias("ls").description("List saved sessions (platform \u2192 users; * = active)").action((_opts, cmd2) => {
477
+ const items = listPlatforms();
478
+ const out = outputOptions(cmd2);
479
+ if (out.json || out.compact) printJson(items, out);
480
+ else process.stdout.write(`${renderSessions(items)}
481
+ `);
482
+ });
584
483
  cmd.command("use <url>").description("Switch the active platform").action((url, _opts, cmd2) => {
585
484
  use(url);
586
485
  printJson(status(), outputOptions(cmd2));
@@ -589,28 +488,56 @@ function registerAuthLeaves(cmd) {
589
488
  cmd.command("delete <url>").description("Delete saved credentials for a platform").action(
590
489
  (url, _opts, cmd2) => printJson({ deleted: deletePlatform(url) }, outputOptions(cmd2))
591
490
  );
592
- cmd.command("switch <url> <user-id>").description("Switch the active user for a platform").action((url, userId, _opts, cmd2) => {
593
- printJson(switchUser(url, userId), outputOptions(cmd2));
491
+ cmd.command("switch <url> <user>").description("Switch the active user for a platform (by username or user id)").action((url, user, _opts, cmd2) => {
492
+ const r = switchUser(url, user);
493
+ const out = outputOptions(cmd2);
494
+ if (out.json || out.compact) printJson(r, out);
495
+ else process.stdout.write(`Switched to ${r.username ?? r.userId} on ${r.baseUrl}
496
+ `);
594
497
  });
595
- cmd.command("users <url>").description("List saved user profiles for a platform").action((url, _opts, cmd2) => {
596
- printJson(usersOf(url), outputOptions(cmd2));
498
+ cmd.command("users <url>").description("List saved users for a platform (* = active)").action((url, _opts, cmd2) => {
499
+ const norm = url.replace(/\/+$/, "");
500
+ const items = listPlatforms().filter((i) => i.baseUrl === norm);
501
+ const out = outputOptions(cmd2);
502
+ if (out.json || out.compact) printJson(items, out);
503
+ else process.stdout.write(`${renderSessions(items)}
504
+ `);
597
505
  });
598
506
  cmd.command("export").description("Export the active session's tokens (for a headless host)").action((_opts, cmd2) => {
599
507
  printJson(exportCreds(), outputOptions(cmd2));
600
508
  });
601
- 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) => {
509
+ 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) => {
602
510
  const g = cmd2.optsWithGlobals();
511
+ if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
603
512
  const ctx = resolveContext({
604
- baseUrl: g.baseUrl,
513
+ baseUrl: url ?? g.baseUrl,
605
514
  token: g.token,
606
515
  user: g.user,
607
516
  businessDomain: g.bizDomain,
608
517
  insecure: g.insecure
609
518
  });
610
- printJson(
611
- await changePassword(ctx, opts.account, opts.oldPassword, opts.newPassword),
612
- outputOptions(cmd2)
613
- );
519
+ const account = opts.account ?? await promptLine("Account: ");
520
+ const oldPassword = opts.oldPassword ?? await promptLine("Current password: ", true);
521
+ let newPassword = opts.newPassword;
522
+ if (!newPassword) {
523
+ newPassword = await promptLine("New password: ", true);
524
+ const confirm = await promptLine("Confirm new password: ", true);
525
+ if (newPassword !== confirm) throw new Error("New passwords do not match.");
526
+ }
527
+ try {
528
+ printJson(
529
+ await changePasswordSafe(ctx, account, oldPassword, newPassword),
530
+ outputOptions(cmd2)
531
+ );
532
+ } catch (e) {
533
+ if (e instanceof HttpError && e.status === 401) {
534
+ throw new InputError("Wrong account or current password.");
535
+ }
536
+ if (e instanceof HttpError && e.status === 400) {
537
+ throw new InputError("New password must differ from the current one.");
538
+ }
539
+ throw e;
540
+ }
614
541
  });
615
542
  }
616
543
  function authCommand() {
@@ -791,6 +718,41 @@ function adminCommand() {
791
718
  outputOptions(cmd)
792
719
  );
793
720
  });
721
+ 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) => {
722
+ printJson(
723
+ await clientFrom(cmd).admin.roleCreate(opts.name, opts.description),
724
+ outputOptions(cmd)
725
+ );
726
+ });
727
+ 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) => {
728
+ printJson(
729
+ await clientFrom(cmd).admin.roleUpdate(roleId, {
730
+ name: opts.name,
731
+ description: opts.description
732
+ }),
733
+ outputOptions(cmd)
734
+ );
735
+ });
736
+ role.command("delete <role>").description("Delete a custom role (403 on built-in)").option("-y, --yes", "skip confirmation").action(async (roleId, _opts, cmd) => {
737
+ printJson(await clientFrom(cmd).admin.roleDelete(roleId), outputOptions(cmd));
738
+ });
739
+ for (const [verb, grant] of [
740
+ ["grant-perm", true],
741
+ ["revoke-perm", false]
742
+ ]) {
743
+ 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) => {
744
+ printJson(
745
+ await clientFrom(cmd).admin.rolePermission(
746
+ roleId,
747
+ grant,
748
+ opts.resourceType,
749
+ opts.resourceId,
750
+ csv(opts.operations) ?? []
751
+ ),
752
+ outputOptions(cmd)
753
+ );
754
+ });
755
+ }
794
756
  const modelBody = (opts) => {
795
757
  if (opts.body || opts.bodyFile) return readBody(opts);
796
758
  const mc = {};
@@ -1026,7 +988,7 @@ function agentCommand() {
1026
988
  import { Command as Command4 } from "commander";
1027
989
 
1028
990
  // src/utils/bkn-validate.ts
1029
- import { existsSync, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
991
+ import { existsSync, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
1030
992
  import { join, resolve } from "path";
1031
993
  var BKN_OBJECT_NAME_MAX_LENGTH = 40;
1032
994
  function parseFrontmatter(text) {
@@ -1079,7 +1041,7 @@ function validateBknDirectory(dirPath) {
1079
1041
  if (!existsSync(networkPath)) {
1080
1042
  errors.push("Missing network.bkn at the BKN root.");
1081
1043
  } else {
1082
- const fm = parseFrontmatter(readFileSync3(networkPath, "utf8"));
1044
+ const fm = parseFrontmatter(readFileSync2(networkPath, "utf8"));
1083
1045
  if (!fm) errors.push("network.bkn has no frontmatter block.");
1084
1046
  else {
1085
1047
  if (fm.type !== "knowledge_network")
@@ -1091,7 +1053,7 @@ function validateBknDirectory(dirPath) {
1091
1053
  const otIds = /* @__PURE__ */ new Set();
1092
1054
  const otFiles = bknFiles(join(dir, "object_types"));
1093
1055
  for (const file of otFiles) {
1094
- const fm = parseFrontmatter(readFileSync3(file, "utf8"));
1056
+ const fm = parseFrontmatter(readFileSync2(file, "utf8"));
1095
1057
  const rel = file.slice(dir.length + 1);
1096
1058
  if (!fm || fm.type !== "object_type") {
1097
1059
  errors.push(`${rel}: not a valid object_type (missing/wrong frontmatter type).`);
@@ -1111,7 +1073,7 @@ function validateBknDirectory(dirPath) {
1111
1073
  }
1112
1074
  const rtFiles = bknFiles(join(dir, "relation_types"));
1113
1075
  for (const file of rtFiles) {
1114
- const text = readFileSync3(file, "utf8");
1076
+ const text = readFileSync2(file, "utf8");
1115
1077
  const fm = parseFrontmatter(text);
1116
1078
  const rel = file.slice(dir.length + 1);
1117
1079
  if (!fm || fm.type !== "relation_type") {
@@ -1705,7 +1667,7 @@ function dataflowCommand() {
1705
1667
  }
1706
1668
 
1707
1669
  // src/commands/explore.ts
1708
- import { createServer as createServer2 } from "http";
1670
+ import { createServer } from "http";
1709
1671
  import { Command as Command9 } from "commander";
1710
1672
  var int6 = (v) => Number.parseInt(v, 10);
1711
1673
  var ROUTES = {
@@ -1757,7 +1719,7 @@ function exploreCommand() {
1757
1719
  );
1758
1720
  cmd.option("--port <n>", "port to listen on", int6, 7777).option("--host <h>", "host to bind", "127.0.0.1").action(async (opts, command) => {
1759
1721
  const client = clientFrom(command);
1760
- const server = createServer2((reqMsg, res) => {
1722
+ const server = createServer((reqMsg, res) => {
1761
1723
  void handle(client, reqMsg, res);
1762
1724
  });
1763
1725
  server.listen(opts.port, opts.host, () => {
@@ -1872,18 +1834,18 @@ import { Command as Command11 } from "commander";
1872
1834
  var int8 = (v) => Number.parseInt(v, 10);
1873
1835
  function resourceCommand() {
1874
1836
  const cmd = new Command11("resource").alias("res").description("Resources \u2014 list, find, get, query, delete");
1875
- cmd.command("list").description("List resources under a datasource/catalog").option("--datasource-id <id>", "filter by datasource/catalog id").option("--type <category>", "resource category (table | logicview)").option("--limit <n>", "page size", int8, DEFAULT_LIST_LIMIT).action(async (opts, cmd2) => {
1837
+ 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) => {
1876
1838
  const data = await clientFrom(cmd2).resource.list({
1877
- datasourceId: opts.datasourceId,
1878
- category: opts.type,
1839
+ datasourceId: opts.catalogId ?? opts.datasourceId,
1840
+ category: opts.category ?? opts.type,
1879
1841
  limit: opts.limit
1880
1842
  });
1881
1843
  printJson(data, outputOptions(cmd2));
1882
1844
  });
1883
- cmd.command("find").description("Search resources by name (fuzzy; --exact for strict)").requiredOption("--name <name>", "resource name to search").option("--exact", "exact name match").option("--datasource-id <id>", "limit to a datasource/catalog").action(async (opts, cmd2) => {
1845
+ cmd.command("find").description("Search resources by name (fuzzy; --exact for strict)").requiredOption("--name <name>", "resource name to search").option("--exact", "exact name match").option("--catalog-id <id>", "limit to a catalog").option("--datasource-id <id>", "alias of --catalog-id").action(async (opts, cmd2) => {
1884
1846
  const data = await clientFrom(cmd2).resource.find(opts.name, {
1885
1847
  exact: opts.exact,
1886
- datasourceId: opts.datasourceId
1848
+ datasourceId: opts.catalogId ?? opts.datasourceId
1887
1849
  });
1888
1850
  printJson(data, outputOptions(cmd2));
1889
1851
  });
@@ -2098,11 +2060,11 @@ function toolCommand() {
2098
2060
  }
2099
2061
 
2100
2062
  // src/commands/trace.ts
2101
- import { readFileSync as readFileSync5, writeFileSync } from "fs";
2063
+ import { readFileSync as readFileSync4, writeFileSync } from "fs";
2102
2064
  import { Command as Command14 } from "commander";
2103
2065
 
2104
2066
  // src/trace-ai/schema-validate.ts
2105
- import { readFileSync as readFileSync4 } from "fs";
2067
+ import { readFileSync as readFileSync3 } from "fs";
2106
2068
  import { extname } from "path";
2107
2069
  import yaml from "js-yaml";
2108
2070
  import { z } from "zod";
@@ -2139,7 +2101,7 @@ var DiagnosisRule = z.object({
2139
2101
  params: z.record(z.string(), z.unknown()).optional()
2140
2102
  });
2141
2103
  function parseFile(file) {
2142
- const text = readFileSync4(file, "utf8");
2104
+ const text = readFileSync3(file, "utf8");
2143
2105
  const ext = extname(file).toLowerCase();
2144
2106
  if (ext === ".yaml" || ext === ".yml") return yaml.load(text);
2145
2107
  return JSON.parse(text);
@@ -2206,7 +2168,7 @@ function traceCommand() {
2206
2168
  });
2207
2169
  const evalSet = cmd.command("eval-set").description("Build + run trace eval sets");
2208
2170
  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) => {
2209
- const raw = JSON.parse(readFileSync5(queriesFile, "utf8"));
2171
+ const raw = JSON.parse(readFileSync4(queriesFile, "utf8"));
2210
2172
  const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
2211
2173
  if (opts.out) {
2212
2174
  writeFileSync(opts.out, JSON.stringify({ cases }, null, 2));
@@ -2216,7 +2178,7 @@ function traceCommand() {
2216
2178
  }
2217
2179
  });
2218
2180
  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) => {
2219
- const raw = JSON.parse(readFileSync5(casesFile, "utf8"));
2181
+ const raw = JSON.parse(readFileSync4(casesFile, "utf8"));
2220
2182
  const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
2221
2183
  const result = await clientFrom(cmd2).trace.evalSetTest(opts.agent, cases, {
2222
2184
  version: opts.version,
@@ -2338,7 +2300,7 @@ function vegaCommand() {
2338
2300
 
2339
2301
  // src/cli.ts
2340
2302
  var program = new Command16();
2341
- 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("--biz-domain <s>", "business domain (alias: -bd)").option("-k, --insecure", "skip TLS verification (dev / self-signed only)").showHelpAfterError();
2303
+ 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();
2342
2304
  program.addCommand(authCommand());
2343
2305
  program.addCommand(callCommand());
2344
2306
  program.addCommand(configCommand());