@openbkn/bkn-sdk 0.1.1-alpha.1 → 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/{chunk-ADZ23DPF.js → chunk-5MOIXIMJ.js} +479 -372
- package/dist/chunk-5MOIXIMJ.js.map +1 -0
- package/dist/cli.js +257 -344
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +70 -34
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-ADZ23DPF.js.map +0 -1
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-
|
|
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.
|
|
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: ">=
|
|
49
|
+
node: ">=18"
|
|
45
50
|
},
|
|
46
51
|
bin: {
|
|
47
52
|
openbkn: "./dist/cli.js"
|
|
@@ -148,6 +153,19 @@ function installGroupedHelp(root) {
|
|
|
148
153
|
apply(root);
|
|
149
154
|
}
|
|
150
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
|
+
|
|
151
169
|
// src/utils/output.ts
|
|
152
170
|
function printJson(value, opts = {}) {
|
|
153
171
|
if (opts.json || opts.compact) {
|
|
@@ -157,10 +175,11 @@ function printJson(value, opts = {}) {
|
|
|
157
175
|
}
|
|
158
176
|
const rows = toRows(value);
|
|
159
177
|
if (rows) {
|
|
160
|
-
const
|
|
178
|
+
const fullColumns = columnsOf(rows).filter((c) => rows.some((r) => stringifyCell(r[c]) !== ""));
|
|
179
|
+
const columns = opts.full ? fullColumns : selectColumns(rows);
|
|
161
180
|
if (columns.length > 0) {
|
|
162
181
|
printTable(rows, columns);
|
|
163
|
-
const hidden =
|
|
182
|
+
const hidden = fullColumns.length - columns.length;
|
|
164
183
|
if (hidden > 0 && !opts.full) {
|
|
165
184
|
process.stdout.write(`\u2026 ${hidden} more column(s); use --full or --json for everything
|
|
166
185
|
`);
|
|
@@ -168,10 +187,31 @@ function printJson(value, opts = {}) {
|
|
|
168
187
|
return;
|
|
169
188
|
}
|
|
170
189
|
}
|
|
190
|
+
if (isEmptyEnvelope(value)) {
|
|
191
|
+
process.stdout.write("(no results)\n");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
171
194
|
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
172
195
|
`);
|
|
173
196
|
}
|
|
174
|
-
|
|
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
|
+
];
|
|
175
215
|
function toRows(value) {
|
|
176
216
|
const isRowArray = (v) => Array.isArray(v) && v.length > 0 && v.every((x) => x !== null && typeof x === "object" && !Array.isArray(x));
|
|
177
217
|
if (isRowArray(value)) return value;
|
|
@@ -286,340 +326,144 @@ function readBody(opts) {
|
|
|
286
326
|
}
|
|
287
327
|
|
|
288
328
|
// src/commands/auth.ts
|
|
289
|
-
import {
|
|
329
|
+
import { createInterface } from "readline";
|
|
290
330
|
import { Command } from "commander";
|
|
291
331
|
|
|
292
|
-
// src/
|
|
293
|
-
import {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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`, {
|
|
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", {
|
|
375
343
|
method: "POST",
|
|
376
|
-
|
|
377
|
-
body: new URLSearchParams(params).toString()
|
|
378
|
-
});
|
|
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());
|
|
385
|
-
}
|
|
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");
|
|
344
|
+
body: { account, old_password: oldPassword, new_password: newPassword }
|
|
418
345
|
});
|
|
346
|
+
return { ok: true };
|
|
419
347
|
}
|
|
420
|
-
|
|
421
|
-
|
|
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;
|
|
422
353
|
try {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
354
|
+
const u = await getUserSafe(
|
|
355
|
+
{ baseUrl, token: accessToken, businessDomain: DEFAULT_BUSINESS_DOMAIN, insecure },
|
|
356
|
+
sub
|
|
357
|
+
);
|
|
358
|
+
return u.account;
|
|
428
359
|
} catch {
|
|
360
|
+
return void 0;
|
|
429
361
|
}
|
|
430
362
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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);
|
|
451
|
-
}
|
|
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}).`);
|
|
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
|
+
};
|
|
494
371
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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;
|
|
507
|
-
}
|
|
508
|
-
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
|
+
});
|
|
509
378
|
}
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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}`);
|
|
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);
|
|
529
385
|
}
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
);
|
|
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}`);
|
|
589
390
|
}
|
|
590
|
-
return
|
|
391
|
+
return lines.join("\n") || "(no saved sessions)";
|
|
591
392
|
}
|
|
592
|
-
|
|
593
|
-
// src/commands/auth.ts
|
|
594
393
|
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(
|
|
596
|
-
"--
|
|
597
|
-
"
|
|
598
|
-
|
|
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) => {
|
|
599
404
|
const g = cmd2.optsWithGlobals();
|
|
600
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
|
+
};
|
|
601
415
|
const token = opts.token ?? g.token;
|
|
602
416
|
if (token) {
|
|
603
|
-
|
|
604
|
-
printJson({ loggedIn: true, ...r2 }, outputOptions(cmd2));
|
|
417
|
+
report(attachToken(url, token, { insecure: g.insecure }));
|
|
605
418
|
return;
|
|
606
419
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
+
);
|
|
623
467
|
});
|
|
624
468
|
cmd.command("status").description("Show base URL and whether a token is configured").action((_opts, cmd2) => printJson(status(), outputOptions(cmd2)));
|
|
625
469
|
cmd.command("token").description("Print the current access token (keep secret)").action(() => {
|
|
@@ -629,7 +473,13 @@ function registerAuthLeaves(cmd) {
|
|
|
629
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(
|
|
630
474
|
(_url, _opts, cmd2) => printJson(whoami(), outputOptions(cmd2))
|
|
631
475
|
);
|
|
632
|
-
cmd.command("list").alias("ls").description("List
|
|
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
|
+
});
|
|
633
483
|
cmd.command("use <url>").description("Switch the active platform").action((url, _opts, cmd2) => {
|
|
634
484
|
use(url);
|
|
635
485
|
printJson(status(), outputOptions(cmd2));
|
|
@@ -638,28 +488,56 @@ function registerAuthLeaves(cmd) {
|
|
|
638
488
|
cmd.command("delete <url>").description("Delete saved credentials for a platform").action(
|
|
639
489
|
(url, _opts, cmd2) => printJson({ deleted: deletePlatform(url) }, outputOptions(cmd2))
|
|
640
490
|
);
|
|
641
|
-
cmd.command("switch <url> <user
|
|
642
|
-
|
|
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
|
+
`);
|
|
643
497
|
});
|
|
644
|
-
cmd.command("users <url>").description("List saved
|
|
645
|
-
|
|
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
|
+
`);
|
|
646
505
|
});
|
|
647
506
|
cmd.command("export").description("Export the active session's tokens (for a headless host)").action((_opts, cmd2) => {
|
|
648
507
|
printJson(exportCreds(), outputOptions(cmd2));
|
|
649
508
|
});
|
|
650
|
-
cmd.command("change-password [url]").description("Change your account password (
|
|
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) => {
|
|
651
510
|
const g = cmd2.optsWithGlobals();
|
|
511
|
+
if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
652
512
|
const ctx = resolveContext({
|
|
653
|
-
baseUrl: g.baseUrl,
|
|
513
|
+
baseUrl: url ?? g.baseUrl,
|
|
654
514
|
token: g.token,
|
|
655
515
|
user: g.user,
|
|
656
516
|
businessDomain: g.bizDomain,
|
|
657
517
|
insecure: g.insecure
|
|
658
518
|
});
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
)
|
|
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
|
+
}
|
|
663
541
|
});
|
|
664
542
|
}
|
|
665
543
|
function authCommand() {
|
|
@@ -840,6 +718,41 @@ function adminCommand() {
|
|
|
840
718
|
outputOptions(cmd)
|
|
841
719
|
);
|
|
842
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
|
+
}
|
|
843
756
|
const modelBody = (opts) => {
|
|
844
757
|
if (opts.body || opts.bodyFile) return readBody(opts);
|
|
845
758
|
const mc = {};
|
|
@@ -1075,7 +988,7 @@ function agentCommand() {
|
|
|
1075
988
|
import { Command as Command4 } from "commander";
|
|
1076
989
|
|
|
1077
990
|
// src/utils/bkn-validate.ts
|
|
1078
|
-
import { existsSync, readFileSync as
|
|
991
|
+
import { existsSync, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
|
|
1079
992
|
import { join, resolve } from "path";
|
|
1080
993
|
var BKN_OBJECT_NAME_MAX_LENGTH = 40;
|
|
1081
994
|
function parseFrontmatter(text) {
|
|
@@ -1128,7 +1041,7 @@ function validateBknDirectory(dirPath) {
|
|
|
1128
1041
|
if (!existsSync(networkPath)) {
|
|
1129
1042
|
errors.push("Missing network.bkn at the BKN root.");
|
|
1130
1043
|
} else {
|
|
1131
|
-
const fm = parseFrontmatter(
|
|
1044
|
+
const fm = parseFrontmatter(readFileSync2(networkPath, "utf8"));
|
|
1132
1045
|
if (!fm) errors.push("network.bkn has no frontmatter block.");
|
|
1133
1046
|
else {
|
|
1134
1047
|
if (fm.type !== "knowledge_network")
|
|
@@ -1140,7 +1053,7 @@ function validateBknDirectory(dirPath) {
|
|
|
1140
1053
|
const otIds = /* @__PURE__ */ new Set();
|
|
1141
1054
|
const otFiles = bknFiles(join(dir, "object_types"));
|
|
1142
1055
|
for (const file of otFiles) {
|
|
1143
|
-
const fm = parseFrontmatter(
|
|
1056
|
+
const fm = parseFrontmatter(readFileSync2(file, "utf8"));
|
|
1144
1057
|
const rel = file.slice(dir.length + 1);
|
|
1145
1058
|
if (!fm || fm.type !== "object_type") {
|
|
1146
1059
|
errors.push(`${rel}: not a valid object_type (missing/wrong frontmatter type).`);
|
|
@@ -1160,7 +1073,7 @@ function validateBknDirectory(dirPath) {
|
|
|
1160
1073
|
}
|
|
1161
1074
|
const rtFiles = bknFiles(join(dir, "relation_types"));
|
|
1162
1075
|
for (const file of rtFiles) {
|
|
1163
|
-
const text =
|
|
1076
|
+
const text = readFileSync2(file, "utf8");
|
|
1164
1077
|
const fm = parseFrontmatter(text);
|
|
1165
1078
|
const rel = file.slice(dir.length + 1);
|
|
1166
1079
|
if (!fm || fm.type !== "relation_type") {
|
|
@@ -1754,7 +1667,7 @@ function dataflowCommand() {
|
|
|
1754
1667
|
}
|
|
1755
1668
|
|
|
1756
1669
|
// src/commands/explore.ts
|
|
1757
|
-
import { createServer
|
|
1670
|
+
import { createServer } from "http";
|
|
1758
1671
|
import { Command as Command9 } from "commander";
|
|
1759
1672
|
var int6 = (v) => Number.parseInt(v, 10);
|
|
1760
1673
|
var ROUTES = {
|
|
@@ -1806,7 +1719,7 @@ function exploreCommand() {
|
|
|
1806
1719
|
);
|
|
1807
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) => {
|
|
1808
1721
|
const client = clientFrom(command);
|
|
1809
|
-
const server =
|
|
1722
|
+
const server = createServer((reqMsg, res) => {
|
|
1810
1723
|
void handle(client, reqMsg, res);
|
|
1811
1724
|
});
|
|
1812
1725
|
server.listen(opts.port, opts.host, () => {
|
|
@@ -2147,11 +2060,11 @@ function toolCommand() {
|
|
|
2147
2060
|
}
|
|
2148
2061
|
|
|
2149
2062
|
// src/commands/trace.ts
|
|
2150
|
-
import { readFileSync as
|
|
2063
|
+
import { readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
2151
2064
|
import { Command as Command14 } from "commander";
|
|
2152
2065
|
|
|
2153
2066
|
// src/trace-ai/schema-validate.ts
|
|
2154
|
-
import { readFileSync as
|
|
2067
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2155
2068
|
import { extname } from "path";
|
|
2156
2069
|
import yaml from "js-yaml";
|
|
2157
2070
|
import { z } from "zod";
|
|
@@ -2188,7 +2101,7 @@ var DiagnosisRule = z.object({
|
|
|
2188
2101
|
params: z.record(z.string(), z.unknown()).optional()
|
|
2189
2102
|
});
|
|
2190
2103
|
function parseFile(file) {
|
|
2191
|
-
const text =
|
|
2104
|
+
const text = readFileSync3(file, "utf8");
|
|
2192
2105
|
const ext = extname(file).toLowerCase();
|
|
2193
2106
|
if (ext === ".yaml" || ext === ".yml") return yaml.load(text);
|
|
2194
2107
|
return JSON.parse(text);
|
|
@@ -2255,7 +2168,7 @@ function traceCommand() {
|
|
|
2255
2168
|
});
|
|
2256
2169
|
const evalSet = cmd.command("eval-set").description("Build + run trace eval sets");
|
|
2257
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) => {
|
|
2258
|
-
const raw = JSON.parse(
|
|
2171
|
+
const raw = JSON.parse(readFileSync4(queriesFile, "utf8"));
|
|
2259
2172
|
const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
|
|
2260
2173
|
if (opts.out) {
|
|
2261
2174
|
writeFileSync(opts.out, JSON.stringify({ cases }, null, 2));
|
|
@@ -2265,7 +2178,7 @@ function traceCommand() {
|
|
|
2265
2178
|
}
|
|
2266
2179
|
});
|
|
2267
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) => {
|
|
2268
|
-
const raw = JSON.parse(
|
|
2181
|
+
const raw = JSON.parse(readFileSync4(casesFile, "utf8"));
|
|
2269
2182
|
const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
|
|
2270
2183
|
const result = await clientFrom(cmd2).trace.evalSetTest(opts.agent, cases, {
|
|
2271
2184
|
version: opts.version,
|