@openbkn/bkn-sdk 0.1.1-alpha.0 → 0.1.1-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-DXA44XWY.js → chunk-GNL6Z5VF.js} +689 -476
- package/dist/chunk-GNL6Z5VF.js.map +1 -0
- package/dist/cli.js +495 -382
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +101 -36
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-DXA44XWY.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,34 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
DEFAULT_BUSINESS_DOMAIN,
|
|
3
4
|
DEFAULT_LIST_LIMIT,
|
|
4
5
|
DEFAULT_QUERY_LIMIT,
|
|
6
|
+
HttpError,
|
|
5
7
|
InputError,
|
|
6
8
|
activePlatform,
|
|
9
|
+
attachNoAuth,
|
|
7
10
|
attachToken,
|
|
8
|
-
changePassword,
|
|
9
11
|
createClient,
|
|
12
|
+
credentialDeviceLogin,
|
|
10
13
|
currentToken,
|
|
14
|
+
currentTokenFresh,
|
|
15
|
+
decodeJwt,
|
|
11
16
|
deletePlatform,
|
|
17
|
+
deviceLogin,
|
|
12
18
|
exportCreds,
|
|
19
|
+
fetchAuthStatus,
|
|
13
20
|
formatError,
|
|
21
|
+
getUserSafe,
|
|
22
|
+
isHeadless,
|
|
14
23
|
listPlatforms,
|
|
15
24
|
logout,
|
|
25
|
+
openBrowser,
|
|
16
26
|
parseEmbeddingFields,
|
|
17
27
|
parsePkMap,
|
|
18
28
|
rawCall,
|
|
19
29
|
readPlatformConfig,
|
|
20
|
-
renderOrgTree,
|
|
21
30
|
renderReportMarkdown,
|
|
31
|
+
request,
|
|
22
32
|
resolveContext,
|
|
23
33
|
setActivePlatform,
|
|
24
34
|
status,
|
|
25
35
|
switchUser,
|
|
26
36
|
toExitCode,
|
|
27
37
|
use,
|
|
28
|
-
usersOf,
|
|
29
38
|
whoami,
|
|
30
39
|
writePlatformConfig
|
|
31
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-GNL6Z5VF.js";
|
|
32
41
|
|
|
33
42
|
// src/cli.ts
|
|
34
43
|
import { Command as Command16 } from "commander";
|
|
@@ -36,12 +45,12 @@ import { Command as Command16 } from "commander";
|
|
|
36
45
|
// package.json
|
|
37
46
|
var package_default = {
|
|
38
47
|
name: "@openbkn/bkn-sdk",
|
|
39
|
-
version: "0.1.1-alpha.
|
|
48
|
+
version: "0.1.1-alpha.10",
|
|
40
49
|
description: "Unified TypeScript SDK + CLI for the BKN (Business Knowledge Network) platform.",
|
|
41
50
|
type: "module",
|
|
42
51
|
license: "Apache-2.0",
|
|
43
52
|
engines: {
|
|
44
|
-
node: ">=
|
|
53
|
+
node: ">=18"
|
|
45
54
|
},
|
|
46
55
|
bin: {
|
|
47
56
|
openbkn: "./dist/cli.js"
|
|
@@ -76,8 +85,8 @@ 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
|
-
"cli-table3": "^0.6.5",
|
|
81
90
|
commander: "^13.1.0",
|
|
82
91
|
"csv-parse": "^6.2.1",
|
|
83
92
|
"js-yaml": "^4.2.0",
|
|
@@ -149,8 +158,20 @@ function installGroupedHelp(root) {
|
|
|
149
158
|
apply(root);
|
|
150
159
|
}
|
|
151
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
|
+
|
|
152
174
|
// src/utils/output.ts
|
|
153
|
-
import Table from "cli-table3";
|
|
154
175
|
function printJson(value, opts = {}) {
|
|
155
176
|
if (opts.json || opts.compact) {
|
|
156
177
|
process.stdout.write(`${JSON.stringify(value, null, opts.compact ? 0 : 2)}
|
|
@@ -159,16 +180,43 @@ function printJson(value, opts = {}) {
|
|
|
159
180
|
}
|
|
160
181
|
const rows = toRows(value);
|
|
161
182
|
if (rows) {
|
|
162
|
-
const
|
|
183
|
+
const fullColumns = columnsOf(rows).filter((c) => rows.some((r) => stringifyCell(r[c]) !== ""));
|
|
184
|
+
const columns = opts.full ? fullColumns : selectColumns(rows);
|
|
163
185
|
if (columns.length > 0) {
|
|
164
186
|
printTable(rows, columns);
|
|
187
|
+
const hidden = fullColumns.length - columns.length;
|
|
188
|
+
if (hidden > 0 && !opts.full) {
|
|
189
|
+
process.stdout.write(`\u2026 ${hidden} more column(s); use --full or --json for everything
|
|
190
|
+
`);
|
|
191
|
+
}
|
|
165
192
|
return;
|
|
166
193
|
}
|
|
167
194
|
}
|
|
195
|
+
if (isEmptyEnvelope(value)) {
|
|
196
|
+
process.stdout.write("(no results)\n");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
168
199
|
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
169
200
|
`);
|
|
170
201
|
}
|
|
171
|
-
|
|
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
|
+
];
|
|
172
220
|
function toRows(value) {
|
|
173
221
|
const isRowArray = (v) => Array.isArray(v) && v.length > 0 && v.every((x) => x !== null && typeof x === "object" && !Array.isArray(x));
|
|
174
222
|
if (isRowArray(value)) return value;
|
|
@@ -187,25 +235,90 @@ function columnsOf(rows) {
|
|
|
187
235
|
}
|
|
188
236
|
return seen;
|
|
189
237
|
}
|
|
238
|
+
var MAX_COLS = 8;
|
|
239
|
+
var NOISE_COLS = /* @__PURE__ */ new Set([
|
|
240
|
+
"creator",
|
|
241
|
+
"updater",
|
|
242
|
+
"create_by",
|
|
243
|
+
"update_by",
|
|
244
|
+
"create_user",
|
|
245
|
+
"update_user",
|
|
246
|
+
"operations",
|
|
247
|
+
"status_message",
|
|
248
|
+
"last_check_time",
|
|
249
|
+
"last_discover_status",
|
|
250
|
+
"health_check_result",
|
|
251
|
+
"health_check_enabled"
|
|
252
|
+
]);
|
|
253
|
+
var isNoiseCol = (c) => NOISE_COLS.has(c) || /_time$/.test(c);
|
|
254
|
+
var isKeyCol = (c) => /^(id|name|key|title|label)$/i.test(c) || /_(id|name|key)$/i.test(c) || /^(status|state|type|category|mode|enabled|version|branch)$/i.test(c);
|
|
255
|
+
function selectColumns(rows) {
|
|
256
|
+
const isObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
257
|
+
const kept = columnsOf(rows).filter((c) => {
|
|
258
|
+
if (isNoiseCol(c)) return false;
|
|
259
|
+
const vals = rows.map((r) => r[c]);
|
|
260
|
+
if (!vals.some((v) => stringifyCell(v) !== "")) return false;
|
|
261
|
+
if (vals.every((v) => v === null || v === void 0 || isObj(v))) return false;
|
|
262
|
+
return true;
|
|
263
|
+
});
|
|
264
|
+
const isLongText = (c) => rows.every((r) => {
|
|
265
|
+
const s = stringifyCell(r[c]);
|
|
266
|
+
return s === "" || s.length >= CELL_MAX - 1;
|
|
267
|
+
});
|
|
268
|
+
const rank = (c) => isKeyCol(c) ? 0 : isLongText(c) ? 2 : 1;
|
|
269
|
+
const ordered = kept.map((c, i) => ({ c, i, r: rank(c) })).sort((a, b) => a.r - b.r || a.i - b.i).map((x) => x.c);
|
|
270
|
+
return ordered.slice(0, MAX_COLS);
|
|
271
|
+
}
|
|
190
272
|
function printTable(rows, columns, opts = {}) {
|
|
191
273
|
if (opts.json || opts.compact) {
|
|
192
274
|
printJson(rows, opts);
|
|
193
275
|
return;
|
|
194
276
|
}
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
277
|
+
const cells = rows.map((row) => columns.map((c) => stringifyCell(row[c])));
|
|
278
|
+
const widths = columns.map(
|
|
279
|
+
(col, i) => Math.max(displayWidth(col), ...cells.map((r) => displayWidth(r[i] ?? "")))
|
|
280
|
+
);
|
|
281
|
+
const fmt = (parts) => parts.map((p, i) => i === parts.length - 1 ? p : pad(p, widths[i] ?? 0)).join(" ").trimEnd();
|
|
282
|
+
const lines = [fmt(columns), ...cells.map(fmt)];
|
|
283
|
+
process.stdout.write(`${lines.join("\n")}
|
|
200
284
|
`);
|
|
201
285
|
}
|
|
202
286
|
var CELL_MAX = 48;
|
|
287
|
+
function displayWidth(s) {
|
|
288
|
+
let w = 0;
|
|
289
|
+
for (const ch of s) w += /[ᄀ-ᅟ⺀-가-힣豈-︰-﹏-⦆¢-₩]/.test(ch) ? 2 : 1;
|
|
290
|
+
return w;
|
|
291
|
+
}
|
|
292
|
+
function pad(s, width) {
|
|
293
|
+
const gap = width - displayWidth(s);
|
|
294
|
+
return gap > 0 ? s + " ".repeat(gap) : s;
|
|
295
|
+
}
|
|
203
296
|
function stringifyCell(v) {
|
|
204
297
|
if (v === null || v === void 0) return "";
|
|
205
|
-
const
|
|
298
|
+
const raw = Array.isArray(v) && v.every((x) => x === null || typeof x !== "object") ? v.join(",") : typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
299
|
+
const s = raw.replace(/\s+/g, " ").trim();
|
|
206
300
|
return s.length > CELL_MAX ? `${s.slice(0, CELL_MAX - 1)}\u2026` : s;
|
|
207
301
|
}
|
|
208
302
|
|
|
303
|
+
// src/utils/prompt.ts
|
|
304
|
+
import { createInterface } from "readline";
|
|
305
|
+
function promptLine(query, hidden = false) {
|
|
306
|
+
return new Promise((resolve2) => {
|
|
307
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
308
|
+
if (hidden) {
|
|
309
|
+
const mutable = rl;
|
|
310
|
+
mutable._writeToOutput = (s) => {
|
|
311
|
+
if (s.startsWith(query)) process.stdout.write(query);
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
rl.question(query, (answer) => {
|
|
315
|
+
rl.close();
|
|
316
|
+
if (hidden) process.stdout.write("\n");
|
|
317
|
+
resolve2(answer.trim());
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
209
322
|
// src/commands/_shared.ts
|
|
210
323
|
import { readFileSync } from "fs";
|
|
211
324
|
function clientFrom(cmd) {
|
|
@@ -220,7 +333,7 @@ function clientFrom(cmd) {
|
|
|
220
333
|
}
|
|
221
334
|
function outputOptions(cmd) {
|
|
222
335
|
const o = cmd.optsWithGlobals();
|
|
223
|
-
return { json: Boolean(o.json), compact: Boolean(o.compact) };
|
|
336
|
+
return { json: Boolean(o.json), compact: Boolean(o.compact), full: Boolean(o.full) };
|
|
224
337
|
}
|
|
225
338
|
function csv(value) {
|
|
226
339
|
if (!value) return void 0;
|
|
@@ -237,350 +350,212 @@ function readBody(opts) {
|
|
|
237
350
|
}
|
|
238
351
|
|
|
239
352
|
// src/commands/auth.ts
|
|
240
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
241
353
|
import { Command } from "commander";
|
|
242
354
|
|
|
243
|
-
// src/
|
|
244
|
-
import {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
2XVy1v2bgSNkGHABgncR7seyIg81JQ3c/Axxd6GsTztjLnlvGAlmT1TphE84mi99
|
|
255
|
-
fUaGD2A1u1qdIuNc+XuisFeNcUW6fct0+x97eS2eEGRr/7qxWmO/P20sFVzXc2bF
|
|
256
|
-
1QIDAQAB
|
|
257
|
-
-----END PUBLIC KEY-----`;
|
|
258
|
-
function normalizeBaseUrl(value) {
|
|
259
|
-
return value.replace(/\/+$/, "");
|
|
260
|
-
}
|
|
261
|
-
function generatePkce() {
|
|
262
|
-
const verifier = randomBytes(48).toString("base64url");
|
|
263
|
-
return { verifier, challenge: createHash("sha256").update(verifier).digest("base64url") };
|
|
264
|
-
}
|
|
265
|
-
function buildAuthorizeUrl(base, clientId, redirectUri, state, codeChallenge, scope = DEFAULT_SCOPE) {
|
|
266
|
-
const params = new URLSearchParams({
|
|
267
|
-
response_type: "code",
|
|
268
|
-
client_id: clientId,
|
|
269
|
-
redirect_uri: redirectUri,
|
|
270
|
-
scope,
|
|
271
|
-
state,
|
|
272
|
-
"x-forwarded-prefix": "",
|
|
273
|
-
lang: "zh-cn",
|
|
274
|
-
product: "adp",
|
|
275
|
-
code_challenge: codeChallenge,
|
|
276
|
-
code_challenge_method: "S256"
|
|
277
|
-
});
|
|
278
|
-
return `${base}/oauth2/auth?${params.toString()}`;
|
|
279
|
-
}
|
|
280
|
-
function mapToken(data) {
|
|
281
|
-
return {
|
|
282
|
-
accessToken: data.access_token,
|
|
283
|
-
refreshToken: data.refresh_token,
|
|
284
|
-
idToken: data.id_token
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
async function registerClient(base, redirectUri, scope = DEFAULT_SCOPE) {
|
|
288
|
-
const res = await fetch(`${base}/oauth2/clients`, {
|
|
289
|
-
method: "POST",
|
|
290
|
-
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
291
|
-
body: JSON.stringify({
|
|
292
|
-
client_name: "openbkn-cli",
|
|
293
|
-
grant_types: ["authorization_code", "implicit", "refresh_token"],
|
|
294
|
-
response_types: ["token id_token", "code", "token"],
|
|
295
|
-
scope,
|
|
296
|
-
redirect_uris: [redirectUri],
|
|
297
|
-
post_logout_redirect_uris: [redirectUri.replace("/callback", "/successful-logout")],
|
|
298
|
-
metadata: { device: { name: "openbkn-cli", client_type: "web", description: "openbkn CLI" } }
|
|
299
|
-
})
|
|
300
|
-
});
|
|
301
|
-
if (!res.ok) {
|
|
302
|
-
throw new Error(
|
|
303
|
-
`Client registration failed (${res.status}): ${await res.text() || res.statusText}`
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
const data = await res.json();
|
|
307
|
-
return { clientId: data.client_id, clientSecret: data.client_secret };
|
|
308
|
-
}
|
|
309
|
-
async function exchangeCode(base, code, redirectUri, client, codeVerifier) {
|
|
310
|
-
const params = {
|
|
311
|
-
grant_type: "authorization_code",
|
|
312
|
-
code,
|
|
313
|
-
redirect_uri: redirectUri,
|
|
314
|
-
code_verifier: codeVerifier
|
|
315
|
-
};
|
|
316
|
-
const headers = {
|
|
317
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
318
|
-
Accept: "application/json"
|
|
319
|
-
};
|
|
320
|
-
if (client.clientSecret) {
|
|
321
|
-
headers.Authorization = `Basic ${Buffer.from(`${client.clientId}:${client.clientSecret}`).toString("base64")}`;
|
|
322
|
-
} else {
|
|
323
|
-
params.client_id = client.clientId;
|
|
324
|
-
}
|
|
325
|
-
const res = await fetch(`${base}/oauth2/token`, {
|
|
355
|
+
// src/api/eacp-crypto.ts
|
|
356
|
+
import {
|
|
357
|
+
constants,
|
|
358
|
+
createPrivateKey,
|
|
359
|
+
createPublicKey,
|
|
360
|
+
publicEncrypt
|
|
361
|
+
} from "crypto";
|
|
362
|
+
|
|
363
|
+
// src/api/admin.ts
|
|
364
|
+
async function changePasswordSafe(ctx, account, oldPassword, newPassword) {
|
|
365
|
+
await request(ctx, "/api/safe/v1/auth/change-password", {
|
|
326
366
|
method: "POST",
|
|
327
|
-
|
|
328
|
-
body: new URLSearchParams(params).toString()
|
|
367
|
+
body: { account, old_password: oldPassword, new_password: newPassword }
|
|
329
368
|
});
|
|
330
|
-
|
|
331
|
-
throw new Error(
|
|
332
|
-
`Token exchange failed (${res.status}): ${await res.text() || res.statusText}`
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
return mapToken(await res.json());
|
|
369
|
+
return { ok: true };
|
|
336
370
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
res.writeHead(404);
|
|
343
|
-
res.end();
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const code = u.searchParams.get("code");
|
|
347
|
-
const error = u.searchParams.get("error");
|
|
348
|
-
if (error) {
|
|
349
|
-
res.writeHead(400, { "content-type": "text/html" });
|
|
350
|
-
res.end(`<h1>Login failed</h1><p>${error}</p>`);
|
|
351
|
-
server.close(() => reject(new Error(`OAuth error: ${error}`)));
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
if (!code) {
|
|
355
|
-
res.writeHead(400);
|
|
356
|
-
res.end("missing code");
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
360
|
-
res.end("<h1>Login successful</h1><p>You can close this window.</p>");
|
|
361
|
-
resolve2({
|
|
362
|
-
code,
|
|
363
|
-
state: u.searchParams.get("state") ?? void 0,
|
|
364
|
-
close: () => server.close()
|
|
365
|
-
});
|
|
366
|
-
});
|
|
367
|
-
server.on("error", reject);
|
|
368
|
-
server.listen(port, "127.0.0.1");
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
function openBrowser(url) {
|
|
372
|
-
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
371
|
+
|
|
372
|
+
// src/commands/auth.ts
|
|
373
|
+
async function resolveAccount(baseUrl, accessToken, insecure, idToken) {
|
|
374
|
+
const sub = decodeJwt(idToken ?? accessToken)?.sub;
|
|
375
|
+
if (!sub) return void 0;
|
|
373
376
|
try {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
377
|
+
const u = await getUserSafe(
|
|
378
|
+
{ baseUrl, token: accessToken, businessDomain: DEFAULT_BUSINESS_DOMAIN, insecure },
|
|
379
|
+
sub
|
|
380
|
+
);
|
|
381
|
+
return u.account;
|
|
379
382
|
} catch {
|
|
383
|
+
return void 0;
|
|
380
384
|
}
|
|
381
385
|
}
|
|
382
|
-
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const { verifier, challenge } = generatePkce();
|
|
389
|
-
const state = randomBytes(12).toString("hex");
|
|
390
|
-
const authUrl = buildAuthorizeUrl(base, client.clientId, redirectUri, state, challenge, scope);
|
|
391
|
-
const waiter = startCallbackServer(port);
|
|
392
|
-
if (opts.noBrowser) {
|
|
393
|
-
process.stderr.write(`Open this URL to log in:
|
|
394
|
-
${authUrl}
|
|
395
|
-
`);
|
|
396
|
-
} else {
|
|
397
|
-
process.stderr.write(`Opening browser for login\u2026
|
|
398
|
-
If it doesn't open, visit:
|
|
399
|
-
${authUrl}
|
|
400
|
-
`);
|
|
401
|
-
openBrowser(authUrl);
|
|
386
|
+
function renderSessions(items) {
|
|
387
|
+
const byPlatform = /* @__PURE__ */ new Map();
|
|
388
|
+
for (const it of items) {
|
|
389
|
+
const arr = byPlatform.get(it.baseUrl) ?? [];
|
|
390
|
+
arr.push(it);
|
|
391
|
+
byPlatform.set(it.baseUrl, arr);
|
|
402
392
|
}
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
function mergeCookies(existing, res) {
|
|
409
|
-
const setCookies = typeof res.headers.getSetCookie === "function" ? res.headers.getSetCookie() : res.headers.get("set-cookie") ? [res.headers.get("set-cookie")] : [];
|
|
410
|
-
const map = /* @__PURE__ */ new Map();
|
|
411
|
-
const add = (pair) => {
|
|
412
|
-
const eq = pair.indexOf("=");
|
|
413
|
-
if (eq > 0) map.set(pair.slice(0, eq), pair.slice(eq + 1));
|
|
414
|
-
};
|
|
415
|
-
for (const p of existing.split(";").map((s) => s.trim()).filter(Boolean))
|
|
416
|
-
add(p);
|
|
417
|
-
for (const sc of setCookies) add(sc.split(";")[0]?.trim() ?? "");
|
|
418
|
-
return [...map.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
|
|
419
|
-
}
|
|
420
|
-
function parseSigninProps(html) {
|
|
421
|
-
const m = html.match(/<script[^>]*\bid=["']__NEXT_DATA__["'][^>]*>([\s\S]*?)<\/script>/i);
|
|
422
|
-
if (!m?.[1]) throw new Error("Could not find __NEXT_DATA__ on /oauth2/signin.");
|
|
423
|
-
const data = JSON.parse(m[1]);
|
|
424
|
-
const pp = data.props?.pageProps;
|
|
425
|
-
const csrftoken = pp?.csrftoken ?? pp?._csrf;
|
|
426
|
-
if (typeof csrftoken !== "string") throw new Error("Sign-in page did not expose csrftoken.");
|
|
427
|
-
return {
|
|
428
|
-
csrftoken,
|
|
429
|
-
challenge: typeof pp?.challenge === "string" ? pp.challenge : void 0,
|
|
430
|
-
remember: pp?.remember === true || pp?.remember === "true"
|
|
431
|
-
};
|
|
432
|
-
}
|
|
433
|
-
async function followToCallback(startUrl, jar0, state, redirectUri) {
|
|
434
|
-
let url = startUrl;
|
|
435
|
-
let jar = jar0;
|
|
436
|
-
const cb = new URL(redirectUri);
|
|
437
|
-
for (let hop = 0; hop < 40; hop++) {
|
|
438
|
-
const resp = await fetch(url, {
|
|
439
|
-
headers: { Cookie: jar, Accept: "text/html,*/*;q=0.8" },
|
|
440
|
-
redirect: "manual"
|
|
441
|
-
});
|
|
442
|
-
jar = mergeCookies(jar, resp);
|
|
443
|
-
if (![302, 303, 307, 308].includes(resp.status)) {
|
|
444
|
-
throw new Error(`Unexpected OAuth response (HTTP ${resp.status}).`);
|
|
445
|
-
}
|
|
446
|
-
const loc = resp.headers.get("location");
|
|
447
|
-
if (!loc) throw new Error(`OAuth redirect missing Location (HTTP ${resp.status}).`);
|
|
448
|
-
const next = new URL(loc, url);
|
|
449
|
-
if (next.origin === cb.origin && next.pathname === cb.pathname) {
|
|
450
|
-
const err = next.searchParams.get("error");
|
|
451
|
-
if (err) throw new Error(`Authorization failed: ${err}`);
|
|
452
|
-
const code = next.searchParams.get("code");
|
|
453
|
-
if (next.searchParams.get("state") !== state) throw new Error("OAuth state mismatch.");
|
|
454
|
-
if (!code) throw new Error("Callback missing authorization code.");
|
|
455
|
-
return code;
|
|
456
|
-
}
|
|
457
|
-
url = next.href;
|
|
393
|
+
const lines = [];
|
|
394
|
+
for (const [platform, users] of byPlatform) {
|
|
395
|
+
lines.push(platform);
|
|
396
|
+
for (const u of users) lines.push(` ${u.active ? "*" : " "} ${u.username ?? u.userId}`);
|
|
458
397
|
}
|
|
459
|
-
|
|
398
|
+
return lines.join("\n") || "(no saved sessions)";
|
|
460
399
|
}
|
|
461
|
-
async function passwordLogin(baseUrl, username, password, opts = {}) {
|
|
462
|
-
const base = normalizeBaseUrl(baseUrl);
|
|
463
|
-
const port = opts.port ?? DEFAULT_REDIRECT_PORT;
|
|
464
|
-
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
465
|
-
const scope = opts.scope ?? DEFAULT_SCOPE;
|
|
466
|
-
const client = opts.clientId ? { clientId: opts.clientId } : await registerClient(base, redirectUri, scope);
|
|
467
|
-
const { verifier, challenge } = generatePkce();
|
|
468
|
-
const state = randomBytes(12).toString("hex");
|
|
469
|
-
let jar = "";
|
|
470
|
-
const authResp = await fetch(
|
|
471
|
-
buildAuthorizeUrl(base, client.clientId, redirectUri, state, challenge, scope),
|
|
472
|
-
{ redirect: "manual" }
|
|
473
|
-
);
|
|
474
|
-
jar = mergeCookies(jar, authResp);
|
|
475
|
-
const authLoc = authResp.headers.get("location");
|
|
476
|
-
if (!authLoc) throw new Error(`/oauth2/auth did not redirect (HTTP ${authResp.status}).`);
|
|
477
|
-
const signinUrl = new URL(authLoc, base);
|
|
478
|
-
if (!signinUrl.pathname.includes("signin")) {
|
|
479
|
-
throw new Error(`Expected a sign-in redirect, got: ${authLoc}`);
|
|
480
|
-
}
|
|
481
|
-
const pageResp = await fetch(signinUrl.href, {
|
|
482
|
-
headers: { Cookie: jar, Accept: "text/html,*/*;q=0.8" },
|
|
483
|
-
redirect: "manual"
|
|
484
|
-
});
|
|
485
|
-
jar = mergeCookies(jar, pageResp);
|
|
486
|
-
const props = parseSigninProps(await pageResp.text());
|
|
487
|
-
const loginChallenge = signinUrl.searchParams.get("login_challenge")?.trim() || props.challenge?.trim();
|
|
488
|
-
if (!loginChallenge) throw new Error("Could not resolve the login challenge.");
|
|
489
|
-
const cipher = publicEncrypt(
|
|
490
|
-
{
|
|
491
|
-
key: opts.signinPublicKeyPem ?? STUDIOWEB_LOGIN_PUBLIC_KEY_PEM,
|
|
492
|
-
padding: cryptoConstants.RSA_PKCS1_PADDING
|
|
493
|
-
},
|
|
494
|
-
Buffer.from(password, "utf8")
|
|
495
|
-
).toString("base64");
|
|
496
|
-
const postResp = await fetch(`${base}/oauth2/signin`, {
|
|
497
|
-
method: "POST",
|
|
498
|
-
headers: {
|
|
499
|
-
Cookie: jar,
|
|
500
|
-
"Content-Type": "application/json",
|
|
501
|
-
Accept: "application/json, text/plain, */*",
|
|
502
|
-
Origin: new URL(base).origin,
|
|
503
|
-
Referer: signinUrl.href
|
|
504
|
-
},
|
|
505
|
-
body: JSON.stringify({
|
|
506
|
-
_csrf: props.csrftoken,
|
|
507
|
-
challenge: loginChallenge,
|
|
508
|
-
account: username,
|
|
509
|
-
password: cipher,
|
|
510
|
-
vcode: { id: "", content: "" },
|
|
511
|
-
dualfactorauthinfo: { validcode: { vcode: "" }, OTP: { OTP: "" } },
|
|
512
|
-
remember: props.remember ?? false,
|
|
513
|
-
device: { name: "", description: "", client_type: "console_web", udids: [] }
|
|
514
|
-
}),
|
|
515
|
-
redirect: "manual"
|
|
516
|
-
});
|
|
517
|
-
jar = mergeCookies(jar, postResp);
|
|
518
|
-
let code;
|
|
519
|
-
if ([302, 303, 307].includes(postResp.status)) {
|
|
520
|
-
const loc = postResp.headers.get("location");
|
|
521
|
-
if (!loc) throw new Error("Sign-in response missing Location.");
|
|
522
|
-
code = await followToCallback(new URL(loc, base).href, jar, state, redirectUri);
|
|
523
|
-
} else if (postResp.status === 200) {
|
|
524
|
-
const text = await postResp.text();
|
|
525
|
-
let json = null;
|
|
526
|
-
try {
|
|
527
|
-
json = JSON.parse(text);
|
|
528
|
-
} catch {
|
|
529
|
-
}
|
|
530
|
-
const redir = json && typeof json.redirect === "string" ? json.redirect : "";
|
|
531
|
-
if (!redir) {
|
|
532
|
-
const msg = json && typeof json.message === "string" ? json.message : text.slice(0, 300);
|
|
533
|
-
throw new InputError(`Sign-in failed: ${msg}`);
|
|
534
|
-
}
|
|
535
|
-
code = await followToCallback(new URL(redir, base).href, jar, state, redirectUri);
|
|
536
|
-
} else {
|
|
537
|
-
throw new InputError(
|
|
538
|
-
`Sign-in failed (HTTP ${postResp.status}): ${(await postResp.text()).slice(0, 300)}`
|
|
539
|
-
);
|
|
540
|
-
}
|
|
541
|
-
return exchangeCode(base, code, redirectUri, client, verifier);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// src/commands/auth.ts
|
|
545
400
|
function registerAuthLeaves(cmd) {
|
|
546
|
-
cmd.command("login <url>").description("Log in to a platform (attach a token, or browser/password OAuth)").option("-u, --username <name>", "username for password signin").option("-p, --password <pwd>", "password for password signin").option("--token <token>", "provide a token directly (CI / headless)").option("--client-id <id>", "use a fixed OAuth2 client id (skip dynamic registration)").option("--client-secret <secret>", "OAuth2 client secret (omit for public/PKCE)").option(
|
|
547
|
-
"--
|
|
548
|
-
"
|
|
549
|
-
|
|
401
|
+
cmd.command("login <url>").description("Log in to a platform (attach a token, or browser/password OAuth)").option("-u, --username <name>", "username for password signin").option("-p, --password <pwd>", "password for password signin").option("--token <token>", "provide a token directly (CI / headless)").option("--client-id <id>", "use a fixed OAuth2 client id (skip dynamic registration)").option("--client-secret <secret>", "OAuth2 client secret (omit for public/PKCE)").option(
|
|
402
|
+
"--port <n>",
|
|
403
|
+
"loopback redirect port for the auth_code flow",
|
|
404
|
+
(v) => Number.parseInt(v, 10)
|
|
405
|
+
).option("--device", "headless device-code login (RFC 8628) \u2014 no callback server, no password").option("--audience <aud>", "device-code token audience", "bkn-safe").option(
|
|
406
|
+
"--timeout <s>",
|
|
407
|
+
"device-login wait before timing out",
|
|
408
|
+
(v) => Number.parseInt(v, 10),
|
|
409
|
+
120
|
|
410
|
+
).option("--no-browser", "(legacy) print the URL instead of opening a browser").option("--product <name>", "(legacy) ISF OAuth product query").option("--signin-public-key-file <path>", "(legacy) RSA public key for ISF /oauth2/signin").option("--no-auth", "register the platform with no authentication (no bkn-safe)").action(async (url, opts, cmd2) => {
|
|
550
411
|
const g = cmd2.optsWithGlobals();
|
|
551
412
|
if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
413
|
+
const out = outputOptions(cmd2);
|
|
414
|
+
const report = (r) => {
|
|
415
|
+
if (out.json || out.compact) {
|
|
416
|
+
printJson({ loggedIn: true, ...r }, out);
|
|
417
|
+
} else if (r.noAuth) {
|
|
418
|
+
process.stdout.write(`Registered ${r.baseUrl ?? url} (no authentication)
|
|
419
|
+
`);
|
|
420
|
+
} else {
|
|
421
|
+
process.stdout.write(`Logged in to ${r.baseUrl ?? url} as ${r.username ?? r.userId}
|
|
422
|
+
`);
|
|
423
|
+
}
|
|
424
|
+
};
|
|
552
425
|
const token = opts.token ?? g.token;
|
|
553
426
|
if (token) {
|
|
554
|
-
|
|
555
|
-
printJson({ loggedIn: true, ...r2 }, outputOptions(cmd2));
|
|
427
|
+
report(attachToken(url, token, { insecure: g.insecure }));
|
|
556
428
|
return;
|
|
557
429
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
430
|
+
if (opts.auth === false) {
|
|
431
|
+
report(attachNoAuth(url, { insecure: g.insecure }));
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const authStatus = await fetchAuthStatus(url);
|
|
435
|
+
if (authStatus && !authStatus.enabled) {
|
|
436
|
+
process.stderr.write(
|
|
437
|
+
`Platform auth is disabled (stack: ${authStatus.stack ?? "none"}) \u2014 registering without auth.
|
|
438
|
+
`
|
|
439
|
+
);
|
|
440
|
+
report(attachNoAuth(url, { insecure: g.insecure }));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
let tokens;
|
|
444
|
+
let account;
|
|
445
|
+
try {
|
|
446
|
+
if (opts.username || opts.password) {
|
|
447
|
+
const username = opts.username ?? await promptLine("Username: ");
|
|
448
|
+
account = username;
|
|
449
|
+
const password = opts.password ?? await promptLine("Password: ", true);
|
|
450
|
+
tokens = await credentialDeviceLogin(url, username, password, {
|
|
451
|
+
clientId: opts.clientId,
|
|
452
|
+
audience: opts.audience,
|
|
453
|
+
timeoutMs: opts.timeout * 1e3
|
|
454
|
+
});
|
|
455
|
+
} else {
|
|
456
|
+
const headless = isHeadless();
|
|
457
|
+
const openInBrowser = !opts.device && opts.browser !== false && !headless;
|
|
458
|
+
tokens = await deviceLogin(url, {
|
|
459
|
+
clientId: opts.clientId,
|
|
460
|
+
audience: opts.audience,
|
|
461
|
+
timeoutMs: opts.timeout * 1e3,
|
|
462
|
+
onPrompt: ({ userCode, verificationUri, verificationUriComplete }) => {
|
|
463
|
+
const target = verificationUriComplete ?? verificationUri;
|
|
464
|
+
process.stderr.write(
|
|
465
|
+
`
|
|
466
|
+
Open this URL to sign in and authorize:
|
|
467
|
+
${target}
|
|
468
|
+
User code: ${userCode}
|
|
469
|
+
`
|
|
470
|
+
);
|
|
471
|
+
if (openInBrowser) openBrowser(target);
|
|
472
|
+
else if (headless && !opts.device && opts.browser !== false)
|
|
473
|
+
process.stderr.write("(headless \u2014 approve on any machine with a browser)\n");
|
|
474
|
+
process.stderr.write("Waiting for authorization\u2026\n");
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
} catch (e) {
|
|
479
|
+
if (e instanceof Error && /Device auth failed \(404\)/.test(e.message)) {
|
|
480
|
+
process.stderr.write("No auth endpoint found \u2014 registering platform without auth.\n");
|
|
481
|
+
report(attachNoAuth(url, { insecure: g.insecure }));
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
throw e;
|
|
485
|
+
}
|
|
486
|
+
if (!account) {
|
|
487
|
+
account = await resolveAccount(
|
|
488
|
+
url,
|
|
489
|
+
tokens.accessToken,
|
|
490
|
+
Boolean(g.insecure),
|
|
491
|
+
tokens.idToken
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
report(
|
|
495
|
+
attachToken(url, tokens.accessToken, {
|
|
496
|
+
refreshToken: tokens.refreshToken,
|
|
497
|
+
idToken: tokens.idToken,
|
|
498
|
+
insecure: g.insecure,
|
|
499
|
+
username: account
|
|
500
|
+
})
|
|
501
|
+
);
|
|
574
502
|
});
|
|
575
503
|
cmd.command("status").description("Show base URL and whether a token is configured").action((_opts, cmd2) => printJson(status(), outputOptions(cmd2)));
|
|
576
|
-
cmd.command("token").description("Print the current access token (keep secret)").action(() => {
|
|
577
|
-
|
|
504
|
+
cmd.command("token").description("Print the current access token, refreshing it if expired (keep secret)").option("--no-refresh", "print the stored token as-is, without refreshing").action(async (opts, cmd2) => {
|
|
505
|
+
const g = cmd2.optsWithGlobals();
|
|
506
|
+
if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
507
|
+
const token = opts.refresh === false ? currentToken() : await currentTokenFresh();
|
|
508
|
+
process.stdout.write(`${token}
|
|
509
|
+
`);
|
|
510
|
+
});
|
|
511
|
+
cmd.command("whoami [url]").description("Show current user identity (from the token)").option("--no-lookup", "skip the backend identity fallback (eacp/user/get)").action(async (_url, opts, cmd2) => {
|
|
512
|
+
const g = cmd2.optsWithGlobals();
|
|
513
|
+
if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
514
|
+
const me = whoami();
|
|
515
|
+
if (opts.lookup !== false && me.baseUrl && me.sub) {
|
|
516
|
+
try {
|
|
517
|
+
const u = await getUserSafe(
|
|
518
|
+
{
|
|
519
|
+
baseUrl: me.baseUrl,
|
|
520
|
+
token: currentToken(),
|
|
521
|
+
businessDomain: DEFAULT_BUSINESS_DOMAIN,
|
|
522
|
+
insecure: Boolean(g.insecure)
|
|
523
|
+
},
|
|
524
|
+
me.sub
|
|
525
|
+
);
|
|
526
|
+
if (u.account) me.username = u.account;
|
|
527
|
+
if (u.name) me.name = u.name;
|
|
528
|
+
} catch {
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const out = outputOptions(cmd2);
|
|
532
|
+
if (out.json || out.compact || out.full) {
|
|
533
|
+
printJson(me, out);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const expMs = typeof me.exp === "number" ? me.exp * 1e3 : void 0;
|
|
537
|
+
const expired = expMs !== void 0 && expMs < Date.now();
|
|
538
|
+
const rows = [
|
|
539
|
+
["User", String(me.username ?? me.sub ?? "(unknown)")],
|
|
540
|
+
...me.name && me.name !== me.username ? [["Name", String(me.name)]] : [],
|
|
541
|
+
["ID", String(me.userId ?? me.sub ?? "-")],
|
|
542
|
+
["Platform", String(me.baseUrl ?? "-")],
|
|
543
|
+
...expMs !== void 0 ? [["Expires", `${new Date(expMs).toISOString()}${expired ? " (expired)" : ""}`]] : []
|
|
544
|
+
];
|
|
545
|
+
const pad2 = Math.max(...rows.map(([k]) => k.length));
|
|
546
|
+
process.stdout.write(
|
|
547
|
+
`${rows.map(([k, v]) => `${k.padEnd(pad2)} ${v}`).join("\n")}
|
|
548
|
+
\u2026 use --full or --json for all claims
|
|
549
|
+
`
|
|
550
|
+
);
|
|
551
|
+
});
|
|
552
|
+
cmd.command("list").alias("ls").description("List saved sessions (platform \u2192 users; * = active)").action((_opts, cmd2) => {
|
|
553
|
+
const items = listPlatforms();
|
|
554
|
+
const out = outputOptions(cmd2);
|
|
555
|
+
if (out.json || out.compact) printJson(items, out);
|
|
556
|
+
else process.stdout.write(`${renderSessions(items)}
|
|
578
557
|
`);
|
|
579
558
|
});
|
|
580
|
-
cmd.command("whoami [url]").description("Show current user identity (from the token)").option("--no-lookup", "skip the backend identity fallback (eacp/user/get)").action(
|
|
581
|
-
(_url, _opts, cmd2) => printJson(whoami(), outputOptions(cmd2))
|
|
582
|
-
);
|
|
583
|
-
cmd.command("list").alias("ls").description("List platforms with a saved session").action((_opts, cmd2) => printJson(listPlatforms(), outputOptions(cmd2)));
|
|
584
559
|
cmd.command("use <url>").description("Switch the active platform").action((url, _opts, cmd2) => {
|
|
585
560
|
use(url);
|
|
586
561
|
printJson(status(), outputOptions(cmd2));
|
|
@@ -589,28 +564,56 @@ function registerAuthLeaves(cmd) {
|
|
|
589
564
|
cmd.command("delete <url>").description("Delete saved credentials for a platform").action(
|
|
590
565
|
(url, _opts, cmd2) => printJson({ deleted: deletePlatform(url) }, outputOptions(cmd2))
|
|
591
566
|
);
|
|
592
|
-
cmd.command("switch <url> <user
|
|
593
|
-
|
|
567
|
+
cmd.command("switch <url> <user>").description("Switch the active user for a platform (by username or user id)").action((url, user, _opts, cmd2) => {
|
|
568
|
+
const r = switchUser(url, user);
|
|
569
|
+
const out = outputOptions(cmd2);
|
|
570
|
+
if (out.json || out.compact) printJson(r, out);
|
|
571
|
+
else process.stdout.write(`Switched to ${r.username ?? r.userId} on ${r.baseUrl}
|
|
572
|
+
`);
|
|
594
573
|
});
|
|
595
|
-
cmd.command("users <url>").description("List saved
|
|
596
|
-
|
|
574
|
+
cmd.command("users <url>").description("List saved users for a platform (* = active)").action((url, _opts, cmd2) => {
|
|
575
|
+
const norm = url.replace(/\/+$/, "");
|
|
576
|
+
const items = listPlatforms().filter((i) => i.baseUrl === norm);
|
|
577
|
+
const out = outputOptions(cmd2);
|
|
578
|
+
if (out.json || out.compact) printJson(items, out);
|
|
579
|
+
else process.stdout.write(`${renderSessions(items)}
|
|
580
|
+
`);
|
|
597
581
|
});
|
|
598
582
|
cmd.command("export").description("Export the active session's tokens (for a headless host)").action((_opts, cmd2) => {
|
|
599
583
|
printJson(exportCreds(), outputOptions(cmd2));
|
|
600
584
|
});
|
|
601
|
-
cmd.command("change-password [url]").description("Change your account password (
|
|
585
|
+
cmd.command("change-password [url]").description("Change your account password (bkn-safe self-service; no browser)").option("-a, --account <name>", "account / login name (the login column, e.g. admin)").option("--old-password <pwd>", "current password").option("--new-password <pwd>", "new password").option("--public-key-file <path>", "(legacy) RSA public key for ISF password encryption").action(async (url, opts, cmd2) => {
|
|
602
586
|
const g = cmd2.optsWithGlobals();
|
|
587
|
+
if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
603
588
|
const ctx = resolveContext({
|
|
604
|
-
baseUrl: g.baseUrl,
|
|
589
|
+
baseUrl: url ?? g.baseUrl,
|
|
605
590
|
token: g.token,
|
|
606
591
|
user: g.user,
|
|
607
592
|
businessDomain: g.bizDomain,
|
|
608
593
|
insecure: g.insecure
|
|
609
594
|
});
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
)
|
|
595
|
+
const account = opts.account ?? await promptLine("Account: ");
|
|
596
|
+
const oldPassword = opts.oldPassword ?? await promptLine("Current password: ", true);
|
|
597
|
+
let newPassword = opts.newPassword;
|
|
598
|
+
if (!newPassword) {
|
|
599
|
+
newPassword = await promptLine("New password: ", true);
|
|
600
|
+
const confirm = await promptLine("Confirm new password: ", true);
|
|
601
|
+
if (newPassword !== confirm) throw new Error("New passwords do not match.");
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
printJson(
|
|
605
|
+
await changePasswordSafe(ctx, account, oldPassword, newPassword),
|
|
606
|
+
outputOptions(cmd2)
|
|
607
|
+
);
|
|
608
|
+
} catch (e) {
|
|
609
|
+
if (e instanceof HttpError && e.status === 401) {
|
|
610
|
+
throw new InputError("Wrong account or current password.");
|
|
611
|
+
}
|
|
612
|
+
if (e instanceof HttpError && e.status === 400) {
|
|
613
|
+
throw new InputError("New password must differ from the current one.");
|
|
614
|
+
}
|
|
615
|
+
throw e;
|
|
616
|
+
}
|
|
614
617
|
});
|
|
615
618
|
}
|
|
616
619
|
function authCommand() {
|
|
@@ -621,6 +624,7 @@ function authCommand() {
|
|
|
621
624
|
|
|
622
625
|
// src/commands/admin.ts
|
|
623
626
|
var int = (v) => Number.parseInt(v, 10);
|
|
627
|
+
var DEFAULT_RESET_PASSWORD = "openbkn";
|
|
624
628
|
function adminCommand() {
|
|
625
629
|
const admin = new Command2("admin").description(
|
|
626
630
|
"Operator CLI (kweaver-admin): org, user, role, models, audit"
|
|
@@ -753,12 +757,25 @@ function adminCommand() {
|
|
|
753
757
|
user.command("delete <id>").description("Delete a user").action(async (id, _opts, cmd) => {
|
|
754
758
|
printJson(await clientFrom(cmd).admin.userDelete(id), outputOptions(cmd));
|
|
755
759
|
});
|
|
756
|
-
user.command("reset-password [id]").description("Reset a user's password (
|
|
760
|
+
user.command("reset-password [id]").description("Reset a user's password (defaults to the platform initial password)").option("--id <userId>", "explicit user UUID (alt to the positional id)").option("--user <account>", "resolve the user by account / login name").option("--password <s>", "the new password (default: platform initial 'openbkn')").option("--new-password <s>", "the new password (alias of --password)").option("--prompt-password", "type the new password interactively (input hidden)").option("-y, --yes", "skip confirmation").action(async (id, opts, cmd) => {
|
|
757
761
|
const userId = id ?? opts.id ?? opts.user;
|
|
758
|
-
const pwd = opts.password ?? opts.newPassword;
|
|
759
762
|
if (!userId) throw new Error("Provide a user id (positional or --id).");
|
|
760
|
-
|
|
761
|
-
|
|
763
|
+
let explicit = opts.password ?? opts.newPassword;
|
|
764
|
+
if (!explicit && opts.promptPassword) {
|
|
765
|
+
explicit = await promptLine("New password: ", true);
|
|
766
|
+
if (!explicit) throw new InputError("No password entered.");
|
|
767
|
+
}
|
|
768
|
+
const pwd = explicit ?? DEFAULT_RESET_PASSWORD;
|
|
769
|
+
const r = await clientFrom(cmd).admin.userResetPassword(userId, pwd);
|
|
770
|
+
const out = outputOptions(cmd);
|
|
771
|
+
if (out.json || out.compact) printJson(r, out);
|
|
772
|
+
else if (explicit) process.stdout.write(`Password reset for ${userId}.
|
|
773
|
+
`);
|
|
774
|
+
else
|
|
775
|
+
process.stdout.write(
|
|
776
|
+
`Password reset for ${userId} to the initial password '${DEFAULT_RESET_PASSWORD}' (must change on next login).
|
|
777
|
+
`
|
|
778
|
+
);
|
|
762
779
|
});
|
|
763
780
|
const role = admin.command("role").description("Role management");
|
|
764
781
|
role.command("list").description("List roles").option("--keyword <s>", "filter by keyword").option("--limit <n>", "page size", int, 100).option("--offset <n>", "page offset", int, 0).option("--source <s>", "role source filter (business | user)").action(async (opts, cmd) => {
|
|
@@ -791,6 +808,41 @@ function adminCommand() {
|
|
|
791
808
|
outputOptions(cmd)
|
|
792
809
|
);
|
|
793
810
|
});
|
|
811
|
+
role.command("create").description("Create a custom role (bkn-safe; built-in roles are read-only)").requiredOption("--name <name>", "role name").option("--description <text>", "role description").action(async (opts, cmd) => {
|
|
812
|
+
printJson(
|
|
813
|
+
await clientFrom(cmd).admin.roleCreate(opts.name, opts.description),
|
|
814
|
+
outputOptions(cmd)
|
|
815
|
+
);
|
|
816
|
+
});
|
|
817
|
+
role.command("update <role>").description("Update a custom role's name/description (403 on built-in)").option("--name <name>", "new name").option("--description <text>", "new description").action(async (roleId, opts, cmd) => {
|
|
818
|
+
printJson(
|
|
819
|
+
await clientFrom(cmd).admin.roleUpdate(roleId, {
|
|
820
|
+
name: opts.name,
|
|
821
|
+
description: opts.description
|
|
822
|
+
}),
|
|
823
|
+
outputOptions(cmd)
|
|
824
|
+
);
|
|
825
|
+
});
|
|
826
|
+
role.command("delete <role>").description("Delete a custom role (403 on built-in)").option("-y, --yes", "skip confirmation").action(async (roleId, _opts, cmd) => {
|
|
827
|
+
printJson(await clientFrom(cmd).admin.roleDelete(roleId), outputOptions(cmd));
|
|
828
|
+
});
|
|
829
|
+
for (const [verb, grant] of [
|
|
830
|
+
["grant-perm", true],
|
|
831
|
+
["revoke-perm", false]
|
|
832
|
+
]) {
|
|
833
|
+
role.command(`${verb} <role>`).description(`${grant ? "Grant" : "Revoke"} a permission on a custom role (403 on built-in)`).requiredOption("--resource-type <t>", "resource type (e.g. catalog)").option("--resource-id <id>", "resource id ('*' = whole type)", "*").requiredOption("--operations <list>", "comma-separated operations").action(async (roleId, opts, cmd) => {
|
|
834
|
+
printJson(
|
|
835
|
+
await clientFrom(cmd).admin.rolePermission(
|
|
836
|
+
roleId,
|
|
837
|
+
grant,
|
|
838
|
+
opts.resourceType,
|
|
839
|
+
opts.resourceId,
|
|
840
|
+
csv(opts.operations) ?? []
|
|
841
|
+
),
|
|
842
|
+
outputOptions(cmd)
|
|
843
|
+
);
|
|
844
|
+
});
|
|
845
|
+
}
|
|
794
846
|
const modelBody = (opts) => {
|
|
795
847
|
if (opts.body || opts.bodyFile) return readBody(opts);
|
|
796
848
|
const mc = {};
|
|
@@ -1026,7 +1078,7 @@ function agentCommand() {
|
|
|
1026
1078
|
import { Command as Command4 } from "commander";
|
|
1027
1079
|
|
|
1028
1080
|
// src/utils/bkn-validate.ts
|
|
1029
|
-
import { existsSync, readFileSync as
|
|
1081
|
+
import { existsSync, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
|
|
1030
1082
|
import { join, resolve } from "path";
|
|
1031
1083
|
var BKN_OBJECT_NAME_MAX_LENGTH = 40;
|
|
1032
1084
|
function parseFrontmatter(text) {
|
|
@@ -1079,7 +1131,7 @@ function validateBknDirectory(dirPath) {
|
|
|
1079
1131
|
if (!existsSync(networkPath)) {
|
|
1080
1132
|
errors.push("Missing network.bkn at the BKN root.");
|
|
1081
1133
|
} else {
|
|
1082
|
-
const fm = parseFrontmatter(
|
|
1134
|
+
const fm = parseFrontmatter(readFileSync2(networkPath, "utf8"));
|
|
1083
1135
|
if (!fm) errors.push("network.bkn has no frontmatter block.");
|
|
1084
1136
|
else {
|
|
1085
1137
|
if (fm.type !== "knowledge_network")
|
|
@@ -1091,7 +1143,7 @@ function validateBknDirectory(dirPath) {
|
|
|
1091
1143
|
const otIds = /* @__PURE__ */ new Set();
|
|
1092
1144
|
const otFiles = bknFiles(join(dir, "object_types"));
|
|
1093
1145
|
for (const file of otFiles) {
|
|
1094
|
-
const fm = parseFrontmatter(
|
|
1146
|
+
const fm = parseFrontmatter(readFileSync2(file, "utf8"));
|
|
1095
1147
|
const rel = file.slice(dir.length + 1);
|
|
1096
1148
|
if (!fm || fm.type !== "object_type") {
|
|
1097
1149
|
errors.push(`${rel}: not a valid object_type (missing/wrong frontmatter type).`);
|
|
@@ -1111,7 +1163,7 @@ function validateBknDirectory(dirPath) {
|
|
|
1111
1163
|
}
|
|
1112
1164
|
const rtFiles = bknFiles(join(dir, "relation_types"));
|
|
1113
1165
|
for (const file of rtFiles) {
|
|
1114
|
-
const text =
|
|
1166
|
+
const text = readFileSync2(file, "utf8");
|
|
1115
1167
|
const fm = parseFrontmatter(text);
|
|
1116
1168
|
const rel = file.slice(dir.length + 1);
|
|
1117
1169
|
if (!fm || fm.type !== "relation_type") {
|
|
@@ -1541,6 +1593,49 @@ function configCommand() {
|
|
|
1541
1593
|
// src/commands/context.ts
|
|
1542
1594
|
import { Command as Command7 } from "commander";
|
|
1543
1595
|
var int4 = (v) => Number.parseInt(v, 10);
|
|
1596
|
+
var collectArg = (v, prev) => {
|
|
1597
|
+
prev.push(v);
|
|
1598
|
+
return prev;
|
|
1599
|
+
};
|
|
1600
|
+
function buildArgs(opts) {
|
|
1601
|
+
let out = {};
|
|
1602
|
+
if (opts.args) {
|
|
1603
|
+
try {
|
|
1604
|
+
out = JSON.parse(opts.args);
|
|
1605
|
+
} catch {
|
|
1606
|
+
throw new InputError("--args must be valid JSON");
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
for (const pair of opts.arg ?? []) {
|
|
1610
|
+
const idx = pair.indexOf("=");
|
|
1611
|
+
if (idx <= 0) throw new InputError(`--arg must be key=value (got: ${pair})`);
|
|
1612
|
+
const key = pair.slice(0, idx);
|
|
1613
|
+
const raw = pair.slice(idx + 1);
|
|
1614
|
+
try {
|
|
1615
|
+
out[key] = JSON.parse(raw);
|
|
1616
|
+
} catch {
|
|
1617
|
+
out[key] = raw;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
return out;
|
|
1621
|
+
}
|
|
1622
|
+
function printToolList(res, out) {
|
|
1623
|
+
if (out.json || out.compact) {
|
|
1624
|
+
printJson(res, out);
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
const r = res ?? {};
|
|
1628
|
+
const arr = [res, r.tools, r.result?.tools, r.data].find(Array.isArray);
|
|
1629
|
+
if (!arr) {
|
|
1630
|
+
printJson(res, out);
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
const rows = arr.map((t) => ({
|
|
1634
|
+
name: t.name ?? t.tool_name ?? t.key ?? "",
|
|
1635
|
+
description: typeof t.description === "string" ? t.description : ""
|
|
1636
|
+
}));
|
|
1637
|
+
printJson(rows, out);
|
|
1638
|
+
}
|
|
1544
1639
|
function contextCommand() {
|
|
1545
1640
|
const cmd = new Command7("context").description(
|
|
1546
1641
|
"Context loader (MCP) \u2014 schema discovery, instance query, skill recall"
|
|
@@ -1567,17 +1662,35 @@ function contextCommand() {
|
|
|
1567
1662
|
outputOptions(cmd2)
|
|
1568
1663
|
);
|
|
1569
1664
|
});
|
|
1570
|
-
cmd.command("
|
|
1571
|
-
|
|
1665
|
+
cmd.command("info").description("List the deploy's MCP tool catalog (global \u2014 no KN needed)").action(async (_opts, cmd2) => {
|
|
1666
|
+
printToolList(await clientFrom(cmd2).context.info(), outputOptions(cmd2));
|
|
1572
1667
|
});
|
|
1573
|
-
cmd.command("
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1668
|
+
cmd.command("tools <kn-id>").description("List MCP tools advertised for a KN session").action(async (knId, _opts, cmd2) => {
|
|
1669
|
+
printToolList(await clientFrom(cmd2).context.tools(knId), outputOptions(cmd2));
|
|
1670
|
+
});
|
|
1671
|
+
cmd.command("tool-call <kn-id> <name>").description("Call any MCP tool by name \u2014 current or future (use `tools` to discover)").option("--args <json>", "tool arguments as JSON").option(
|
|
1672
|
+
"--arg <key=value>",
|
|
1673
|
+
"one argument (repeatable; value parsed as JSON, else string)",
|
|
1674
|
+
collectArg,
|
|
1675
|
+
[]
|
|
1676
|
+
).action(async (knId, name, opts, cmd2) => {
|
|
1677
|
+
printJson(
|
|
1678
|
+
await clientFrom(cmd2).context.toolCall(knId, name, buildArgs(opts)),
|
|
1679
|
+
outputOptions(cmd2)
|
|
1680
|
+
);
|
|
1681
|
+
});
|
|
1682
|
+
cmd.command("call-method <kn-id> <method>").description(
|
|
1683
|
+
"Call any MCP method by name (e.g. tools/list, resources/read) \u2014 current or future"
|
|
1684
|
+
).option("--args <json>", "method params as JSON").option(
|
|
1685
|
+
"--arg <key=value>",
|
|
1686
|
+
"one param (repeatable; value parsed as JSON, else string)",
|
|
1687
|
+
collectArg,
|
|
1688
|
+
[]
|
|
1689
|
+
).action(async (knId, method, opts, cmd2) => {
|
|
1690
|
+
printJson(
|
|
1691
|
+
await clientFrom(cmd2).context.callMethod(knId, method, buildArgs(opts)),
|
|
1692
|
+
outputOptions(cmd2)
|
|
1693
|
+
);
|
|
1581
1694
|
});
|
|
1582
1695
|
cmd.command("resources <kn-id>").description("List MCP resources").action(async (knId, _opts, cmd2) => {
|
|
1583
1696
|
printJson(await clientFrom(cmd2).context.resources(knId), outputOptions(cmd2));
|
|
@@ -1609,19 +1722,19 @@ function contextCommand() {
|
|
|
1609
1722
|
throw new InputError("--args must be valid JSON");
|
|
1610
1723
|
}
|
|
1611
1724
|
};
|
|
1612
|
-
cmd.command("query-instance-subgraph <kn-id>").description("
|
|
1725
|
+
cmd.command("query-instance-subgraph <kn-id>").description("Query an instance subgraph across relation-type paths").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, opts, cmd2) => {
|
|
1613
1726
|
printJson(
|
|
1614
1727
|
await clientFrom(cmd2).context.queryInstanceSubgraph(knId, jsonArgs(opts.args)),
|
|
1615
1728
|
outputOptions(cmd2)
|
|
1616
1729
|
);
|
|
1617
1730
|
});
|
|
1618
|
-
cmd.command("get-logic-properties <kn-id>").description("
|
|
1731
|
+
cmd.command("get-logic-properties <kn-id>").description("Compute logic-property values for instances").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, opts, cmd2) => {
|
|
1619
1732
|
printJson(
|
|
1620
1733
|
await clientFrom(cmd2).context.logicProperties(knId, jsonArgs(opts.args)),
|
|
1621
1734
|
outputOptions(cmd2)
|
|
1622
1735
|
);
|
|
1623
1736
|
});
|
|
1624
|
-
cmd.command("get-action-info <kn-id>").description("
|
|
1737
|
+
cmd.command("get-action-info <kn-id>").description("Fetch action info / dynamic tools for an instance").requiredOption("--args <json>", "tool arguments as JSON").action(async (knId, opts, cmd2) => {
|
|
1625
1738
|
printJson(
|
|
1626
1739
|
await clientFrom(cmd2).context.actionInfo(knId, jsonArgs(opts.args)),
|
|
1627
1740
|
outputOptions(cmd2)
|
|
@@ -1705,7 +1818,7 @@ function dataflowCommand() {
|
|
|
1705
1818
|
}
|
|
1706
1819
|
|
|
1707
1820
|
// src/commands/explore.ts
|
|
1708
|
-
import { createServer
|
|
1821
|
+
import { createServer } from "http";
|
|
1709
1822
|
import { Command as Command9 } from "commander";
|
|
1710
1823
|
var int6 = (v) => Number.parseInt(v, 10);
|
|
1711
1824
|
var ROUTES = {
|
|
@@ -1757,7 +1870,7 @@ function exploreCommand() {
|
|
|
1757
1870
|
);
|
|
1758
1871
|
cmd.option("--port <n>", "port to listen on", int6, 7777).option("--host <h>", "host to bind", "127.0.0.1").action(async (opts, command) => {
|
|
1759
1872
|
const client = clientFrom(command);
|
|
1760
|
-
const server =
|
|
1873
|
+
const server = createServer((reqMsg, res) => {
|
|
1761
1874
|
void handle(client, reqMsg, res);
|
|
1762
1875
|
});
|
|
1763
1876
|
server.listen(opts.port, opts.host, () => {
|
|
@@ -1872,18 +1985,18 @@ import { Command as Command11 } from "commander";
|
|
|
1872
1985
|
var int8 = (v) => Number.parseInt(v, 10);
|
|
1873
1986
|
function resourceCommand() {
|
|
1874
1987
|
const cmd = new Command11("resource").alias("res").description("Resources \u2014 list, find, get, query, delete");
|
|
1875
|
-
cmd.command("list").description("List resources under a
|
|
1988
|
+
cmd.command("list").description("List resources under a catalog").option("--catalog-id <id>", "filter by catalog id").option("--datasource-id <id>", "alias of --catalog-id").option("--category <c>", "resource category (table | logicview | dataset)").option("--type <c>", "alias of --category").option("--limit <n>", "page size", int8, DEFAULT_LIST_LIMIT).action(async (opts, cmd2) => {
|
|
1876
1989
|
const data = await clientFrom(cmd2).resource.list({
|
|
1877
|
-
datasourceId: opts.datasourceId,
|
|
1878
|
-
category: opts.type,
|
|
1990
|
+
datasourceId: opts.catalogId ?? opts.datasourceId,
|
|
1991
|
+
category: opts.category ?? opts.type,
|
|
1879
1992
|
limit: opts.limit
|
|
1880
1993
|
});
|
|
1881
1994
|
printJson(data, outputOptions(cmd2));
|
|
1882
1995
|
});
|
|
1883
|
-
cmd.command("find").description("Search resources by name (fuzzy; --exact for strict)").requiredOption("--name <name>", "resource name to search").option("--exact", "exact name match").option("--
|
|
1996
|
+
cmd.command("find").description("Search resources by name (fuzzy; --exact for strict)").requiredOption("--name <name>", "resource name to search").option("--exact", "exact name match").option("--catalog-id <id>", "limit to a catalog").option("--datasource-id <id>", "alias of --catalog-id").action(async (opts, cmd2) => {
|
|
1884
1997
|
const data = await clientFrom(cmd2).resource.find(opts.name, {
|
|
1885
1998
|
exact: opts.exact,
|
|
1886
|
-
datasourceId: opts.datasourceId
|
|
1999
|
+
datasourceId: opts.catalogId ?? opts.datasourceId
|
|
1887
2000
|
});
|
|
1888
2001
|
printJson(data, outputOptions(cmd2));
|
|
1889
2002
|
});
|
|
@@ -2098,11 +2211,11 @@ function toolCommand() {
|
|
|
2098
2211
|
}
|
|
2099
2212
|
|
|
2100
2213
|
// src/commands/trace.ts
|
|
2101
|
-
import { readFileSync as
|
|
2214
|
+
import { readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
2102
2215
|
import { Command as Command14 } from "commander";
|
|
2103
2216
|
|
|
2104
2217
|
// src/trace-ai/schema-validate.ts
|
|
2105
|
-
import { readFileSync as
|
|
2218
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2106
2219
|
import { extname } from "path";
|
|
2107
2220
|
import yaml from "js-yaml";
|
|
2108
2221
|
import { z } from "zod";
|
|
@@ -2139,7 +2252,7 @@ var DiagnosisRule = z.object({
|
|
|
2139
2252
|
params: z.record(z.string(), z.unknown()).optional()
|
|
2140
2253
|
});
|
|
2141
2254
|
function parseFile(file) {
|
|
2142
|
-
const text =
|
|
2255
|
+
const text = readFileSync3(file, "utf8");
|
|
2143
2256
|
const ext = extname(file).toLowerCase();
|
|
2144
2257
|
if (ext === ".yaml" || ext === ".yml") return yaml.load(text);
|
|
2145
2258
|
return JSON.parse(text);
|
|
@@ -2206,7 +2319,7 @@ function traceCommand() {
|
|
|
2206
2319
|
});
|
|
2207
2320
|
const evalSet = cmd.command("eval-set").description("Build + run trace eval sets");
|
|
2208
2321
|
evalSet.command("build <queries-file>").description("Build eval cases from a queries JSON file").option("--out <file>", "write the cases JSON here (default: stdout)").action(async (queriesFile, opts, cmd2) => {
|
|
2209
|
-
const raw = JSON.parse(
|
|
2322
|
+
const raw = JSON.parse(readFileSync4(queriesFile, "utf8"));
|
|
2210
2323
|
const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
|
|
2211
2324
|
if (opts.out) {
|
|
2212
2325
|
writeFileSync(opts.out, JSON.stringify({ cases }, null, 2));
|
|
@@ -2216,7 +2329,7 @@ function traceCommand() {
|
|
|
2216
2329
|
}
|
|
2217
2330
|
});
|
|
2218
2331
|
evalSet.command("test <cases-file>").description("Run an eval set against an agent (--llm enables semantic_match)").requiredOption("--agent <id>", "agent id to run the queries against").option("--version <v>", "agent version", "v0").option("--llm", "enable semantic_match assertions via the local `claude` CLI").action(async (casesFile, opts, cmd2) => {
|
|
2219
|
-
const raw = JSON.parse(
|
|
2332
|
+
const raw = JSON.parse(readFileSync4(casesFile, "utf8"));
|
|
2220
2333
|
const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
|
|
2221
2334
|
const result = await clientFrom(cmd2).trace.evalSetTest(opts.agent, cases, {
|
|
2222
2335
|
version: opts.version,
|
|
@@ -2338,7 +2451,7 @@ function vegaCommand() {
|
|
|
2338
2451
|
|
|
2339
2452
|
// src/cli.ts
|
|
2340
2453
|
var program = new Command16();
|
|
2341
|
-
program.name("openbkn").description("Operate the BKN platform from the CLI").version(package_default.version, "-V, --version", "output the version number").option("--base-url <url>", "platform base URL (env: BKN_BASE_URL)").option("--token <value>", "access token (env: BKN_TOKEN)").option("--user <id|name>", "use specific user credentials (env: BKN_USER)").option("--json", "machine-readable JSON output").option("--compact", "single-line JSON output").option("--biz-domain <s>", "business domain (alias: -bd)").option("-k, --insecure", "skip TLS verification (dev / self-signed only)").showHelpAfterError();
|
|
2454
|
+
program.name("openbkn").description("Operate the BKN platform from the CLI").version(package_default.version, "-V, --version", "output the version number").option("--base-url <url>", "platform base URL (env: BKN_BASE_URL)").option("--token <value>", "access token (env: BKN_TOKEN)").option("--user <id|name>", "use specific user credentials (env: BKN_USER)").option("--json", "machine-readable JSON output").option("--compact", "single-line JSON output").option("--full", "human view: show all columns (default trims to the key ones)").option("--biz-domain <s>", "business domain (alias: -bd)").option("-k, --insecure", "skip TLS verification (dev / self-signed only)").showHelpAfterError();
|
|
2342
2455
|
program.addCommand(authCommand());
|
|
2343
2456
|
program.addCommand(callCommand());
|
|
2344
2457
|
program.addCommand(configCommand());
|