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