@openbkn/bkn-sdk 0.1.1-alpha.1 → 0.1.1-alpha.3
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-APJNRHLS.js} +504 -375
- package/dist/chunk-APJNRHLS.js.map +1 -0
- package/dist/cli.js +284 -344
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +81 -35
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-ADZ23DPF.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,34 +1,41 @@
|
|
|
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
|
+
decodeJwt,
|
|
11
15
|
deletePlatform,
|
|
16
|
+
deviceLogin,
|
|
12
17
|
exportCreds,
|
|
18
|
+
fetchAuthStatus,
|
|
13
19
|
formatError,
|
|
20
|
+
getUserSafe,
|
|
14
21
|
listPlatforms,
|
|
15
22
|
logout,
|
|
23
|
+
openBrowser,
|
|
16
24
|
parseEmbeddingFields,
|
|
17
25
|
parsePkMap,
|
|
18
26
|
rawCall,
|
|
19
27
|
readPlatformConfig,
|
|
20
|
-
renderOrgTree,
|
|
21
28
|
renderReportMarkdown,
|
|
29
|
+
request,
|
|
22
30
|
resolveContext,
|
|
23
31
|
setActivePlatform,
|
|
24
32
|
status,
|
|
25
33
|
switchUser,
|
|
26
34
|
toExitCode,
|
|
27
35
|
use,
|
|
28
|
-
usersOf,
|
|
29
36
|
whoami,
|
|
30
37
|
writePlatformConfig
|
|
31
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-APJNRHLS.js";
|
|
32
39
|
|
|
33
40
|
// src/cli.ts
|
|
34
41
|
import { Command as Command16 } from "commander";
|
|
@@ -36,12 +43,12 @@ import { Command as Command16 } from "commander";
|
|
|
36
43
|
// package.json
|
|
37
44
|
var package_default = {
|
|
38
45
|
name: "@openbkn/bkn-sdk",
|
|
39
|
-
version: "0.1.1-alpha.
|
|
46
|
+
version: "0.1.1-alpha.3",
|
|
40
47
|
description: "Unified TypeScript SDK + CLI for the BKN (Business Knowledge Network) platform.",
|
|
41
48
|
type: "module",
|
|
42
49
|
license: "Apache-2.0",
|
|
43
50
|
engines: {
|
|
44
|
-
node: ">=
|
|
51
|
+
node: ">=18"
|
|
45
52
|
},
|
|
46
53
|
bin: {
|
|
47
54
|
openbkn: "./dist/cli.js"
|
|
@@ -148,6 +155,19 @@ function installGroupedHelp(root) {
|
|
|
148
155
|
apply(root);
|
|
149
156
|
}
|
|
150
157
|
|
|
158
|
+
// src/utils/org-tree.ts
|
|
159
|
+
function renderOrgTree(nodes, prefix = "") {
|
|
160
|
+
const lines = [];
|
|
161
|
+
nodes.forEach((node, i) => {
|
|
162
|
+
const last = i === nodes.length - 1;
|
|
163
|
+
lines.push(`${prefix}${last ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}${node.name} (id: ${node.id})`);
|
|
164
|
+
if (node.children.length) {
|
|
165
|
+
lines.push(renderOrgTree(node.children, `${prefix}${last ? " " : "\u2502 "}`));
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
151
171
|
// src/utils/output.ts
|
|
152
172
|
function printJson(value, opts = {}) {
|
|
153
173
|
if (opts.json || opts.compact) {
|
|
@@ -157,10 +177,11 @@ function printJson(value, opts = {}) {
|
|
|
157
177
|
}
|
|
158
178
|
const rows = toRows(value);
|
|
159
179
|
if (rows) {
|
|
160
|
-
const
|
|
180
|
+
const fullColumns = columnsOf(rows).filter((c) => rows.some((r) => stringifyCell(r[c]) !== ""));
|
|
181
|
+
const columns = opts.full ? fullColumns : selectColumns(rows);
|
|
161
182
|
if (columns.length > 0) {
|
|
162
183
|
printTable(rows, columns);
|
|
163
|
-
const hidden =
|
|
184
|
+
const hidden = fullColumns.length - columns.length;
|
|
164
185
|
if (hidden > 0 && !opts.full) {
|
|
165
186
|
process.stdout.write(`\u2026 ${hidden} more column(s); use --full or --json for everything
|
|
166
187
|
`);
|
|
@@ -168,10 +189,31 @@ function printJson(value, opts = {}) {
|
|
|
168
189
|
return;
|
|
169
190
|
}
|
|
170
191
|
}
|
|
192
|
+
if (isEmptyEnvelope(value)) {
|
|
193
|
+
process.stdout.write("(no results)\n");
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
171
196
|
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
172
197
|
`);
|
|
173
198
|
}
|
|
174
|
-
|
|
199
|
+
function isEmptyEnvelope(value) {
|
|
200
|
+
if (!value || typeof value !== "object") return false;
|
|
201
|
+
const o = value;
|
|
202
|
+
return ROW_ENVELOPES.some((k) => Array.isArray(o[k]) && o[k].length === 0);
|
|
203
|
+
}
|
|
204
|
+
var ROW_ENVELOPES = [
|
|
205
|
+
"entries",
|
|
206
|
+
"data",
|
|
207
|
+
"cases",
|
|
208
|
+
"reports",
|
|
209
|
+
"results",
|
|
210
|
+
"list",
|
|
211
|
+
"recurringRules",
|
|
212
|
+
"users",
|
|
213
|
+
"roles",
|
|
214
|
+
"departments",
|
|
215
|
+
"members"
|
|
216
|
+
];
|
|
175
217
|
function toRows(value) {
|
|
176
218
|
const isRowArray = (v) => Array.isArray(v) && v.length > 0 && v.every((x) => x !== null && typeof x === "object" && !Array.isArray(x));
|
|
177
219
|
if (isRowArray(value)) return value;
|
|
@@ -286,340 +328,169 @@ function readBody(opts) {
|
|
|
286
328
|
}
|
|
287
329
|
|
|
288
330
|
// src/commands/auth.ts
|
|
289
|
-
import {
|
|
331
|
+
import { createInterface } from "readline";
|
|
290
332
|
import { Command } from "commander";
|
|
291
333
|
|
|
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`, {
|
|
334
|
+
// src/api/eacp-crypto.ts
|
|
335
|
+
import {
|
|
336
|
+
constants,
|
|
337
|
+
createPrivateKey,
|
|
338
|
+
createPublicKey,
|
|
339
|
+
publicEncrypt
|
|
340
|
+
} from "crypto";
|
|
341
|
+
|
|
342
|
+
// src/api/admin.ts
|
|
343
|
+
async function changePasswordSafe(ctx, account, oldPassword, newPassword) {
|
|
344
|
+
await request(ctx, "/api/safe/v1/auth/change-password", {
|
|
375
345
|
method: "POST",
|
|
376
|
-
|
|
377
|
-
body: new URLSearchParams(params).toString()
|
|
346
|
+
body: { account, old_password: oldPassword, new_password: newPassword }
|
|
378
347
|
});
|
|
379
|
-
|
|
380
|
-
throw new Error(
|
|
381
|
-
`Token exchange failed (${res.status}): ${await res.text() || res.statusText}`
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
return mapToken(await res.json());
|
|
348
|
+
return { ok: true };
|
|
385
349
|
}
|
|
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";
|
|
350
|
+
|
|
351
|
+
// src/commands/auth.ts
|
|
352
|
+
async function resolveAccount(baseUrl, accessToken, insecure, idToken) {
|
|
353
|
+
const sub = decodeJwt(idToken ?? accessToken)?.sub;
|
|
354
|
+
if (!sub) return void 0;
|
|
422
355
|
try {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
356
|
+
const u = await getUserSafe(
|
|
357
|
+
{ baseUrl, token: accessToken, businessDomain: DEFAULT_BUSINESS_DOMAIN, insecure },
|
|
358
|
+
sub
|
|
359
|
+
);
|
|
360
|
+
return u.account;
|
|
428
361
|
} catch {
|
|
362
|
+
return void 0;
|
|
429
363
|
}
|
|
430
364
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const authUrl = buildAuthorizeUrl(base, client.clientId, redirectUri, state, challenge, scope);
|
|
440
|
-
const waiter = startCallbackServer(port);
|
|
441
|
-
if (opts.noBrowser) {
|
|
442
|
-
process.stderr.write(`Open this URL to log in:
|
|
443
|
-
${authUrl}
|
|
444
|
-
`);
|
|
445
|
-
} else {
|
|
446
|
-
process.stderr.write(`Opening browser for login\u2026
|
|
447
|
-
If it doesn't open, visit:
|
|
448
|
-
${authUrl}
|
|
449
|
-
`);
|
|
450
|
-
openBrowser(authUrl);
|
|
451
|
-
}
|
|
452
|
-
const { code, state: returned, close } = await waiter;
|
|
453
|
-
close();
|
|
454
|
-
if (returned && returned !== state) throw new Error("OAuth state mismatch \u2014 possible CSRF.");
|
|
455
|
-
return exchangeCode(base, code, redirectUri, client, verifier);
|
|
456
|
-
}
|
|
457
|
-
function mergeCookies(existing, res) {
|
|
458
|
-
const setCookies = typeof res.headers.getSetCookie === "function" ? res.headers.getSetCookie() : res.headers.get("set-cookie") ? [res.headers.get("set-cookie")] : [];
|
|
459
|
-
const map = /* @__PURE__ */ new Map();
|
|
460
|
-
const add = (pair) => {
|
|
461
|
-
const eq = pair.indexOf("=");
|
|
462
|
-
if (eq > 0) map.set(pair.slice(0, eq), pair.slice(eq + 1));
|
|
463
|
-
};
|
|
464
|
-
for (const p of existing.split(";").map((s) => s.trim()).filter(Boolean))
|
|
465
|
-
add(p);
|
|
466
|
-
for (const sc of setCookies) add(sc.split(";")[0]?.trim() ?? "");
|
|
467
|
-
return [...map.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
|
|
468
|
-
}
|
|
469
|
-
function parseSigninProps(html) {
|
|
470
|
-
const m = html.match(/<script[^>]*\bid=["']__NEXT_DATA__["'][^>]*>([\s\S]*?)<\/script>/i);
|
|
471
|
-
if (!m?.[1]) throw new Error("Could not find __NEXT_DATA__ on /oauth2/signin.");
|
|
472
|
-
const data = JSON.parse(m[1]);
|
|
473
|
-
const pp = data.props?.pageProps;
|
|
474
|
-
const csrftoken = pp?.csrftoken ?? pp?._csrf;
|
|
475
|
-
if (typeof csrftoken !== "string") throw new Error("Sign-in page did not expose csrftoken.");
|
|
476
|
-
return {
|
|
477
|
-
csrftoken,
|
|
478
|
-
challenge: typeof pp?.challenge === "string" ? pp.challenge : void 0,
|
|
479
|
-
remember: pp?.remember === true || pp?.remember === "true"
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
async function followToCallback(startUrl, jar0, state, redirectUri) {
|
|
483
|
-
let url = startUrl;
|
|
484
|
-
let jar = jar0;
|
|
485
|
-
const cb = new URL(redirectUri);
|
|
486
|
-
for (let hop = 0; hop < 40; hop++) {
|
|
487
|
-
const resp = await fetch(url, {
|
|
488
|
-
headers: { Cookie: jar, Accept: "text/html,*/*;q=0.8" },
|
|
489
|
-
redirect: "manual"
|
|
490
|
-
});
|
|
491
|
-
jar = mergeCookies(jar, resp);
|
|
492
|
-
if (![302, 303, 307, 308].includes(resp.status)) {
|
|
493
|
-
throw new Error(`Unexpected OAuth response (HTTP ${resp.status}).`);
|
|
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;
|
|
365
|
+
function promptLine(query, hidden = false) {
|
|
366
|
+
return new Promise((resolve2) => {
|
|
367
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
368
|
+
if (hidden) {
|
|
369
|
+
const mutable = rl;
|
|
370
|
+
mutable._writeToOutput = (s) => {
|
|
371
|
+
if (s.startsWith(query)) process.stdout.write(query);
|
|
372
|
+
};
|
|
505
373
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
374
|
+
rl.question(query, (answer) => {
|
|
375
|
+
rl.close();
|
|
376
|
+
if (hidden) process.stdout.write("\n");
|
|
377
|
+
resolve2(answer.trim());
|
|
378
|
+
});
|
|
379
|
+
});
|
|
509
380
|
}
|
|
510
|
-
|
|
511
|
-
const
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const { verifier, challenge } = generatePkce();
|
|
517
|
-
const state = randomBytes(12).toString("hex");
|
|
518
|
-
let jar = "";
|
|
519
|
-
const authResp = await fetch(
|
|
520
|
-
buildAuthorizeUrl(base, client.clientId, redirectUri, state, challenge, scope),
|
|
521
|
-
{ redirect: "manual" }
|
|
522
|
-
);
|
|
523
|
-
jar = mergeCookies(jar, authResp);
|
|
524
|
-
const authLoc = authResp.headers.get("location");
|
|
525
|
-
if (!authLoc) throw new Error(`/oauth2/auth did not redirect (HTTP ${authResp.status}).`);
|
|
526
|
-
const signinUrl = new URL(authLoc, base);
|
|
527
|
-
if (!signinUrl.pathname.includes("signin")) {
|
|
528
|
-
throw new Error(`Expected a sign-in redirect, got: ${authLoc}`);
|
|
381
|
+
function renderSessions(items) {
|
|
382
|
+
const byPlatform = /* @__PURE__ */ new Map();
|
|
383
|
+
for (const it of items) {
|
|
384
|
+
const arr = byPlatform.get(it.baseUrl) ?? [];
|
|
385
|
+
arr.push(it);
|
|
386
|
+
byPlatform.set(it.baseUrl, arr);
|
|
529
387
|
}
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
});
|
|
534
|
-
jar = mergeCookies(jar, pageResp);
|
|
535
|
-
const props = parseSigninProps(await pageResp.text());
|
|
536
|
-
const loginChallenge = signinUrl.searchParams.get("login_challenge")?.trim() || props.challenge?.trim();
|
|
537
|
-
if (!loginChallenge) throw new Error("Could not resolve the login challenge.");
|
|
538
|
-
const cipher = publicEncrypt(
|
|
539
|
-
{
|
|
540
|
-
key: opts.signinPublicKeyPem ?? STUDIOWEB_LOGIN_PUBLIC_KEY_PEM,
|
|
541
|
-
padding: cryptoConstants.RSA_PKCS1_PADDING
|
|
542
|
-
},
|
|
543
|
-
Buffer.from(password, "utf8")
|
|
544
|
-
).toString("base64");
|
|
545
|
-
const postResp = await fetch(`${base}/oauth2/signin`, {
|
|
546
|
-
method: "POST",
|
|
547
|
-
headers: {
|
|
548
|
-
Cookie: jar,
|
|
549
|
-
"Content-Type": "application/json",
|
|
550
|
-
Accept: "application/json, text/plain, */*",
|
|
551
|
-
Origin: new URL(base).origin,
|
|
552
|
-
Referer: signinUrl.href
|
|
553
|
-
},
|
|
554
|
-
body: JSON.stringify({
|
|
555
|
-
_csrf: props.csrftoken,
|
|
556
|
-
challenge: loginChallenge,
|
|
557
|
-
account: username,
|
|
558
|
-
password: cipher,
|
|
559
|
-
vcode: { id: "", content: "" },
|
|
560
|
-
dualfactorauthinfo: { validcode: { vcode: "" }, OTP: { OTP: "" } },
|
|
561
|
-
remember: props.remember ?? false,
|
|
562
|
-
device: { name: "", description: "", client_type: "console_web", udids: [] }
|
|
563
|
-
}),
|
|
564
|
-
redirect: "manual"
|
|
565
|
-
});
|
|
566
|
-
jar = mergeCookies(jar, postResp);
|
|
567
|
-
let code;
|
|
568
|
-
if ([302, 303, 307].includes(postResp.status)) {
|
|
569
|
-
const loc = postResp.headers.get("location");
|
|
570
|
-
if (!loc) throw new Error("Sign-in response missing Location.");
|
|
571
|
-
code = await followToCallback(new URL(loc, base).href, jar, state, redirectUri);
|
|
572
|
-
} else if (postResp.status === 200) {
|
|
573
|
-
const text = await postResp.text();
|
|
574
|
-
let json = null;
|
|
575
|
-
try {
|
|
576
|
-
json = JSON.parse(text);
|
|
577
|
-
} catch {
|
|
578
|
-
}
|
|
579
|
-
const redir = json && typeof json.redirect === "string" ? json.redirect : "";
|
|
580
|
-
if (!redir) {
|
|
581
|
-
const msg = json && typeof json.message === "string" ? json.message : text.slice(0, 300);
|
|
582
|
-
throw new InputError(`Sign-in failed: ${msg}`);
|
|
583
|
-
}
|
|
584
|
-
code = await followToCallback(new URL(redir, base).href, jar, state, redirectUri);
|
|
585
|
-
} else {
|
|
586
|
-
throw new InputError(
|
|
587
|
-
`Sign-in failed (HTTP ${postResp.status}): ${(await postResp.text()).slice(0, 300)}`
|
|
588
|
-
);
|
|
388
|
+
const lines = [];
|
|
389
|
+
for (const [platform, users] of byPlatform) {
|
|
390
|
+
lines.push(platform);
|
|
391
|
+
for (const u of users) lines.push(` ${u.active ? "*" : " "} ${u.username ?? u.userId}`);
|
|
589
392
|
}
|
|
590
|
-
return
|
|
393
|
+
return lines.join("\n") || "(no saved sessions)";
|
|
591
394
|
}
|
|
592
|
-
|
|
593
|
-
// src/commands/auth.ts
|
|
594
395
|
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
|
-
|
|
396
|
+
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(
|
|
397
|
+
"--port <n>",
|
|
398
|
+
"loopback redirect port for the auth_code flow",
|
|
399
|
+
(v) => Number.parseInt(v, 10)
|
|
400
|
+
).option("--device", "headless device-code login (RFC 8628) \u2014 no callback server, no password").option("--audience <aud>", "device-code token audience", "bkn-safe").option(
|
|
401
|
+
"--timeout <s>",
|
|
402
|
+
"device-login wait before timing out",
|
|
403
|
+
(v) => Number.parseInt(v, 10),
|
|
404
|
+
120
|
|
405
|
+
).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
406
|
const g = cmd2.optsWithGlobals();
|
|
600
407
|
if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
408
|
+
const out = outputOptions(cmd2);
|
|
409
|
+
const report = (r) => {
|
|
410
|
+
if (out.json || out.compact) {
|
|
411
|
+
printJson({ loggedIn: true, ...r }, out);
|
|
412
|
+
} else if (r.noAuth) {
|
|
413
|
+
process.stdout.write(`Registered ${r.baseUrl ?? url} (no authentication)
|
|
414
|
+
`);
|
|
415
|
+
} else {
|
|
416
|
+
process.stdout.write(`Logged in to ${r.baseUrl ?? url} as ${r.username ?? r.userId}
|
|
417
|
+
`);
|
|
418
|
+
}
|
|
419
|
+
};
|
|
601
420
|
const token = opts.token ?? g.token;
|
|
602
421
|
if (token) {
|
|
603
|
-
|
|
604
|
-
printJson({ loggedIn: true, ...r2 }, outputOptions(cmd2));
|
|
422
|
+
report(attachToken(url, token, { insecure: g.insecure }));
|
|
605
423
|
return;
|
|
606
424
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
425
|
+
if (opts.auth === false) {
|
|
426
|
+
report(attachNoAuth(url, { insecure: g.insecure }));
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const authStatus = await fetchAuthStatus(url);
|
|
430
|
+
if (authStatus && !authStatus.enabled) {
|
|
431
|
+
process.stderr.write(
|
|
432
|
+
`Platform auth is disabled (stack: ${authStatus.stack ?? "none"}) \u2014 registering without auth.
|
|
433
|
+
`
|
|
434
|
+
);
|
|
435
|
+
report(attachNoAuth(url, { insecure: g.insecure }));
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
let tokens;
|
|
439
|
+
let account;
|
|
440
|
+
try {
|
|
441
|
+
if (opts.username || opts.password) {
|
|
442
|
+
const username = opts.username ?? await promptLine("Username: ");
|
|
443
|
+
account = username;
|
|
444
|
+
const password = opts.password ?? await promptLine("Password: ", true);
|
|
445
|
+
tokens = await credentialDeviceLogin(url, username, password, {
|
|
446
|
+
clientId: opts.clientId,
|
|
447
|
+
audience: opts.audience,
|
|
448
|
+
timeoutMs: opts.timeout * 1e3
|
|
449
|
+
});
|
|
450
|
+
} else {
|
|
451
|
+
const openInBrowser = !opts.device && opts.browser !== false;
|
|
452
|
+
tokens = await deviceLogin(url, {
|
|
453
|
+
clientId: opts.clientId,
|
|
454
|
+
audience: opts.audience,
|
|
455
|
+
timeoutMs: opts.timeout * 1e3,
|
|
456
|
+
onPrompt: ({ userCode, verificationUri, verificationUriComplete }) => {
|
|
457
|
+
const target = verificationUriComplete ?? verificationUri;
|
|
458
|
+
process.stderr.write(
|
|
459
|
+
`
|
|
460
|
+
Open this URL to sign in and authorize:
|
|
461
|
+
${target}
|
|
462
|
+
User code: ${userCode}
|
|
463
|
+
`
|
|
464
|
+
);
|
|
465
|
+
if (openInBrowser) openBrowser(target);
|
|
466
|
+
process.stderr.write("Waiting for authorization\u2026\n");
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
} catch (e) {
|
|
471
|
+
if (e instanceof Error && /Device auth failed \(404\)/.test(e.message)) {
|
|
472
|
+
process.stderr.write("No auth endpoint found \u2014 registering platform without auth.\n");
|
|
473
|
+
report(attachNoAuth(url, { insecure: g.insecure }));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
throw e;
|
|
477
|
+
}
|
|
478
|
+
if (!account) {
|
|
479
|
+
account = await resolveAccount(
|
|
480
|
+
url,
|
|
481
|
+
tokens.accessToken,
|
|
482
|
+
Boolean(g.insecure),
|
|
483
|
+
tokens.idToken
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
report(
|
|
487
|
+
attachToken(url, tokens.accessToken, {
|
|
488
|
+
refreshToken: tokens.refreshToken,
|
|
489
|
+
idToken: tokens.idToken,
|
|
490
|
+
insecure: g.insecure,
|
|
491
|
+
username: account
|
|
492
|
+
})
|
|
493
|
+
);
|
|
623
494
|
});
|
|
624
495
|
cmd.command("status").description("Show base URL and whether a token is configured").action((_opts, cmd2) => printJson(status(), outputOptions(cmd2)));
|
|
625
496
|
cmd.command("token").description("Print the current access token (keep secret)").action(() => {
|
|
@@ -629,7 +500,13 @@ function registerAuthLeaves(cmd) {
|
|
|
629
500
|
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
501
|
(_url, _opts, cmd2) => printJson(whoami(), outputOptions(cmd2))
|
|
631
502
|
);
|
|
632
|
-
cmd.command("list").alias("ls").description("List
|
|
503
|
+
cmd.command("list").alias("ls").description("List saved sessions (platform \u2192 users; * = active)").action((_opts, cmd2) => {
|
|
504
|
+
const items = listPlatforms();
|
|
505
|
+
const out = outputOptions(cmd2);
|
|
506
|
+
if (out.json || out.compact) printJson(items, out);
|
|
507
|
+
else process.stdout.write(`${renderSessions(items)}
|
|
508
|
+
`);
|
|
509
|
+
});
|
|
633
510
|
cmd.command("use <url>").description("Switch the active platform").action((url, _opts, cmd2) => {
|
|
634
511
|
use(url);
|
|
635
512
|
printJson(status(), outputOptions(cmd2));
|
|
@@ -638,28 +515,56 @@ function registerAuthLeaves(cmd) {
|
|
|
638
515
|
cmd.command("delete <url>").description("Delete saved credentials for a platform").action(
|
|
639
516
|
(url, _opts, cmd2) => printJson({ deleted: deletePlatform(url) }, outputOptions(cmd2))
|
|
640
517
|
);
|
|
641
|
-
cmd.command("switch <url> <user
|
|
642
|
-
|
|
518
|
+
cmd.command("switch <url> <user>").description("Switch the active user for a platform (by username or user id)").action((url, user, _opts, cmd2) => {
|
|
519
|
+
const r = switchUser(url, user);
|
|
520
|
+
const out = outputOptions(cmd2);
|
|
521
|
+
if (out.json || out.compact) printJson(r, out);
|
|
522
|
+
else process.stdout.write(`Switched to ${r.username ?? r.userId} on ${r.baseUrl}
|
|
523
|
+
`);
|
|
643
524
|
});
|
|
644
|
-
cmd.command("users <url>").description("List saved
|
|
645
|
-
|
|
525
|
+
cmd.command("users <url>").description("List saved users for a platform (* = active)").action((url, _opts, cmd2) => {
|
|
526
|
+
const norm = url.replace(/\/+$/, "");
|
|
527
|
+
const items = listPlatforms().filter((i) => i.baseUrl === norm);
|
|
528
|
+
const out = outputOptions(cmd2);
|
|
529
|
+
if (out.json || out.compact) printJson(items, out);
|
|
530
|
+
else process.stdout.write(`${renderSessions(items)}
|
|
531
|
+
`);
|
|
646
532
|
});
|
|
647
533
|
cmd.command("export").description("Export the active session's tokens (for a headless host)").action((_opts, cmd2) => {
|
|
648
534
|
printJson(exportCreds(), outputOptions(cmd2));
|
|
649
535
|
});
|
|
650
|
-
cmd.command("change-password [url]").description("Change your account password (
|
|
536
|
+
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
537
|
const g = cmd2.optsWithGlobals();
|
|
538
|
+
if (g.insecure) process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
652
539
|
const ctx = resolveContext({
|
|
653
|
-
baseUrl: g.baseUrl,
|
|
540
|
+
baseUrl: url ?? g.baseUrl,
|
|
654
541
|
token: g.token,
|
|
655
542
|
user: g.user,
|
|
656
543
|
businessDomain: g.bizDomain,
|
|
657
544
|
insecure: g.insecure
|
|
658
545
|
});
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
)
|
|
546
|
+
const account = opts.account ?? await promptLine("Account: ");
|
|
547
|
+
const oldPassword = opts.oldPassword ?? await promptLine("Current password: ", true);
|
|
548
|
+
let newPassword = opts.newPassword;
|
|
549
|
+
if (!newPassword) {
|
|
550
|
+
newPassword = await promptLine("New password: ", true);
|
|
551
|
+
const confirm = await promptLine("Confirm new password: ", true);
|
|
552
|
+
if (newPassword !== confirm) throw new Error("New passwords do not match.");
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
printJson(
|
|
556
|
+
await changePasswordSafe(ctx, account, oldPassword, newPassword),
|
|
557
|
+
outputOptions(cmd2)
|
|
558
|
+
);
|
|
559
|
+
} catch (e) {
|
|
560
|
+
if (e instanceof HttpError && e.status === 401) {
|
|
561
|
+
throw new InputError("Wrong account or current password.");
|
|
562
|
+
}
|
|
563
|
+
if (e instanceof HttpError && e.status === 400) {
|
|
564
|
+
throw new InputError("New password must differ from the current one.");
|
|
565
|
+
}
|
|
566
|
+
throw e;
|
|
567
|
+
}
|
|
663
568
|
});
|
|
664
569
|
}
|
|
665
570
|
function authCommand() {
|
|
@@ -840,6 +745,41 @@ function adminCommand() {
|
|
|
840
745
|
outputOptions(cmd)
|
|
841
746
|
);
|
|
842
747
|
});
|
|
748
|
+
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) => {
|
|
749
|
+
printJson(
|
|
750
|
+
await clientFrom(cmd).admin.roleCreate(opts.name, opts.description),
|
|
751
|
+
outputOptions(cmd)
|
|
752
|
+
);
|
|
753
|
+
});
|
|
754
|
+
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) => {
|
|
755
|
+
printJson(
|
|
756
|
+
await clientFrom(cmd).admin.roleUpdate(roleId, {
|
|
757
|
+
name: opts.name,
|
|
758
|
+
description: opts.description
|
|
759
|
+
}),
|
|
760
|
+
outputOptions(cmd)
|
|
761
|
+
);
|
|
762
|
+
});
|
|
763
|
+
role.command("delete <role>").description("Delete a custom role (403 on built-in)").option("-y, --yes", "skip confirmation").action(async (roleId, _opts, cmd) => {
|
|
764
|
+
printJson(await clientFrom(cmd).admin.roleDelete(roleId), outputOptions(cmd));
|
|
765
|
+
});
|
|
766
|
+
for (const [verb, grant] of [
|
|
767
|
+
["grant-perm", true],
|
|
768
|
+
["revoke-perm", false]
|
|
769
|
+
]) {
|
|
770
|
+
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) => {
|
|
771
|
+
printJson(
|
|
772
|
+
await clientFrom(cmd).admin.rolePermission(
|
|
773
|
+
roleId,
|
|
774
|
+
grant,
|
|
775
|
+
opts.resourceType,
|
|
776
|
+
opts.resourceId,
|
|
777
|
+
csv(opts.operations) ?? []
|
|
778
|
+
),
|
|
779
|
+
outputOptions(cmd)
|
|
780
|
+
);
|
|
781
|
+
});
|
|
782
|
+
}
|
|
843
783
|
const modelBody = (opts) => {
|
|
844
784
|
if (opts.body || opts.bodyFile) return readBody(opts);
|
|
845
785
|
const mc = {};
|
|
@@ -1075,7 +1015,7 @@ function agentCommand() {
|
|
|
1075
1015
|
import { Command as Command4 } from "commander";
|
|
1076
1016
|
|
|
1077
1017
|
// src/utils/bkn-validate.ts
|
|
1078
|
-
import { existsSync, readFileSync as
|
|
1018
|
+
import { existsSync, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
|
|
1079
1019
|
import { join, resolve } from "path";
|
|
1080
1020
|
var BKN_OBJECT_NAME_MAX_LENGTH = 40;
|
|
1081
1021
|
function parseFrontmatter(text) {
|
|
@@ -1128,7 +1068,7 @@ function validateBknDirectory(dirPath) {
|
|
|
1128
1068
|
if (!existsSync(networkPath)) {
|
|
1129
1069
|
errors.push("Missing network.bkn at the BKN root.");
|
|
1130
1070
|
} else {
|
|
1131
|
-
const fm = parseFrontmatter(
|
|
1071
|
+
const fm = parseFrontmatter(readFileSync2(networkPath, "utf8"));
|
|
1132
1072
|
if (!fm) errors.push("network.bkn has no frontmatter block.");
|
|
1133
1073
|
else {
|
|
1134
1074
|
if (fm.type !== "knowledge_network")
|
|
@@ -1140,7 +1080,7 @@ function validateBknDirectory(dirPath) {
|
|
|
1140
1080
|
const otIds = /* @__PURE__ */ new Set();
|
|
1141
1081
|
const otFiles = bknFiles(join(dir, "object_types"));
|
|
1142
1082
|
for (const file of otFiles) {
|
|
1143
|
-
const fm = parseFrontmatter(
|
|
1083
|
+
const fm = parseFrontmatter(readFileSync2(file, "utf8"));
|
|
1144
1084
|
const rel = file.slice(dir.length + 1);
|
|
1145
1085
|
if (!fm || fm.type !== "object_type") {
|
|
1146
1086
|
errors.push(`${rel}: not a valid object_type (missing/wrong frontmatter type).`);
|
|
@@ -1160,7 +1100,7 @@ function validateBknDirectory(dirPath) {
|
|
|
1160
1100
|
}
|
|
1161
1101
|
const rtFiles = bknFiles(join(dir, "relation_types"));
|
|
1162
1102
|
for (const file of rtFiles) {
|
|
1163
|
-
const text =
|
|
1103
|
+
const text = readFileSync2(file, "utf8");
|
|
1164
1104
|
const fm = parseFrontmatter(text);
|
|
1165
1105
|
const rel = file.slice(dir.length + 1);
|
|
1166
1106
|
if (!fm || fm.type !== "relation_type") {
|
|
@@ -1754,7 +1694,7 @@ function dataflowCommand() {
|
|
|
1754
1694
|
}
|
|
1755
1695
|
|
|
1756
1696
|
// src/commands/explore.ts
|
|
1757
|
-
import { createServer
|
|
1697
|
+
import { createServer } from "http";
|
|
1758
1698
|
import { Command as Command9 } from "commander";
|
|
1759
1699
|
var int6 = (v) => Number.parseInt(v, 10);
|
|
1760
1700
|
var ROUTES = {
|
|
@@ -1806,7 +1746,7 @@ function exploreCommand() {
|
|
|
1806
1746
|
);
|
|
1807
1747
|
cmd.option("--port <n>", "port to listen on", int6, 7777).option("--host <h>", "host to bind", "127.0.0.1").action(async (opts, command) => {
|
|
1808
1748
|
const client = clientFrom(command);
|
|
1809
|
-
const server =
|
|
1749
|
+
const server = createServer((reqMsg, res) => {
|
|
1810
1750
|
void handle(client, reqMsg, res);
|
|
1811
1751
|
});
|
|
1812
1752
|
server.listen(opts.port, opts.host, () => {
|
|
@@ -2147,11 +2087,11 @@ function toolCommand() {
|
|
|
2147
2087
|
}
|
|
2148
2088
|
|
|
2149
2089
|
// src/commands/trace.ts
|
|
2150
|
-
import { readFileSync as
|
|
2090
|
+
import { readFileSync as readFileSync4, writeFileSync } from "fs";
|
|
2151
2091
|
import { Command as Command14 } from "commander";
|
|
2152
2092
|
|
|
2153
2093
|
// src/trace-ai/schema-validate.ts
|
|
2154
|
-
import { readFileSync as
|
|
2094
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2155
2095
|
import { extname } from "path";
|
|
2156
2096
|
import yaml from "js-yaml";
|
|
2157
2097
|
import { z } from "zod";
|
|
@@ -2188,7 +2128,7 @@ var DiagnosisRule = z.object({
|
|
|
2188
2128
|
params: z.record(z.string(), z.unknown()).optional()
|
|
2189
2129
|
});
|
|
2190
2130
|
function parseFile(file) {
|
|
2191
|
-
const text =
|
|
2131
|
+
const text = readFileSync3(file, "utf8");
|
|
2192
2132
|
const ext = extname(file).toLowerCase();
|
|
2193
2133
|
if (ext === ".yaml" || ext === ".yml") return yaml.load(text);
|
|
2194
2134
|
return JSON.parse(text);
|
|
@@ -2255,7 +2195,7 @@ function traceCommand() {
|
|
|
2255
2195
|
});
|
|
2256
2196
|
const evalSet = cmd.command("eval-set").description("Build + run trace eval sets");
|
|
2257
2197
|
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(
|
|
2198
|
+
const raw = JSON.parse(readFileSync4(queriesFile, "utf8"));
|
|
2259
2199
|
const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
|
|
2260
2200
|
if (opts.out) {
|
|
2261
2201
|
writeFileSync(opts.out, JSON.stringify({ cases }, null, 2));
|
|
@@ -2265,7 +2205,7 @@ function traceCommand() {
|
|
|
2265
2205
|
}
|
|
2266
2206
|
});
|
|
2267
2207
|
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(
|
|
2208
|
+
const raw = JSON.parse(readFileSync4(casesFile, "utf8"));
|
|
2269
2209
|
const cases = clientFrom(cmd2).trace.evalSetBuild(raw);
|
|
2270
2210
|
const result = await clientFrom(cmd2).trace.evalSetTest(opts.agent, cases, {
|
|
2271
2211
|
version: opts.version,
|