@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.
@@ -38,8 +38,12 @@ function toExitCode(err) {
38
38
  }
39
39
  function formatError(err) {
40
40
  if (err instanceof HttpError) {
41
- if (err.status === 401 || err.status === 403) {
42
- return `Not authorized (HTTP ${err.status}). Run \`openbkn auth login\` and retry.`;
41
+ const serverMsg = serverError(err.body);
42
+ if (err.status === 401) {
43
+ return `Not authorized (HTTP 401)${serverMsg ? `: ${serverMsg}` : ""}. Run \`openbkn auth login\` and retry.`;
44
+ }
45
+ if (err.status === 403) {
46
+ return `Forbidden (HTTP 403)${serverMsg ? `: ${serverMsg}` : " \u2014 admin privileges required"}.`;
43
47
  }
44
48
  const detail = err.body ? `: ${truncate(err.body, 500)}` : "";
45
49
  return `Request failed (HTTP ${err.status} ${err.statusText})${detail}`;
@@ -56,6 +60,16 @@ function formatError(err) {
56
60
  }
57
61
  return String(err);
58
62
  }
63
+ function serverError(body) {
64
+ if (!body) return "";
65
+ try {
66
+ const j = JSON.parse(body);
67
+ const m = j.error ?? j.detail ?? j.description ?? j.message;
68
+ return typeof m === "string" ? m : "";
69
+ } catch {
70
+ return "";
71
+ }
72
+ }
59
73
  function isTlsCertError(code) {
60
74
  return code === "DEPTH_ZERO_SELF_SIGNED_CERT" || code === "SELF_SIGNED_CERT_IN_CHAIN" || code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE" || code === "CERT_HAS_EXPIRED" || code.includes("CERT");
61
75
  }
@@ -220,23 +234,228 @@ function resolveContext(opts = {}) {
220
234
  }
221
235
  const normalized = baseUrl.replace(/\/+$/, "");
222
236
  const stored = readToken(normalized);
223
- const token = opts.token ?? process.env.BKN_TOKEN ?? stored?.accessToken;
224
- if (!token) {
237
+ const explicit = opts.token ?? process.env.BKN_TOKEN;
238
+ const token = explicit ?? stored?.accessToken ?? "";
239
+ if (!token && !stored?.noAuth) {
225
240
  throw new InputError("No access token. Set BKN_TOKEN or run `openbkn auth login`.");
226
241
  }
242
+ const insecure = opts.insecure ?? stored?.tlsInsecure ?? false;
243
+ const refresh = !explicit && stored?.refreshToken ? {
244
+ refreshToken: stored.refreshToken,
245
+ persist: (t) => {
246
+ writeToken(normalized, {
247
+ ...stored,
248
+ accessToken: t.accessToken,
249
+ refreshToken: t.refreshToken ?? stored.refreshToken,
250
+ idToken: t.idToken ?? stored.idToken
251
+ });
252
+ }
253
+ } : void 0;
227
254
  return {
228
255
  baseUrl: normalized,
229
256
  token,
230
257
  businessDomain: opts.businessDomain ?? readPlatformConfig(normalized).businessDomain ?? DEFAULT_BUSINESS_DOMAIN,
231
- insecure: opts.insecure ?? stored?.tlsInsecure ?? false
258
+ insecure,
259
+ ...refresh ? { refresh } : {}
232
260
  };
233
261
  }
234
262
 
263
+ // src/auth/oauth.ts
264
+ import { spawn } from "child_process";
265
+ function normalizeBaseUrl(value) {
266
+ return value.replace(/\/+$/, "");
267
+ }
268
+ function mapToken(data) {
269
+ return {
270
+ accessToken: data.access_token,
271
+ refreshToken: data.refresh_token,
272
+ idToken: data.id_token
273
+ };
274
+ }
275
+ function openBrowser(url) {
276
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
277
+ try {
278
+ spawn(cmd, [url], {
279
+ stdio: "ignore",
280
+ detached: true,
281
+ shell: process.platform === "win32"
282
+ }).unref();
283
+ } catch {
284
+ }
285
+ }
286
+ function mergeCookies(existing, res) {
287
+ const setCookies = typeof res.headers.getSetCookie === "function" ? res.headers.getSetCookie() : res.headers.get("set-cookie") ? [res.headers.get("set-cookie")] : [];
288
+ const map = /* @__PURE__ */ new Map();
289
+ const add = (pair) => {
290
+ const eq = pair.indexOf("=");
291
+ if (eq > 0) map.set(pair.slice(0, eq), pair.slice(eq + 1));
292
+ };
293
+ for (const p of existing.split(";").map((s) => s.trim()).filter(Boolean))
294
+ add(p);
295
+ for (const sc of setCookies) add(sc.split(";")[0]?.trim() ?? "");
296
+ return [...map.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
297
+ }
298
+ async function fetchAuthStatus(baseUrl) {
299
+ try {
300
+ const res = await fetch(`${normalizeBaseUrl(baseUrl)}/install-status.json`, {
301
+ headers: { Accept: "application/json" }
302
+ });
303
+ if (!res.ok) return null;
304
+ const j = await res.json();
305
+ if (!j.auth) return null;
306
+ return { enabled: Boolean(j.auth.enabled), stack: j.auth.stack };
307
+ } catch {
308
+ return null;
309
+ }
310
+ }
311
+ async function refreshAccessToken(baseUrl, refreshToken, clientId = "openbkn-sdk") {
312
+ const base = normalizeBaseUrl(baseUrl);
313
+ const res = await fetch(`${base}/oauth2/token`, {
314
+ method: "POST",
315
+ headers: {
316
+ "Content-Type": "application/x-www-form-urlencoded",
317
+ Accept: "application/json"
318
+ },
319
+ body: new URLSearchParams({
320
+ grant_type: "refresh_token",
321
+ refresh_token: refreshToken,
322
+ client_id: clientId
323
+ }).toString()
324
+ });
325
+ if (!res.ok) {
326
+ throw new Error(
327
+ `Token refresh failed (${res.status}): ${await res.text() || res.statusText}`
328
+ );
329
+ }
330
+ return mapToken(await res.json());
331
+ }
332
+ var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
333
+ var DEFAULT_DEVICE_CLIENT_ID = "openbkn-sdk";
334
+ var DEVICE_SCOPE = "openid offline";
335
+ var form = (body) => ({
336
+ method: "POST",
337
+ headers: {
338
+ "Content-Type": "application/x-www-form-urlencoded",
339
+ Accept: "application/json"
340
+ },
341
+ body: new URLSearchParams(body).toString()
342
+ });
343
+ async function requestDeviceCode(base, clientId, scope, audience) {
344
+ const params = { client_id: clientId, scope };
345
+ if (audience) params.audience = audience;
346
+ const res = await fetch(`${base}/oauth2/device/auth`, form(params));
347
+ if (!res.ok) {
348
+ throw new Error(`Device auth failed (${res.status}): ${await res.text() || res.statusText}`);
349
+ }
350
+ const da = await res.json();
351
+ if (!da.verification_uri) {
352
+ throw new Error(
353
+ "Device auth returned no verification_uri \u2014 is Hydra's device flow configured?"
354
+ );
355
+ }
356
+ return da;
357
+ }
358
+ async function pollDeviceToken(base, deviceCode, clientId, intervalMs, windowMs) {
359
+ let interval = intervalMs;
360
+ const deadline = Date.now() + windowMs;
361
+ while (Date.now() < deadline) {
362
+ await new Promise((r) => setTimeout(r, interval));
363
+ const tokRes = await fetch(
364
+ `${base}/oauth2/token`,
365
+ form({ grant_type: DEVICE_GRANT, device_code: deviceCode, client_id: clientId })
366
+ );
367
+ const data = await tokRes.json().catch(() => ({}));
368
+ if (tokRes.ok) return mapToken(data);
369
+ switch (data.error) {
370
+ case "authorization_pending":
371
+ break;
372
+ // keep polling
373
+ case "slow_down":
374
+ interval += 5e3;
375
+ break;
376
+ case "access_denied":
377
+ throw new InputError("Device authorization denied.");
378
+ case "expired_token":
379
+ throw new InputError("Device code expired \u2014 run login again.");
380
+ default:
381
+ throw new Error(`Device token poll failed: ${String(data.error ?? tokRes.status)}`);
382
+ }
383
+ }
384
+ throw new InputError("Device login timed out.");
385
+ }
386
+ async function deviceLogin(baseUrl, opts = {}) {
387
+ const base = normalizeBaseUrl(baseUrl);
388
+ const clientId = opts.clientId ?? DEFAULT_DEVICE_CLIENT_ID;
389
+ const da = await requestDeviceCode(base, clientId, opts.scope ?? DEVICE_SCOPE, opts.audience);
390
+ opts.onPrompt?.({
391
+ userCode: da.user_code,
392
+ verificationUri: onBaseHost(base, da.verification_uri),
393
+ verificationUriComplete: da.verification_uri_complete ? onBaseHost(base, da.verification_uri_complete) : void 0
394
+ });
395
+ const windowMs = Math.min(opts.timeoutMs ?? Number.POSITIVE_INFINITY, da.expires_in * 1e3);
396
+ return pollDeviceToken(base, da.device_code, clientId, (da.interval ?? 5) * 1e3, windowMs);
397
+ }
398
+ function onBaseHost(base, uri) {
399
+ try {
400
+ const u = new URL(uri);
401
+ const b = new URL(base);
402
+ return `${b.origin}${u.pathname}${u.search}`;
403
+ } catch {
404
+ return uri;
405
+ }
406
+ }
407
+ async function credentialDeviceLogin(baseUrl, username, password, opts = {}) {
408
+ const base = normalizeBaseUrl(baseUrl);
409
+ const clientId = opts.clientId ?? DEFAULT_DEVICE_CLIENT_ID;
410
+ const da = await requestDeviceCode(base, clientId, opts.scope ?? DEVICE_SCOPE, opts.audience);
411
+ let jar = "";
412
+ const hop = async (url, init) => {
413
+ const r = await fetch(url, {
414
+ method: init?.method ?? "GET",
415
+ headers: { Cookie: jar, Accept: "text/html,*/*;q=0.8", ...init?.headers ?? {} },
416
+ body: init?.body,
417
+ redirect: "manual"
418
+ });
419
+ jar = mergeCookies(jar, r);
420
+ return r;
421
+ };
422
+ const postForm = (path, body) => hop(`${base}${path}`, {
423
+ method: "POST",
424
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
425
+ body: new URLSearchParams(body).toString()
426
+ });
427
+ const v = await hop(`${base}/oauth2/device/verify?user_code=${encodeURIComponent(da.user_code)}`);
428
+ let loc = v.headers.get("location");
429
+ for (let i = 0; i < 25 && loc; i++) {
430
+ const u = new URL(loc, base);
431
+ let r;
432
+ if (u.pathname.endsWith("/device")) {
433
+ const dc = u.searchParams.get("device_challenge");
434
+ if (!dc) throw new Error(`/device without device_challenge: ${loc}`);
435
+ r = await postForm("/device", { device_challenge: dc, user_code: da.user_code });
436
+ } else if (u.pathname.endsWith("/login")) {
437
+ const lc = u.searchParams.get("login_challenge");
438
+ if (!lc) throw new Error(`/login without login_challenge: ${loc}`);
439
+ r = await postForm("/login", { login_challenge: lc, account: username, password });
440
+ if (r.status === 401) throw new InputError("Sign-in failed: wrong account or password");
441
+ } else if (u.pathname.endsWith("/consent")) {
442
+ const cc = u.searchParams.get("consent_challenge");
443
+ if (!cc) throw new Error(`/consent without consent_challenge: ${loc}`);
444
+ r = await postForm("/consent", { consent_challenge: cc, decision: "allow" });
445
+ } else {
446
+ r = await hop(u.href);
447
+ }
448
+ loc = r.headers.get("location");
449
+ }
450
+ const windowMs = Math.min(opts.timeoutMs ?? Number.POSITIVE_INFINITY, da.expires_in * 1e3);
451
+ return pollDeviceToken(base, da.device_code, clientId, (da.interval ?? 5) * 1e3, windowMs);
452
+ }
453
+
235
454
  // src/api/headers.ts
236
455
  function buildHeaders(ctx, extra) {
237
456
  return {
238
- authorization: `Bearer ${ctx.token}`,
239
- token: ctx.token,
457
+ // No token = a no-auth platform (no bkn-safe); send no Authorization.
458
+ ...ctx.token ? { authorization: `Bearer ${ctx.token}`, token: ctx.token } : {},
240
459
  "x-business-domain": ctx.businessDomain,
241
460
  ...extra
242
461
  };
@@ -260,16 +479,20 @@ async function request(ctx, path, init = {}) {
260
479
  const hasBody = init.body !== void 0;
261
480
  const controller = new AbortController();
262
481
  const timer = setTimeout(() => controller.abort(), init.timeoutMs ?? DEFAULT_TIMEOUT_MS);
482
+ const send = () => fetch(url, {
483
+ method: init.method ?? (hasBody ? "POST" : "GET"),
484
+ headers: buildHeaders(ctx, {
485
+ ...hasBody ? { "content-type": "application/json" } : {},
486
+ ...init.headers
487
+ }),
488
+ body: hasBody ? JSON.stringify(init.body) : void 0,
489
+ signal: controller.signal
490
+ });
263
491
  try {
264
- const res = await fetch(url, {
265
- method: init.method ?? (hasBody ? "POST" : "GET"),
266
- headers: buildHeaders(ctx, {
267
- ...hasBody ? { "content-type": "application/json" } : {},
268
- ...init.headers
269
- }),
270
- body: hasBody ? JSON.stringify(init.body) : void 0,
271
- signal: controller.signal
272
- });
492
+ let res = await send();
493
+ if (res.status === 401 && ctx.refresh && await tryRefresh(ctx)) {
494
+ res = await send();
495
+ }
273
496
  const text = await res.text();
274
497
  if (!res.ok) throw new HttpError(res.status, res.statusText, text);
275
498
  return text ? JSON.parse(text) : void 0;
@@ -277,364 +500,244 @@ async function request(ctx, path, init = {}) {
277
500
  clearTimeout(timer);
278
501
  }
279
502
  }
280
-
281
- // src/api/eacp-crypto.ts
282
- import {
283
- constants,
284
- createPrivateKey,
285
- createPublicKey,
286
- publicEncrypt
287
- } from "crypto";
288
- var EACP_MODIFYPWD_PRIVATE_KEY_PEM = `-----BEGIN RSA PRIVATE KEY-----
289
- MIICXgIBAAKBgQDB2fhLla9rMx+6LWTXajnK11Kdp520s1Q+TfPfIXI/7G9+L2YC
290
- 4RA3M5rgRi32s5+UFQ/CVqUFqMqVuzaZ4lw/uEdk1qHcP0g6LB3E9wkl2FclFR0M
291
- +/HrWmxPoON+0y/tFQxxfNgsUodFzbdh0XY1rIVUIbPLvufUBbLKXHDPpwIDAQAB
292
- AoGBALCM/H6ajXFs1nCR903aCVicUzoS9qckzI0SIhIOPCfMBp8+PAJTSJl9/ohU
293
- YnhVj/kmVXwBvboxyJAmOcxdRPWL7iTk5nA1oiVXMer3Wby+tRg/ls91xQbJLVv3
294
- oGSt7q0CXxJpRH2oYkVVlMMlZUwKz3ovHiLKAnhw+jEsdL2BAkEA9hA97yyeA2eq
295
- f9dMu/ici99R3WJRRtk4NEI4WShtWPyziDg48d3SOzYmhEJjPuOo3g1ze01os70P
296
- ApE7d0qcyQJBAMmt+FR8h5MwxPQPAzjh/fTuTttvUfBeMiUDrIycK1I/L96lH+fU
297
- i4Nu+7TPOzExnPeGO5UJbZxrpIEUB7Zs8O8CQQCLzTCTGiNwxc5eMgH77kVrRudp
298
- Q7nv6ex/7Hu9VDXEUFbkdyULbj9KuvppPJrMmWZROw04qgNp02mayM8jeLXZAkEA
299
- o+PM/pMn9TPXiWE9xBbaMhUKXgXLd2KEq1GeAbHS/oY8l1hmYhV1vjwNLbSNrH9d
300
- yEP73TQJL+jFiONHFTbYXwJAU03Xgum5mLIkX/02LpOrz2QCdfX1IMJk2iKi9osV
301
- KqfbvHsF0+GvFGg18/FXStG9Kr4TjqLsygQJT76/MnMluw==
302
- -----END RSA PRIVATE KEY-----`;
303
- var cachedKey;
304
- function publicKey() {
305
- if (!cachedKey) cachedKey = createPublicKey(createPrivateKey(EACP_MODIFYPWD_PRIVATE_KEY_PEM));
306
- return cachedKey;
307
- }
308
- function encryptModifyPwd(plain) {
309
- const buf = publicEncrypt(
310
- { key: publicKey(), padding: constants.RSA_PKCS1_PADDING },
311
- Buffer.from(plain, "utf8")
312
- );
313
- return buf.toString("base64");
503
+ async function tryRefresh(ctx) {
504
+ if (!ctx.refresh) return false;
505
+ try {
506
+ const t = await refreshAccessToken(ctx.baseUrl, ctx.refresh.refreshToken, ctx.refresh.clientId);
507
+ ctx.token = t.accessToken;
508
+ if (t.refreshToken) ctx.refresh.refreshToken = t.refreshToken;
509
+ ctx.refresh.persist(t);
510
+ return true;
511
+ } catch {
512
+ return false;
513
+ }
314
514
  }
315
515
 
316
- // src/api/admin.ts
317
- var UM = "/api/user-management/v1";
318
- var AUTHZ = "/api/authorization/v1";
319
- var ISFWEB = "/isfweb/api/ShareMgnt";
320
- async function callerUserId(ctx) {
321
- const sub = decodeJwt(ctx.token)?.sub;
322
- if (sub) return sub;
323
- const info = await request(ctx, "/api/eacp/v1/user/get");
324
- if (info?.userid) return info.userid;
325
- throw new Error(
326
- "Cannot resolve the caller user id (token has no JWT `sub` and eacp/v1/user/get returned no `userid`). Re-login."
516
+ // src/api/safe.ts
517
+ var ADMIN = "/api/safe/v1/admin";
518
+ function notOnSafe(operation) {
519
+ throw new InputError(
520
+ `'${operation}' is not available on bkn-safe \u2014 its admin API has no such endpoint. See docs/exec-plans/admin-bkn-safe-migration.md.`
327
521
  );
328
522
  }
329
- async function shareMgnt(ctx, method, params = []) {
330
- try {
331
- return await request(ctx, `${ISFWEB}/${method}`, { method: "POST", body: params });
332
- } catch (e) {
333
- if (e instanceof HttpError) {
334
- try {
335
- const parsed = JSON.parse(e.body);
336
- const err = parsed?.error;
337
- if (err?.errMsg) {
338
- const m = err.errID !== void 0 ? `${err.errMsg} (errID=${err.errID})` : err.errMsg;
339
- throw new Error(`ShareMgnt.${method} failed: ${m}`);
340
- }
341
- } catch (inner) {
342
- if (inner instanceof Error && inner.message.startsWith("ShareMgnt.")) throw inner;
343
- }
344
- }
345
- throw e;
346
- }
347
- }
348
- var DEPT_FIELDS = "name,code,remark,manager,enabled,parent_deps,email";
349
- var USER_FIELDS = "name,account,email,enabled,frozen,parent_deps,roles";
350
- function listDepartments(ctx, opts = {}) {
351
- return request(ctx, `${UM}/console/search-departments/${DEPT_FIELDS}`, {
352
- query: {
353
- role: opts.role ?? "super_admin",
354
- offset: opts.offset ?? 0,
355
- limit: opts.limit ?? 100,
356
- name: opts.name || void 0
357
- }
523
+ function listUsersSafe(ctx, opts = {}) {
524
+ return request(ctx, `${ADMIN}/users`, {
525
+ query: { search: opts.search || void 0, offset: opts.offset, limit: opts.limit }
358
526
  });
359
527
  }
360
- async function listAllDepartments(ctx, role = "super_admin", pageSize = 100) {
361
- const out = [];
362
- let offset = 0;
363
- for (; ; ) {
364
- const data = await listDepartments(ctx, { role, offset, limit: pageSize });
365
- const entries = data.entries ?? [];
366
- out.push(...entries);
367
- if (entries.length < pageSize) break;
368
- if (data.total_count !== void 0 && out.length >= data.total_count) break;
369
- offset += pageSize;
370
- }
371
- return out;
528
+ function getUserSafe(ctx, userId) {
529
+ return request(ctx, `${ADMIN}/users/${encodeURIComponent(userId)}`);
372
530
  }
373
- function listUsers(ctx, opts = {}) {
374
- return request(ctx, `${UM}/console/search-users/${USER_FIELDS}`, {
375
- query: {
376
- role: opts.role ?? "super_admin",
377
- offset: opts.offset ?? 0,
378
- limit: opts.limit ?? 100,
379
- department_id: opts.orgId || void 0,
380
- name: opts.name || void 0
531
+ function createUserSafe(ctx, input) {
532
+ return request(ctx, `${ADMIN}/users`, {
533
+ method: "POST",
534
+ body: {
535
+ account: input.account,
536
+ password: input.password,
537
+ ...input.name ? { name: input.name } : {},
538
+ ...input.email ? { email: input.email } : {},
539
+ ...input.accountType ? { account_type: input.accountType } : {},
540
+ ...input.id ? { id: input.id } : {}
381
541
  }
382
542
  });
383
543
  }
384
- function listRoles(ctx, opts = {}) {
385
- return request(ctx, `${AUTHZ}/roles`, {
386
- query: {
387
- offset: opts.offset ?? 0,
388
- limit: opts.limit ?? 100,
389
- keyword: opts.keyword || void 0
544
+ function updateUserSafe(ctx, userId, input) {
545
+ return request(ctx, `${ADMIN}/users/${encodeURIComponent(userId)}`, {
546
+ method: "PUT",
547
+ body: {
548
+ ...input.name !== void 0 ? { name: input.name } : {},
549
+ ...input.email !== void 0 ? { email: input.email } : {},
550
+ ...input.telephone !== void 0 ? { telephone: input.telephone } : {},
551
+ ...input.enabled !== void 0 ? { enabled: input.enabled } : {},
552
+ ...input.accountType !== void 0 ? { account_type: input.accountType } : {}
390
553
  }
391
554
  });
392
555
  }
393
- function getRole(ctx, roleId) {
394
- return request(ctx, `${AUTHZ}/roles/${encodeURIComponent(roleId)}`);
556
+ async function deleteUserSafe(ctx, userId) {
557
+ await request(ctx, `${ADMIN}/users/${encodeURIComponent(userId)}`, { method: "DELETE" });
558
+ return { ok: true };
395
559
  }
396
- function getUser(ctx, userId) {
397
- return shareMgnt(ctx, "Usrm_GetUserInfo", [userId]);
560
+ async function setUserPasswordSafe(ctx, userId, password) {
561
+ await request(ctx, `${ADMIN}/users/${encodeURIComponent(userId)}/password`, {
562
+ method: "PUT",
563
+ body: { password }
564
+ });
565
+ return { ok: true };
398
566
  }
399
- async function getUserRoles(ctx, userId) {
400
- try {
401
- return await request(ctx, `${AUTHZ}/accessor_roles`, {
402
- query: { accessor_id: userId, accessor_type: "user" }
403
- });
404
- } catch (e) {
405
- if (!(e instanceof HttpError) || e.status !== 404) throw e;
406
- const rolesPage = await listRoles(ctx, { offset: 0, limit: 200 });
407
- const roles = rolesPage.entries ?? [];
408
- const matched = [];
409
- await Promise.all(
410
- roles.map(async (role) => {
411
- const members = await listRoleMembers(ctx, role.id, { offset: 0, limit: 500 });
412
- if ((members.entries ?? []).some((m) => m.id === userId && (!m.type || m.type === "user"))) {
413
- matched.push(role);
414
- }
415
- })
416
- );
417
- return {
418
- entries: matched,
419
- total_count: matched.length,
420
- route: "fallback:list-roles+role-members"
421
- };
422
- }
567
+ function getUserRolesSafe(ctx, userId) {
568
+ return request(ctx, `${ADMIN}/role-bindings`, { query: { accessor_id: userId } });
423
569
  }
424
- async function getDepartment(ctx, deptId) {
425
- try {
426
- return await shareMgnt(ctx, "Usrm_GetOrgDepartmentById", [deptId]);
427
- } catch (e) {
428
- const msg = e instanceof Error ? e.message : String(e);
429
- if (!/部门不存在|errID:?\s*20201|errID=?\s*99|NoneType.+subscriptable/i.test(msg)) throw e;
430
- return shareMgnt(ctx, "Usrm_GetDepartmentById", [deptId]);
431
- }
570
+ async function assignRoleSafe(ctx, accessorId, roleId) {
571
+ await request(ctx, `${ADMIN}/role-bindings`, {
572
+ method: "POST",
573
+ body: { accessor_id: accessorId, role_id: roleId }
574
+ });
575
+ return { ok: true };
432
576
  }
433
- function getDepartmentMembers(ctx, deptId, opts = {}) {
434
- return request(ctx, `${UM}/department-members/${encodeURIComponent(deptId)}/users`, {
435
- query: { role: opts.role ?? "super_admin", offset: opts.offset ?? 0, limit: opts.limit ?? 100 }
577
+ async function removeRoleSafe(ctx, accessorId, roleId) {
578
+ await request(ctx, `${ADMIN}/role-bindings`, {
579
+ method: "DELETE",
580
+ body: { accessor_id: accessorId, role_id: roleId }
436
581
  });
582
+ return { ok: true };
437
583
  }
438
- async function createDepartment(ctx, input) {
439
- const ncTAddDepartParam = {
440
- parentId: input.parentId ?? "-1",
441
- departName: input.name,
442
- managerID: input.managerID ?? null,
443
- code: input.code ?? "",
444
- remark: input.remark ?? "",
445
- status: input.status ?? 1,
446
- email: input.email ?? "",
447
- ossId: ""
448
- };
449
- const id = await shareMgnt(ctx, "Usrm_AddDepartment", [{ ncTAddDepartParam }]);
450
- return { id };
451
- }
452
- async function updateDepartment(ctx, deptId, input) {
453
- const p = { departId: deptId };
454
- if (input.name !== void 0) p.departName = input.name;
455
- if (input.managerID !== void 0) p.managerID = input.managerID;
456
- if (input.code !== void 0) p.code = input.code;
457
- if (input.remark !== void 0) p.remark = input.remark;
458
- if (input.status !== void 0) p.status = input.status;
459
- if (input.email !== void 0) p.email = input.email;
460
- await shareMgnt(ctx, "Usrm_EditDepartment", [{ ncTEditDepartParam: p }]);
461
- return { id: deptId, updated: true };
462
- }
463
- async function deleteDepartment(ctx, deptId) {
464
- await request(ctx, `${UM}/management/departments/${encodeURIComponent(deptId)}`, {
465
- method: "DELETE"
584
+ function listDepartmentsSafe(ctx, opts = {}) {
585
+ return request(ctx, `${ADMIN}/departments`, {
586
+ query: {
587
+ search: opts.search || void 0,
588
+ parent_id: opts.parentId,
589
+ offset: opts.offset,
590
+ limit: opts.limit
591
+ }
466
592
  });
467
- return { id: deptId, deleted: true };
468
- }
469
- async function createUser(ctx, input) {
470
- const ncTUsrmUserInfo = {
471
- loginName: input.loginName,
472
- displayName: input.displayName ?? input.loginName,
473
- code: input.code ?? "",
474
- position: input.position ?? "",
475
- managerID: null,
476
- managerDisplayName: null,
477
- remark: input.remark ?? "",
478
- email: input.email ?? "",
479
- telNumber: input.telNumber ?? "",
480
- idcardNumber: "",
481
- departmentIds: input.departmentIds ?? ["-1"],
482
- priority: input.priority ?? 999,
483
- csfLevel2: null,
484
- pwdControl: false,
485
- expireTime: -1
486
- };
487
- if (input.csfLevel !== void 0) ncTUsrmUserInfo.csfLevel = input.csfLevel;
488
- const id = await shareMgnt(ctx, "Usrm_AddUser", [
489
- { ncTUsrmAddUserInfo: { user: { ncTUsrmUserInfo } } },
490
- await callerUserId(ctx)
491
- ]);
492
- return { id };
493
- }
494
- async function updateUser(ctx, userId, input) {
495
- const restBody = {};
496
- if (input.displayName !== void 0) restBody.display_name = input.displayName;
497
- if (input.code !== void 0) restBody.code = input.code;
498
- if (input.position !== void 0) restBody.position = input.position;
499
- if (input.remark !== void 0) restBody.remark = input.remark;
500
- if (input.email !== void 0) restBody.email = input.email;
501
- if (input.telNumber !== void 0) restBody.tel_number = input.telNumber;
502
- if (input.managerID !== void 0) restBody.manager_id = input.managerID;
503
- if (input.priority !== void 0) restBody.priority = input.priority;
504
- if (input.csfLevel !== void 0) restBody.csf_level = input.csfLevel;
505
- try {
506
- const r = await request(ctx, `${UM}/management/users/${encodeURIComponent(userId)}`, {
507
- method: "PATCH",
508
- body: restBody
509
- });
510
- return r ?? { id: userId, updated: true, route: "rest" };
511
- } catch (e) {
512
- if (!(e instanceof HttpError) || e.status !== 404 && e.status !== 405) throw e;
513
- const ncTEditUserParam = {
514
- id: userId,
515
- displayName: input.displayName ?? "",
516
- code: input.code ?? "",
517
- position: input.position ?? "",
518
- managerID: input.managerID ?? "",
519
- remark: input.remark ?? "",
520
- idcardNumber: null,
521
- priority: input.priority ?? 999,
522
- csfLevel: input.csfLevel ?? 5,
523
- csfLevel2: null,
524
- email: input.email ?? "",
525
- telNumber: input.telNumber ?? "",
526
- expireTime: -1
527
- };
528
- await shareMgnt(ctx, "Usrm_EditUser", [{ ncTEditUserParam }, await callerUserId(ctx)]);
529
- return { id: userId, updated: true, route: "shareMgnt" };
530
- }
531
593
  }
532
- async function deleteUser(ctx, userId) {
533
- try {
534
- await request(ctx, `${UM}/users/${encodeURIComponent(userId)}`, { method: "DELETE" });
535
- } catch (e) {
536
- if (!(e instanceof HttpError) || e.status !== 404) throw e;
537
- await shareMgnt(ctx, "Usrm_DelUser", [userId]);
538
- }
539
- return { id: userId, deleted: true };
594
+ function getDepartmentSafe(ctx, deptId) {
595
+ return request(ctx, `${ADMIN}/departments/${encodeURIComponent(deptId)}`);
540
596
  }
541
- async function setUserPassword(ctx, userId, newPassword) {
542
- await request(ctx, `${UM}/management/users/${encodeURIComponent(userId)}/password`, {
543
- method: "PUT",
544
- body: { password: encryptModifyPwd(newPassword) }
545
- });
546
- return { id: userId, passwordReset: true };
597
+ function getDepartmentMembersSafe(ctx, deptId) {
598
+ return request(ctx, `${ADMIN}/departments/${encodeURIComponent(deptId)}/members`);
547
599
  }
548
- var EACP = "/api/eacp/v1";
549
- function listAuditLogs(ctx, opts = {}) {
550
- return request(ctx, `${EACP}/auth1/login-log`, {
600
+ function createDepartmentSafe(ctx, input) {
601
+ return request(ctx, `${ADMIN}/departments`, {
551
602
  method: "POST",
552
603
  body: {
553
- page_num: opts.page ?? 1,
554
- page_size: opts.size ?? 30,
555
- user_name: opts.user || void 0,
556
- start_time: opts.start || void 0,
557
- end_time: opts.end || void 0
604
+ name: input.name,
605
+ ...input.parentId ? { parent_id: input.parentId } : {},
606
+ ...input.type ? { type: input.type } : {},
607
+ ...input.id ? { id: input.id } : {}
558
608
  }
559
609
  });
560
610
  }
561
- function changePassword(ctx, account, oldPassword, newPassword) {
562
- return request(ctx, `${EACP}/auth1/modifypassword`, {
563
- method: "POST",
611
+ function updateDepartmentSafe(ctx, deptId, input) {
612
+ return request(ctx, `${ADMIN}/departments/${encodeURIComponent(deptId)}`, {
613
+ method: "PUT",
564
614
  body: {
565
- account,
566
- oldpwd: encryptModifyPwd(oldPassword),
567
- newpwd: encryptModifyPwd(newPassword)
615
+ ...input.name !== void 0 ? { name: input.name } : {},
616
+ ...input.parentId !== void 0 ? { parent_id: input.parentId } : {},
617
+ ...input.type !== void 0 ? { type: input.type } : {}
568
618
  }
569
619
  });
570
620
  }
571
- function listRoleMembers(ctx, roleId, opts = {}) {
572
- return request(ctx, `${AUTHZ}/role-members/${encodeURIComponent(roleId)}`, {
573
- query: {
574
- offset: opts.offset ?? 0,
575
- limit: opts.limit ?? 100,
576
- keyword: opts.keyword || void 0
621
+ async function deleteDepartmentSafe(ctx, deptId) {
622
+ await request(ctx, `${ADMIN}/departments/${encodeURIComponent(deptId)}`, { method: "DELETE" });
623
+ return { ok: true };
624
+ }
625
+ async function buildDepartmentTree(ctx) {
626
+ const res = await listDepartmentsSafe(ctx, { limit: 1e3 });
627
+ const flat = Array.isArray(res) ? res : res.departments ?? [];
628
+ const byId = /* @__PURE__ */ new Map();
629
+ for (const d of flat) if (d.id) byId.set(d.id, { ...d, children: [] });
630
+ const roots = [];
631
+ for (const d of byId.values()) {
632
+ const parent = d.parent_id ? byId.get(d.parent_id) : void 0;
633
+ if (parent) parent.children?.push(d);
634
+ else roots.push(d);
635
+ }
636
+ return roots;
637
+ }
638
+ function listRolesSafe(ctx, source) {
639
+ return request(ctx, `${ADMIN}/roles`, { query: { source: source || void 0 } });
640
+ }
641
+ function getRoleSafe(ctx, roleId) {
642
+ return request(ctx, `${ADMIN}/roles/${encodeURIComponent(roleId)}`);
643
+ }
644
+ function roleMembersSafe(ctx, roleId) {
645
+ return request(ctx, `${ADMIN}/roles/${encodeURIComponent(roleId)}/members`);
646
+ }
647
+ function createRoleSafe(ctx, input) {
648
+ return request(ctx, `${ADMIN}/roles`, {
649
+ method: "POST",
650
+ body: {
651
+ name: input.name,
652
+ ...input.description ? { description: input.description } : {},
653
+ ...input.id ? { id: input.id } : {}
577
654
  }
578
655
  });
579
656
  }
580
- function modifyRoleMembers(ctx, roleId, method, members) {
581
- return request(ctx, `${AUTHZ}/role-members/${encodeURIComponent(roleId)}`, {
582
- method: "POST",
583
- body: { method, members }
657
+ function updateRoleSafe(ctx, roleId, input) {
658
+ return request(ctx, `${ADMIN}/roles/${encodeURIComponent(roleId)}`, {
659
+ method: "PUT",
660
+ body: {
661
+ ...input.name !== void 0 ? { name: input.name } : {},
662
+ ...input.description !== void 0 ? { description: input.description } : {}
663
+ }
584
664
  });
585
665
  }
586
-
587
- // src/utils/org-tree.ts
588
- function buildOrgTree(entries) {
589
- const nodes = /* @__PURE__ */ new Map();
590
- for (const e of entries) nodes.set(e.id, { id: e.id, name: e.name ?? e.id, children: [] });
591
- const roots = [];
592
- for (const e of entries) {
593
- const node = nodes.get(e.id);
594
- if (!node) continue;
595
- const deps = e.parent_deps;
596
- const parentId = deps && deps.length > 0 ? deps[deps.length - 1]?.id : void 0;
597
- const parent = parentId ? nodes.get(parentId) : void 0;
598
- if (parent) parent.children.push(node);
599
- else roots.push(node);
600
- }
601
- return roots;
666
+ async function deleteRoleSafe(ctx, roleId) {
667
+ await request(ctx, `${ADMIN}/roles/${encodeURIComponent(roleId)}`, { method: "DELETE" });
668
+ return { ok: true };
602
669
  }
603
- function renderOrgTree(nodes, prefix = "") {
604
- const lines = [];
605
- nodes.forEach((node, i) => {
606
- const last = i === nodes.length - 1;
607
- lines.push(`${prefix}${last ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}${node.name} (id: ${node.id})`);
608
- if (node.children.length) {
609
- lines.push(renderOrgTree(node.children, `${prefix}${last ? " " : "\u2502 "}`));
670
+ async function setRolePermissionSafe(ctx, roleId, grant, perm) {
671
+ await request(ctx, `${ADMIN}/roles/${encodeURIComponent(roleId)}/permissions`, {
672
+ method: grant ? "POST" : "DELETE",
673
+ body: {
674
+ resource: { type: perm.resourceType, id: perm.resourceId },
675
+ operations: perm.operations
610
676
  }
611
677
  });
612
- return lines.join("\n");
678
+ return { ok: true };
613
679
  }
614
680
 
615
681
  // src/resources/admin.ts
682
+ var DEFAULT_NEW_USER_PASSWORD = "openbkn";
616
683
  function admin(ctx) {
617
684
  return {
618
- orgList: (opts) => listDepartments(ctx, opts),
619
- orgGet: (deptId) => getDepartment(ctx, deptId),
620
- orgMembers: (deptId, opts) => getDepartmentMembers(ctx, deptId, opts),
621
- orgTree: async (role) => buildOrgTree(await listAllDepartments(ctx, role)),
622
- orgCreate: (input) => createDepartment(ctx, input),
623
- orgUpdate: (deptId, input) => updateDepartment(ctx, deptId, input),
624
- orgDelete: (deptId) => deleteDepartment(ctx, deptId),
625
- userList: (opts) => listUsers(ctx, opts),
626
- userGet: (userId) => getUser(ctx, userId),
627
- userRoles: (userId) => getUserRoles(ctx, userId),
628
- userCreate: (input) => createUser(ctx, input),
629
- userUpdate: (userId, input) => updateUser(ctx, userId, input),
630
- userDelete: (userId) => deleteUser(ctx, userId),
631
- userResetPassword: (userId, newPassword) => setUserPassword(ctx, userId, newPassword),
632
- roleList: (opts) => listRoles(ctx, opts),
633
- roleGet: (roleId) => getRole(ctx, roleId),
634
- roleMembers: (roleId, opts) => listRoleMembers(ctx, roleId, opts),
635
- addRoleMember: (roleId, id, type = "user") => modifyRoleMembers(ctx, roleId, "POST", [{ id, type }]),
636
- removeRoleMember: (roleId, id, type = "user") => modifyRoleMembers(ctx, roleId, "DELETE", [{ id, type }]),
637
- auditList: (opts) => listAuditLogs(ctx, opts)
685
+ // ── departments ──
686
+ orgList: (opts) => listDepartmentsSafe(ctx, { search: opts?.name, offset: opts?.offset, limit: opts?.limit }),
687
+ orgGet: (deptId) => getDepartmentSafe(ctx, deptId),
688
+ orgTree: (_role) => buildDepartmentTree(ctx),
689
+ orgMembers: (deptId, _opts) => getDepartmentMembersSafe(ctx, deptId),
690
+ orgCreate: (input) => createDepartmentSafe(ctx, { name: input.name, parentId: input.parentId }),
691
+ orgUpdate: (deptId, input) => updateDepartmentSafe(ctx, deptId, { name: input.name }),
692
+ orgDelete: (deptId) => deleteDepartmentSafe(ctx, deptId),
693
+ // ── users ──
694
+ userList: (opts) => listUsersSafe(ctx, { search: opts?.name, offset: opts?.offset, limit: opts?.limit }),
695
+ userGet: (userId) => getUserSafe(ctx, userId),
696
+ userRoles: async (userId) => {
697
+ const [bound, all] = await Promise.all([getUserRolesSafe(ctx, userId), listRolesSafe(ctx)]);
698
+ const ids = bound.role_ids ?? [];
699
+ const nameById = new Map(
700
+ (all.roles ?? []).map((r) => [
701
+ r.id,
702
+ r.name
703
+ ])
704
+ );
705
+ return { roles: ids.map((id) => ({ name: nameById.get(id) ?? id, id })) };
706
+ },
707
+ userCreate: (input) => createUserSafe(ctx, {
708
+ account: input.loginName,
709
+ password: DEFAULT_NEW_USER_PASSWORD,
710
+ name: input.displayName,
711
+ email: input.email
712
+ }),
713
+ userUpdate: (userId, input) => updateUserSafe(ctx, userId, {
714
+ name: input.displayName,
715
+ email: input.email,
716
+ telephone: input.telNumber
717
+ }),
718
+ userDelete: (userId) => deleteUserSafe(ctx, userId),
719
+ userResetPassword: (userId, newPassword) => setUserPasswordSafe(ctx, userId, newPassword),
720
+ // ── roles ──
721
+ roleList: (_opts) => listRolesSafe(ctx),
722
+ roleGet: (roleId) => getRoleSafe(ctx, roleId),
723
+ roleMembers: async (roleId, _opts) => {
724
+ const [mem, users] = await Promise.all([
725
+ roleMembersSafe(ctx, roleId),
726
+ listUsersSafe(ctx, { limit: 500 })
727
+ ]);
728
+ const ids = mem.accessor_ids ?? [];
729
+ const nameById = new Map(
730
+ (users.users ?? []).map((u) => [u.id, u.account ?? u.name ?? u.id])
731
+ );
732
+ return { members: ids.map((id) => ({ account: nameById.get(id) ?? id, id })) };
733
+ },
734
+ addRoleMember: (roleId, id, _type = "user") => assignRoleSafe(ctx, id, roleId),
735
+ removeRoleMember: (roleId, id, _type = "user") => removeRoleSafe(ctx, id, roleId),
736
+ roleCreate: (name, description) => createRoleSafe(ctx, { name, description }),
737
+ roleUpdate: (roleId, input) => updateRoleSafe(ctx, roleId, input),
738
+ roleDelete: (roleId) => deleteRoleSafe(ctx, roleId),
739
+ rolePermission: (roleId, grant, resourceType, resourceId, operations) => setRolePermissionSafe(ctx, roleId, grant, { resourceType, resourceId, operations }),
740
+ auditList: (_opts) => notOnSafe("audit list")
638
741
  };
639
742
  }
640
743
 
@@ -2611,13 +2714,13 @@ async function uploadBkn(ctx, tarBuffer, opts = {}) {
2611
2714
  applyTls(ctx);
2612
2715
  const url = new URL(`${ctx.baseUrl}${BKNS}`);
2613
2716
  url.searchParams.set("branch", opts.branch ?? "main");
2614
- const form = new FormData();
2615
- form.append(
2717
+ const form2 = new FormData();
2718
+ form2.append(
2616
2719
  "file",
2617
2720
  new Blob([new Uint8Array(tarBuffer)], { type: "application/octet-stream" }),
2618
2721
  "bkn.tar"
2619
2722
  );
2620
- const res = await fetch(url, { method: "POST", headers: buildHeaders(ctx), body: form });
2723
+ const res = await fetch(url, { method: "POST", headers: buildHeaders(ctx), body: form2 });
2621
2724
  const text = await res.text();
2622
2725
  if (!res.ok) throw new HttpError(res.status, res.statusText, text);
2623
2726
  return text ? JSON.parse(text) : void 0;
@@ -2973,9 +3076,9 @@ function extractTarToDirectory(tarBuffer, dirPath) {
2973
3076
  }
2974
3077
 
2975
3078
  // src/utils/csv-import.ts
2976
- import { statSync as statSync2 } from "fs";
2977
- import { glob, readFile } from "fs/promises";
2978
- import { basename, resolve as resolve2 } from "path";
3079
+ import { readdirSync as readdirSync3, statSync as statSync2 } from "fs";
3080
+ import { readFile } from "fs/promises";
3081
+ import { basename, dirname, resolve as resolve2 } from "path";
2979
3082
  import { parse } from "csv-parse/sync";
2980
3083
  async function parseCsvFile(filePath) {
2981
3084
  let content = await readFile(filePath, "utf8");
@@ -3039,13 +3142,25 @@ function buildImportDag(opts) {
3039
3142
  ]
3040
3143
  };
3041
3144
  }
3145
+ function globOne(pattern) {
3146
+ const dir = dirname(pattern);
3147
+ const re = new RegExp(
3148
+ `^${basename(pattern).replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`
3149
+ );
3150
+ let entries;
3151
+ try {
3152
+ entries = readdirSync3(resolve2(dir));
3153
+ } catch {
3154
+ return [];
3155
+ }
3156
+ return entries.filter((f) => re.test(f)).map((f) => resolve2(dir, f));
3157
+ }
3042
3158
  async function resolveFiles(pattern) {
3043
3159
  const out = [];
3044
3160
  for (const part of pattern.split(",").map((p) => p.trim()).filter(Boolean)) {
3045
3161
  if (part.includes("*") || part.includes("?")) {
3046
- for await (const entry of glob(part)) {
3047
- const p = String(entry);
3048
- if (/\.csv$/i.test(p)) out.push(resolve2(p));
3162
+ for (const p of globOne(part)) {
3163
+ if (/\.csv$/i.test(p)) out.push(p);
3049
3164
  }
3050
3165
  } else {
3051
3166
  statSync2(resolve2(part));
@@ -3628,21 +3743,21 @@ function resources(ctx) {
3628
3743
 
3629
3744
  // src/resources/skills.ts
3630
3745
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
3631
- import { basename as basename2, dirname as dirname2, resolve as resolve5 } from "path";
3746
+ import { basename as basename2, dirname as dirname3, resolve as resolve5 } from "path";
3632
3747
 
3633
3748
  // src/api/skills.ts
3634
3749
  var BASE5 = "/api/agent-operator-integration/v1";
3635
3750
  async function registerSkillZip(ctx, bytes, opts = {}) {
3636
3751
  applyTls(ctx);
3637
- const form = new FormData();
3638
- form.set("file_type", "zip");
3639
- form.set("file", new Blob([bytes]), opts.filename ?? "skill.zip");
3640
- if (opts.source) form.set("source", opts.source);
3641
- if (opts.extendInfo) form.set("extend_info", JSON.stringify(opts.extendInfo));
3752
+ const form2 = new FormData();
3753
+ form2.set("file_type", "zip");
3754
+ form2.set("file", new Blob([bytes]), opts.filename ?? "skill.zip");
3755
+ if (opts.source) form2.set("source", opts.source);
3756
+ if (opts.extendInfo) form2.set("extend_info", JSON.stringify(opts.extendInfo));
3642
3757
  const res = await fetch(`${ctx.baseUrl}${BASE5}/skills`, {
3643
3758
  method: "POST",
3644
3759
  headers: buildHeaders(ctx),
3645
- body: form
3760
+ body: form2
3646
3761
  });
3647
3762
  const text = await res.text();
3648
3763
  if (!res.ok) throw new HttpError(res.status, res.statusText, text);
@@ -3650,13 +3765,13 @@ async function registerSkillZip(ctx, bytes, opts = {}) {
3650
3765
  }
3651
3766
  async function updateSkillPackageZip(ctx, skillId, bytes, filename = "skill.zip") {
3652
3767
  applyTls(ctx);
3653
- const form = new FormData();
3654
- form.set("file_type", "zip");
3655
- form.set("file", new Blob([bytes]), filename);
3768
+ const form2 = new FormData();
3769
+ form2.set("file_type", "zip");
3770
+ form2.set("file", new Blob([bytes]), filename);
3656
3771
  const res = await fetch(`${ctx.baseUrl}${BASE5}/skills/${encodeURIComponent(skillId)}/package`, {
3657
3772
  method: "PUT",
3658
3773
  headers: buildHeaders(ctx),
3659
- body: form
3774
+ body: form2
3660
3775
  });
3661
3776
  const text = await res.text();
3662
3777
  if (!res.ok) throw new HttpError(res.status, res.statusText, text);
@@ -3730,11 +3845,11 @@ function setSkillStatus(ctx, skillId, status2) {
3730
3845
  }
3731
3846
 
3732
3847
  // src/utils/skill-archive.ts
3733
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
3734
- import { dirname, join as join3, relative, resolve as resolve4, sep } from "path";
3848
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync4, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
3849
+ import { dirname as dirname2, join as join3, relative, resolve as resolve4, sep } from "path";
3735
3850
  import JSZip from "jszip";
3736
3851
  function walk(dir, base, files) {
3737
- for (const entry of readdirSync3(dir)) {
3852
+ for (const entry of readdirSync4(dir)) {
3738
3853
  const full = join3(dir, entry);
3739
3854
  const st = statSync3(full);
3740
3855
  if (st.isDirectory()) walk(full, base, files);
@@ -3762,7 +3877,7 @@ async function unzipToDirectory(bytes, dir) {
3762
3877
  for (const entry of entries) {
3763
3878
  if (entry.dir) continue;
3764
3879
  const out = join3(abs, entry.name);
3765
- mkdirSync3(dirname(out), { recursive: true });
3880
+ mkdirSync3(dirname2(out), { recursive: true });
3766
3881
  writeFileSync2(out, await entry.async("nodebuffer"));
3767
3882
  written.push(out);
3768
3883
  }
@@ -3795,7 +3910,7 @@ function skills(ctx) {
3795
3910
  download: async (skillId, outPath) => {
3796
3911
  const bytes = await downloadSkill(ctx, skillId);
3797
3912
  const dest = resolve5(outPath ?? `${skillId}.zip`);
3798
- mkdirSync4(dirname2(dest), { recursive: true });
3913
+ mkdirSync4(dirname3(dest), { recursive: true });
3799
3914
  writeFileSync3(dest, bytes);
3800
3915
  return { skillId, path: dest, bytes: bytes.length };
3801
3916
  },
@@ -3811,7 +3926,7 @@ function skills(ctx) {
3811
3926
 
3812
3927
  // src/resources/toolboxes.ts
3813
3928
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
3814
- import { dirname as dirname3, resolve as resolve6 } from "path";
3929
+ import { dirname as dirname4, resolve as resolve6 } from "path";
3815
3930
 
3816
3931
  // src/api/toolboxes.ts
3817
3932
  import { readFile as readFile2 } from "fs/promises";
@@ -3831,12 +3946,12 @@ async function exportConfig(ctx, id, type = "toolbox") {
3831
3946
  async function importConfig(ctx, filePath, type = "toolbox") {
3832
3947
  applyTls(ctx);
3833
3948
  const buf = await readFile2(filePath);
3834
- const form = new FormData();
3835
- form.append("data", new Blob([new Uint8Array(buf)]), basename3(filePath));
3949
+ const form2 = new FormData();
3950
+ form2.append("data", new Blob([new Uint8Array(buf)]), basename3(filePath));
3836
3951
  const res = await fetch(`${ctx.baseUrl}${IMPEX}/import/${encodeURIComponent(type)}`, {
3837
3952
  method: "POST",
3838
3953
  headers: buildHeaders(ctx),
3839
- body: form
3954
+ body: form2
3840
3955
  });
3841
3956
  const text = await res.text();
3842
3957
  if (!res.ok) throw new HttpError(res.status, res.statusText, text);
@@ -3845,13 +3960,13 @@ async function importConfig(ctx, filePath, type = "toolbox") {
3845
3960
  async function uploadTool(ctx, boxId, filePath, metadataType = "openapi") {
3846
3961
  applyTls(ctx);
3847
3962
  const buf = await readFile2(filePath);
3848
- const form = new FormData();
3849
- form.append("metadata_type", metadataType);
3850
- form.append("data", new Blob([new Uint8Array(buf)]), basename3(filePath));
3963
+ const form2 = new FormData();
3964
+ form2.append("metadata_type", metadataType);
3965
+ form2.append("data", new Blob([new Uint8Array(buf)]), basename3(filePath));
3851
3966
  const res = await fetch(`${ctx.baseUrl}${PATH}/${encodeURIComponent(boxId)}/tool`, {
3852
3967
  method: "POST",
3853
3968
  headers: buildHeaders(ctx),
3854
- body: form
3969
+ body: form2
3855
3970
  });
3856
3971
  const text = await res.text();
3857
3972
  if (!res.ok) throw new HttpError(res.status, res.statusText, text);
@@ -3937,7 +4052,7 @@ function toolboxes(ctx) {
3937
4052
  export: async (id, outPath, type) => {
3938
4053
  const bytes = await exportConfig(ctx, id, type);
3939
4054
  const dest = resolve6(outPath);
3940
- mkdirSync5(dirname3(dest), { recursive: true });
4055
+ mkdirSync5(dirname4(dest), { recursive: true });
3941
4056
  writeFileSync4(dest, bytes);
3942
4057
  return { id, path: dest, bytes: bytes.length };
3943
4058
  },
@@ -4017,7 +4132,7 @@ async function getSpansByConversation(ctx, conversationId, opts = {}) {
4017
4132
  }
4018
4133
 
4019
4134
  // src/trace-ai/claude-judge.ts
4020
- import { spawn, spawnSync as spawnSync2 } from "child_process";
4135
+ import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
4021
4136
  var ClaudeJudgeError = class extends Error {
4022
4137
  constructor(message, reason) {
4023
4138
  super(message);
@@ -4063,7 +4178,7 @@ async function judgeJson(prompt, opts = {}) {
4063
4178
  const timeoutMs = opts.timeoutMs ?? 12e4;
4064
4179
  const args = ["-p", "--output-format=json", "--dangerously-skip-permissions"];
4065
4180
  const stdout = await new Promise((resolve7, reject) => {
4066
- const child = spawn(binary, args, { stdio: ["pipe", "pipe", "pipe"] });
4181
+ const child = spawn2(binary, args, { stdio: ["pipe", "pipe", "pipe"] });
4067
4182
  let out = "";
4068
4183
  let timedOut = false;
4069
4184
  const killer = setTimeout(() => {
@@ -4864,6 +4979,7 @@ function createClient(opts = {}) {
4864
4979
  // src/resources/auth.ts
4865
4980
  var auth_exports = {};
4866
4981
  __export(auth_exports, {
4982
+ attachNoAuth: () => attachNoAuth,
4867
4983
  attachToken: () => attachToken,
4868
4984
  currentToken: () => currentToken,
4869
4985
  deletePlatform: () => deletePlatform,
@@ -4901,12 +5017,19 @@ function attachToken(baseUrl, accessToken, opts = {}) {
4901
5017
  refreshToken: opts.refreshToken,
4902
5018
  idToken: opts.idToken,
4903
5019
  tlsInsecure: opts.insecure,
4904
- username: decodeJwt(opts.idToken ?? accessToken)?.preferred_username
5020
+ // Prefer the account the user typed (-u); device tokens carry no username.
5021
+ username: opts.username ?? decodeJwt(opts.idToken ?? accessToken)?.preferred_username
4905
5022
  };
4906
5023
  const userId = writeToken(url, token);
4907
5024
  setActivePlatform(url);
4908
5025
  return { baseUrl: url, userId, username: usernameOf(token) };
4909
5026
  }
5027
+ function attachNoAuth(baseUrl, opts = {}) {
5028
+ const url = normalize(baseUrl);
5029
+ writeToken(url, { baseUrl: url, accessToken: "", noAuth: true, tlsInsecure: opts.insecure });
5030
+ setActivePlatform(url);
5031
+ return { baseUrl: url, noAuth: true };
5032
+ }
4910
5033
  function status() {
4911
5034
  const baseUrl = activePlatform();
4912
5035
  if (!baseUrl) return { hasToken: false };
@@ -4961,14 +5084,16 @@ function logout() {
4961
5084
  function deletePlatform(baseUrl, userId) {
4962
5085
  return deleteToken(normalize(baseUrl), userId);
4963
5086
  }
4964
- function switchUser(baseUrl, userId) {
5087
+ function switchUser(baseUrl, userOrName) {
4965
5088
  const url = normalize(baseUrl);
4966
- if (!readToken(url, userId)) {
4967
- throw new InputError(`No saved token for user '${userId}' on ${url}.`);
5089
+ const users = usersOf(url);
5090
+ const match = users.find((u) => u.userId === userOrName) ?? users.find((u) => (u.username ?? u.displayName) === userOrName);
5091
+ if (!match) {
5092
+ throw new InputError(`No saved user '${userOrName}' on ${url}. Try \`auth users ${url}\`.`);
4968
5093
  }
4969
- setActiveUser(url, userId);
5094
+ setActiveUser(url, match.userId);
4970
5095
  setActivePlatform(url);
4971
- return { baseUrl: url, userId };
5096
+ return { baseUrl: url, userId: match.userId, username: match.username ?? match.displayName };
4972
5097
  }
4973
5098
  function usersOf(baseUrl) {
4974
5099
  const url = normalize(baseUrl);
@@ -4996,14 +5121,18 @@ export {
4996
5121
  InputError,
4997
5122
  toExitCode,
4998
5123
  formatError,
5124
+ decodeJwt,
4999
5125
  activePlatform,
5000
5126
  setActivePlatform,
5001
5127
  readPlatformConfig,
5002
5128
  writePlatformConfig,
5003
5129
  resolveContext,
5130
+ openBrowser,
5131
+ fetchAuthStatus,
5132
+ deviceLogin,
5133
+ credentialDeviceLogin,
5004
5134
  request,
5005
- changePassword,
5006
- renderOrgTree,
5135
+ getUserSafe,
5007
5136
  admin,
5008
5137
  agents,
5009
5138
  context,
@@ -5020,6 +5149,7 @@ export {
5020
5149
  vega,
5021
5150
  createClient,
5022
5151
  attachToken,
5152
+ attachNoAuth,
5023
5153
  status,
5024
5154
  currentToken,
5025
5155
  whoami,
@@ -5028,8 +5158,7 @@ export {
5028
5158
  logout,
5029
5159
  deletePlatform,
5030
5160
  switchUser,
5031
- usersOf,
5032
5161
  exportCreds,
5033
5162
  auth_exports
5034
5163
  };
5035
- //# sourceMappingURL=chunk-ADZ23DPF.js.map
5164
+ //# sourceMappingURL=chunk-APJNRHLS.js.map