@openbkn/bkn-sdk 0.1.1-alpha.1 → 0.1.1-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,22 +4,20 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
- // src/types.ts
8
- var DEFAULT_BUSINESS_DOMAIN = "bd_public";
9
- var DEFAULT_LIST_LIMIT = 30;
10
- var DEFAULT_QUERY_LIMIT = 50;
11
-
12
7
  // src/utils/errors.ts
13
8
  var HttpError = class extends Error {
14
9
  status;
15
10
  statusText;
16
11
  body;
17
- constructor(status2, statusText, body) {
12
+ /** Optional next-step guidance, overriding the status default (e.g. AppKey re-issue). */
13
+ hint;
14
+ constructor(status2, statusText, body, hint) {
18
15
  super(`HTTP ${status2} ${statusText}`);
19
16
  this.name = "HttpError";
20
17
  this.status = status2;
21
18
  this.statusText = statusText;
22
19
  this.body = body;
20
+ this.hint = hint;
23
21
  }
24
22
  };
25
23
  var InputError = class extends Error {
@@ -38,8 +36,13 @@ function toExitCode(err) {
38
36
  }
39
37
  function formatError(err) {
40
38
  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.`;
39
+ const serverMsg = serverError(err.body);
40
+ if (err.status === 401) {
41
+ const next = err.hint ?? "Run `openbkn auth login` and retry.";
42
+ return `Not authorized (HTTP 401)${serverMsg ? `: ${serverMsg}` : ""}. ${next}`;
43
+ }
44
+ if (err.status === 403) {
45
+ return `Forbidden (HTTP 403)${serverMsg ? `: ${serverMsg}` : " \u2014 admin privileges required"}.`;
43
46
  }
44
47
  const detail = err.body ? `: ${truncate(err.body, 500)}` : "";
45
48
  return `Request failed (HTTP ${err.status} ${err.statusText})${detail}`;
@@ -56,6 +59,16 @@ function formatError(err) {
56
59
  }
57
60
  return String(err);
58
61
  }
62
+ function serverError(body) {
63
+ if (!body) return "";
64
+ try {
65
+ const j = JSON.parse(body);
66
+ const m = j.error ?? j.detail ?? j.description ?? j.message;
67
+ return typeof m === "string" ? m : "";
68
+ } catch {
69
+ return "";
70
+ }
71
+ }
59
72
  function isTlsCertError(code) {
60
73
  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
74
  }
@@ -63,6 +76,277 @@ function truncate(s, n) {
63
76
  return s.length > n ? `${s.slice(0, n)}\u2026` : s;
64
77
  }
65
78
 
79
+ // src/auth/oauth.ts
80
+ import { spawn } from "child_process";
81
+ function normalizeBaseUrl(value) {
82
+ return value.replace(/\/+$/, "");
83
+ }
84
+ function mapToken(data) {
85
+ return {
86
+ accessToken: data.access_token,
87
+ refreshToken: data.refresh_token,
88
+ idToken: data.id_token
89
+ };
90
+ }
91
+ function isHeadless() {
92
+ if (process.platform !== "linux") return false;
93
+ return !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY;
94
+ }
95
+ function openBrowser(url) {
96
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
97
+ try {
98
+ const child = spawn(cmd, [url], {
99
+ stdio: "ignore",
100
+ detached: true,
101
+ shell: process.platform === "win32"
102
+ });
103
+ child.on("error", () => {
104
+ });
105
+ child.unref();
106
+ } catch {
107
+ }
108
+ }
109
+ function mergeCookies(existing, res) {
110
+ const setCookies = typeof res.headers.getSetCookie === "function" ? res.headers.getSetCookie() : res.headers.get("set-cookie") ? [res.headers.get("set-cookie")] : [];
111
+ const map = /* @__PURE__ */ new Map();
112
+ const add = (pair) => {
113
+ const eq = pair.indexOf("=");
114
+ if (eq > 0) map.set(pair.slice(0, eq), pair.slice(eq + 1));
115
+ };
116
+ for (const p of existing.split(";").map((s) => s.trim()).filter(Boolean))
117
+ add(p);
118
+ for (const sc of setCookies) add(sc.split(";")[0]?.trim() ?? "");
119
+ return [...map.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
120
+ }
121
+ async function fetchAuthStatus(baseUrl) {
122
+ try {
123
+ const res = await fetch(`${normalizeBaseUrl(baseUrl)}/install-status.json`, {
124
+ headers: { Accept: "application/json" }
125
+ });
126
+ if (!res.ok) return null;
127
+ const j = await res.json();
128
+ if (!j.auth) return null;
129
+ return { enabled: Boolean(j.auth.enabled), stack: j.auth.stack };
130
+ } catch {
131
+ return null;
132
+ }
133
+ }
134
+ async function refreshAccessToken(baseUrl, refreshToken, clientId = "openbkn-sdk") {
135
+ const base = normalizeBaseUrl(baseUrl);
136
+ const res = await fetch(`${base}/oauth2/token`, {
137
+ method: "POST",
138
+ headers: {
139
+ "Content-Type": "application/x-www-form-urlencoded",
140
+ Accept: "application/json"
141
+ },
142
+ body: new URLSearchParams({
143
+ grant_type: "refresh_token",
144
+ refresh_token: refreshToken,
145
+ client_id: clientId
146
+ }).toString()
147
+ });
148
+ if (!res.ok) {
149
+ throw new Error(
150
+ `Token refresh failed (${res.status}): ${await res.text() || res.statusText}`
151
+ );
152
+ }
153
+ return mapToken(await res.json());
154
+ }
155
+ var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
156
+ var DEFAULT_DEVICE_CLIENT_ID = "openbkn-sdk";
157
+ var DEVICE_SCOPE = "openid offline";
158
+ var form = (body) => ({
159
+ method: "POST",
160
+ headers: {
161
+ "Content-Type": "application/x-www-form-urlencoded",
162
+ Accept: "application/json"
163
+ },
164
+ body: new URLSearchParams(body).toString()
165
+ });
166
+ async function requestDeviceCode(base, clientId, scope, audience) {
167
+ const params = { client_id: clientId, scope };
168
+ if (audience) params.audience = audience;
169
+ const res = await fetch(`${base}/oauth2/device/auth`, form(params));
170
+ if (!res.ok) {
171
+ throw new Error(`Device auth failed (${res.status}): ${await res.text() || res.statusText}`);
172
+ }
173
+ const da = await res.json();
174
+ if (!da.verification_uri) {
175
+ throw new Error(
176
+ "Device auth returned no verification_uri \u2014 is Hydra's device flow configured?"
177
+ );
178
+ }
179
+ return da;
180
+ }
181
+ async function pollDeviceToken(base, deviceCode, clientId, intervalMs, windowMs) {
182
+ let interval = intervalMs;
183
+ const deadline = Date.now() + windowMs;
184
+ while (Date.now() < deadline) {
185
+ await new Promise((r) => setTimeout(r, interval));
186
+ const tokRes = await fetch(
187
+ `${base}/oauth2/token`,
188
+ form({ grant_type: DEVICE_GRANT, device_code: deviceCode, client_id: clientId })
189
+ );
190
+ const data = await tokRes.json().catch(() => ({}));
191
+ if (tokRes.ok) return mapToken(data);
192
+ switch (data.error) {
193
+ case "authorization_pending":
194
+ break;
195
+ // keep polling
196
+ case "slow_down":
197
+ interval += 5e3;
198
+ break;
199
+ case "access_denied":
200
+ throw new InputError("Device authorization denied.");
201
+ case "expired_token":
202
+ throw new InputError("Device code expired \u2014 run login again.");
203
+ default:
204
+ throw new Error(`Device token poll failed: ${String(data.error ?? tokRes.status)}`);
205
+ }
206
+ }
207
+ throw new InputError("Device login timed out.");
208
+ }
209
+ async function deviceLogin(baseUrl, opts = {}) {
210
+ const base = normalizeBaseUrl(baseUrl);
211
+ const clientId = opts.clientId ?? DEFAULT_DEVICE_CLIENT_ID;
212
+ const da = await requestDeviceCode(base, clientId, opts.scope ?? DEVICE_SCOPE, opts.audience);
213
+ opts.onPrompt?.({
214
+ userCode: da.user_code,
215
+ verificationUri: onBaseHost(base, da.verification_uri),
216
+ verificationUriComplete: da.verification_uri_complete ? onBaseHost(base, da.verification_uri_complete) : void 0
217
+ });
218
+ const windowMs = Math.min(opts.timeoutMs ?? Number.POSITIVE_INFINITY, da.expires_in * 1e3);
219
+ return pollDeviceToken(base, da.device_code, clientId, (da.interval ?? 5) * 1e3, windowMs);
220
+ }
221
+ function onBaseHost(base, uri) {
222
+ try {
223
+ const u = new URL(uri);
224
+ const b = new URL(base);
225
+ return `${b.origin}${u.pathname}${u.search}`;
226
+ } catch {
227
+ return uri;
228
+ }
229
+ }
230
+ async function credentialDeviceLogin(baseUrl, username, password, opts = {}) {
231
+ const base = normalizeBaseUrl(baseUrl);
232
+ const clientId = opts.clientId ?? DEFAULT_DEVICE_CLIENT_ID;
233
+ const da = await requestDeviceCode(base, clientId, opts.scope ?? DEVICE_SCOPE, opts.audience);
234
+ let jar = "";
235
+ const hop = async (url, init) => {
236
+ const r = await fetch(url, {
237
+ method: init?.method ?? "GET",
238
+ headers: { Cookie: jar, Accept: "text/html,*/*;q=0.8", ...init?.headers ?? {} },
239
+ body: init?.body,
240
+ redirect: "manual"
241
+ });
242
+ jar = mergeCookies(jar, r);
243
+ return r;
244
+ };
245
+ const postForm = (path, body) => hop(`${base}${path}`, {
246
+ method: "POST",
247
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
248
+ body: new URLSearchParams(body).toString()
249
+ });
250
+ const v = await hop(`${base}/oauth2/device/verify?user_code=${encodeURIComponent(da.user_code)}`);
251
+ let loc = v.headers.get("location");
252
+ for (let i = 0; i < 25 && loc; i++) {
253
+ const u = new URL(loc, base);
254
+ let r;
255
+ if (u.pathname.endsWith("/device")) {
256
+ const dc = u.searchParams.get("device_challenge");
257
+ if (!dc) throw new Error(`/device without device_challenge: ${loc}`);
258
+ r = await postForm("/device", { device_challenge: dc, user_code: da.user_code });
259
+ } else if (u.pathname.endsWith("/login")) {
260
+ const lc = u.searchParams.get("login_challenge");
261
+ if (!lc) throw new Error(`/login without login_challenge: ${loc}`);
262
+ r = await postForm("/login", { login_challenge: lc, account: username, password });
263
+ if (r.status === 401) throw new InputError("Sign-in failed: wrong account or password");
264
+ } else if (u.pathname.endsWith("/consent")) {
265
+ const cc = u.searchParams.get("consent_challenge");
266
+ if (!cc) throw new Error(`/consent without consent_challenge: ${loc}`);
267
+ r = await postForm("/consent", { consent_challenge: cc, decision: "allow" });
268
+ } else {
269
+ r = await hop(u.href);
270
+ }
271
+ loc = r.headers.get("location");
272
+ }
273
+ const windowMs = Math.min(opts.timeoutMs ?? Number.POSITIVE_INFINITY, da.expires_in * 1e3);
274
+ return pollDeviceToken(base, da.device_code, clientId, (da.interval ?? 5) * 1e3, windowMs);
275
+ }
276
+
277
+ // src/api/headers.ts
278
+ function buildHeaders(ctx, extra) {
279
+ return {
280
+ // No token = a no-auth platform (no bkn-safe); send no Authorization.
281
+ ...ctx.token ? { authorization: `Bearer ${ctx.token}`, token: ctx.token } : {},
282
+ "x-business-domain": ctx.businessDomain,
283
+ ...extra
284
+ };
285
+ }
286
+
287
+ // src/api/tls.ts
288
+ function applyTls(ctx) {
289
+ if (ctx.insecure && process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0") {
290
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
291
+ }
292
+ }
293
+
294
+ // src/api/http.ts
295
+ var DEFAULT_TIMEOUT_MS = 3e4;
296
+ async function request(ctx, path, init = {}) {
297
+ const url = new URL(path.startsWith("http") ? path : `${ctx.baseUrl}${path}`);
298
+ for (const [k, v] of Object.entries(init.query ?? {})) {
299
+ if (v !== void 0) url.searchParams.set(k, String(v));
300
+ }
301
+ applyTls(ctx);
302
+ const hasBody = init.body !== void 0;
303
+ const controller = new AbortController();
304
+ const timer = setTimeout(() => controller.abort(), init.timeoutMs ?? DEFAULT_TIMEOUT_MS);
305
+ const send = () => fetch(url, {
306
+ method: init.method ?? (hasBody ? "POST" : "GET"),
307
+ headers: buildHeaders(ctx, {
308
+ ...hasBody ? { "content-type": "application/json" } : {},
309
+ ...init.headers
310
+ }),
311
+ body: hasBody ? JSON.stringify(init.body) : void 0,
312
+ signal: controller.signal
313
+ });
314
+ try {
315
+ let res = await send();
316
+ if (res.status === 401 && ctx.refresh && await tryRefresh(ctx)) {
317
+ res = await send();
318
+ }
319
+ const text = await res.text();
320
+ if (!res.ok) throw new HttpError(res.status, res.statusText, text, hintFor(ctx, res.status));
321
+ return text ? JSON.parse(text) : void 0;
322
+ } finally {
323
+ clearTimeout(timer);
324
+ }
325
+ }
326
+ function hintFor(ctx, status2) {
327
+ if (status2 === 401 && ctx.token.startsWith("bak_")) {
328
+ return "AppKey invalid / expired / revoked / owner disabled \u2014 re-issue with `openbkn appkey create` (or `appkey regenerate <id>`). Do not auto-retry.";
329
+ }
330
+ return void 0;
331
+ }
332
+ async function tryRefresh(ctx) {
333
+ if (!ctx.refresh) return false;
334
+ try {
335
+ const t = await refreshAccessToken(ctx.baseUrl, ctx.refresh.refreshToken, ctx.refresh.clientId);
336
+ ctx.token = t.accessToken;
337
+ if (t.refreshToken) ctx.refresh.refreshToken = t.refreshToken;
338
+ ctx.refresh.persist(t);
339
+ return true;
340
+ } catch {
341
+ return false;
342
+ }
343
+ }
344
+
345
+ // src/types.ts
346
+ var DEFAULT_BUSINESS_DOMAIN = "bd_public";
347
+ var DEFAULT_LIST_LIMIT = 30;
348
+ var DEFAULT_QUERY_LIMIT = 50;
349
+
66
350
  // src/config/store.ts
67
351
  import {
68
352
  chmodSync,
@@ -220,424 +504,269 @@ function resolveContext(opts = {}) {
220
504
  }
221
505
  const normalized = baseUrl.replace(/\/+$/, "");
222
506
  const stored = readToken(normalized);
223
- const token = opts.token ?? process.env.BKN_TOKEN ?? stored?.accessToken;
224
- if (!token) {
507
+ const explicit = opts.token ?? process.env.BKN_TOKEN;
508
+ const token = explicit ?? stored?.accessToken ?? "";
509
+ if (!token && !stored?.noAuth) {
225
510
  throw new InputError("No access token. Set BKN_TOKEN or run `openbkn auth login`.");
226
511
  }
512
+ const insecure = opts.insecure ?? stored?.tlsInsecure ?? false;
513
+ const refresh = !explicit && stored?.refreshToken ? {
514
+ refreshToken: stored.refreshToken,
515
+ persist: (t) => {
516
+ writeToken(normalized, {
517
+ ...stored,
518
+ accessToken: t.accessToken,
519
+ refreshToken: t.refreshToken ?? stored.refreshToken,
520
+ idToken: t.idToken ?? stored.idToken
521
+ });
522
+ }
523
+ } : void 0;
227
524
  return {
228
525
  baseUrl: normalized,
229
526
  token,
230
527
  businessDomain: opts.businessDomain ?? readPlatformConfig(normalized).businessDomain ?? DEFAULT_BUSINESS_DOMAIN,
231
- insecure: opts.insecure ?? stored?.tlsInsecure ?? false
528
+ insecure,
529
+ ...refresh ? { refresh } : {}
232
530
  };
233
531
  }
234
532
 
235
- // src/api/headers.ts
236
- function buildHeaders(ctx, extra) {
237
- return {
238
- authorization: `Bearer ${ctx.token}`,
239
- token: ctx.token,
240
- "x-business-domain": ctx.businessDomain,
241
- ...extra
242
- };
243
- }
244
-
245
- // src/api/tls.ts
246
- function applyTls(ctx) {
247
- if (ctx.insecure && process.env.NODE_TLS_REJECT_UNAUTHORIZED !== "0") {
248
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
249
- }
250
- }
251
-
252
- // src/api/http.ts
253
- var DEFAULT_TIMEOUT_MS = 3e4;
254
- async function request(ctx, path, init = {}) {
255
- const url = new URL(path.startsWith("http") ? path : `${ctx.baseUrl}${path}`);
256
- for (const [k, v] of Object.entries(init.query ?? {})) {
257
- if (v !== void 0) url.searchParams.set(k, String(v));
258
- }
259
- applyTls(ctx);
260
- const hasBody = init.body !== void 0;
261
- const controller = new AbortController();
262
- const timer = setTimeout(() => controller.abort(), init.timeoutMs ?? DEFAULT_TIMEOUT_MS);
263
- 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
- });
273
- const text = await res.text();
274
- if (!res.ok) throw new HttpError(res.status, res.statusText, text);
275
- return text ? JSON.parse(text) : void 0;
276
- } finally {
277
- clearTimeout(timer);
278
- }
279
- }
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")
533
+ // src/api/safe.ts
534
+ var ADMIN = "/api/safe/v1/admin";
535
+ function notOnSafe(operation) {
536
+ throw new InputError(
537
+ `'${operation}' is not available on bkn-safe \u2014 its admin API has no such endpoint. See docs/exec-plans/admin-bkn-safe-migration.md.`
312
538
  );
313
- return buf.toString("base64");
314
539
  }
315
-
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."
327
- );
328
- }
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
- }
540
+ function listUsersSafe(ctx, opts = {}) {
541
+ return request(ctx, `${ADMIN}/users`, {
542
+ query: { search: opts.search || void 0, offset: opts.offset, limit: opts.limit }
358
543
  });
359
544
  }
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;
545
+ function getUserSafe(ctx, userId) {
546
+ return request(ctx, `${ADMIN}/users/${encodeURIComponent(userId)}`);
372
547
  }
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
548
+ function createUserSafe(ctx, input) {
549
+ return request(ctx, `${ADMIN}/users`, {
550
+ method: "POST",
551
+ body: {
552
+ account: input.account,
553
+ password: input.password,
554
+ ...input.name ? { name: input.name } : {},
555
+ ...input.email ? { email: input.email } : {},
556
+ ...input.accountType ? { account_type: input.accountType } : {},
557
+ ...input.id ? { id: input.id } : {}
381
558
  }
382
559
  });
383
560
  }
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
561
+ function updateUserSafe(ctx, userId, input) {
562
+ return request(ctx, `${ADMIN}/users/${encodeURIComponent(userId)}`, {
563
+ method: "PUT",
564
+ body: {
565
+ ...input.name !== void 0 ? { name: input.name } : {},
566
+ ...input.email !== void 0 ? { email: input.email } : {},
567
+ ...input.telephone !== void 0 ? { telephone: input.telephone } : {},
568
+ ...input.enabled !== void 0 ? { enabled: input.enabled } : {},
569
+ ...input.accountType !== void 0 ? { account_type: input.accountType } : {}
390
570
  }
391
571
  });
392
572
  }
393
- function getRole(ctx, roleId) {
394
- return request(ctx, `${AUTHZ}/roles/${encodeURIComponent(roleId)}`);
573
+ async function deleteUserSafe(ctx, userId) {
574
+ await request(ctx, `${ADMIN}/users/${encodeURIComponent(userId)}`, { method: "DELETE" });
575
+ return { ok: true };
395
576
  }
396
- function getUser(ctx, userId) {
397
- return shareMgnt(ctx, "Usrm_GetUserInfo", [userId]);
577
+ async function setUserPasswordSafe(ctx, userId, password) {
578
+ await request(ctx, `${ADMIN}/users/${encodeURIComponent(userId)}/password`, {
579
+ method: "PUT",
580
+ body: { password }
581
+ });
582
+ return { ok: true };
398
583
  }
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
- }
584
+ function getUserRolesSafe(ctx, userId) {
585
+ return request(ctx, `${ADMIN}/role-bindings`, { query: { accessor_id: userId } });
423
586
  }
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
- }
587
+ async function assignRoleSafe(ctx, accessorId, roleId) {
588
+ await request(ctx, `${ADMIN}/role-bindings`, {
589
+ method: "POST",
590
+ body: { accessor_id: accessorId, role_id: roleId }
591
+ });
592
+ return { ok: true };
432
593
  }
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 }
594
+ async function removeRoleSafe(ctx, accessorId, roleId) {
595
+ await request(ctx, `${ADMIN}/role-bindings`, {
596
+ method: "DELETE",
597
+ body: { accessor_id: accessorId, role_id: roleId }
436
598
  });
599
+ return { ok: true };
437
600
  }
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"
601
+ function listDepartmentsSafe(ctx, opts = {}) {
602
+ return request(ctx, `${ADMIN}/departments`, {
603
+ query: {
604
+ search: opts.search || void 0,
605
+ parent_id: opts.parentId,
606
+ offset: opts.offset,
607
+ limit: opts.limit
608
+ }
466
609
  });
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
610
  }
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 };
611
+ function getDepartmentSafe(ctx, deptId) {
612
+ return request(ctx, `${ADMIN}/departments/${encodeURIComponent(deptId)}`);
540
613
  }
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 };
614
+ function getDepartmentMembersSafe(ctx, deptId) {
615
+ return request(ctx, `${ADMIN}/departments/${encodeURIComponent(deptId)}/members`);
547
616
  }
548
- var EACP = "/api/eacp/v1";
549
- function listAuditLogs(ctx, opts = {}) {
550
- return request(ctx, `${EACP}/auth1/login-log`, {
617
+ function createDepartmentSafe(ctx, input) {
618
+ return request(ctx, `${ADMIN}/departments`, {
551
619
  method: "POST",
552
620
  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
621
+ name: input.name,
622
+ ...input.parentId ? { parent_id: input.parentId } : {},
623
+ ...input.type ? { type: input.type } : {},
624
+ ...input.id ? { id: input.id } : {}
558
625
  }
559
626
  });
560
627
  }
561
- function changePassword(ctx, account, oldPassword, newPassword) {
562
- return request(ctx, `${EACP}/auth1/modifypassword`, {
563
- method: "POST",
628
+ function updateDepartmentSafe(ctx, deptId, input) {
629
+ return request(ctx, `${ADMIN}/departments/${encodeURIComponent(deptId)}`, {
630
+ method: "PUT",
564
631
  body: {
565
- account,
566
- oldpwd: encryptModifyPwd(oldPassword),
567
- newpwd: encryptModifyPwd(newPassword)
632
+ ...input.name !== void 0 ? { name: input.name } : {},
633
+ ...input.parentId !== void 0 ? { parent_id: input.parentId } : {},
634
+ ...input.type !== void 0 ? { type: input.type } : {}
568
635
  }
569
636
  });
570
637
  }
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
638
+ async function deleteDepartmentSafe(ctx, deptId) {
639
+ await request(ctx, `${ADMIN}/departments/${encodeURIComponent(deptId)}`, { method: "DELETE" });
640
+ return { ok: true };
641
+ }
642
+ async function buildDepartmentTree(ctx) {
643
+ const res = await listDepartmentsSafe(ctx, { limit: 1e3 });
644
+ const flat = Array.isArray(res) ? res : res.departments ?? [];
645
+ const byId = /* @__PURE__ */ new Map();
646
+ for (const d of flat) if (d.id) byId.set(d.id, { ...d, children: [] });
647
+ const roots = [];
648
+ for (const d of byId.values()) {
649
+ const parent = d.parent_id ? byId.get(d.parent_id) : void 0;
650
+ if (parent) parent.children?.push(d);
651
+ else roots.push(d);
652
+ }
653
+ return roots;
654
+ }
655
+ function listRolesSafe(ctx, source) {
656
+ return request(ctx, `${ADMIN}/roles`, { query: { source: source || void 0 } });
657
+ }
658
+ function getRoleSafe(ctx, roleId) {
659
+ return request(ctx, `${ADMIN}/roles/${encodeURIComponent(roleId)}`);
660
+ }
661
+ function roleMembersSafe(ctx, roleId) {
662
+ return request(ctx, `${ADMIN}/roles/${encodeURIComponent(roleId)}/members`);
663
+ }
664
+ function createRoleSafe(ctx, input) {
665
+ return request(ctx, `${ADMIN}/roles`, {
666
+ method: "POST",
667
+ body: {
668
+ name: input.name,
669
+ ...input.description ? { description: input.description } : {},
670
+ ...input.id ? { id: input.id } : {}
577
671
  }
578
672
  });
579
673
  }
580
- function modifyRoleMembers(ctx, roleId, method, members) {
581
- return request(ctx, `${AUTHZ}/role-members/${encodeURIComponent(roleId)}`, {
582
- method: "POST",
583
- body: { method, members }
674
+ function updateRoleSafe(ctx, roleId, input) {
675
+ return request(ctx, `${ADMIN}/roles/${encodeURIComponent(roleId)}`, {
676
+ method: "PUT",
677
+ body: {
678
+ ...input.name !== void 0 ? { name: input.name } : {},
679
+ ...input.description !== void 0 ? { description: input.description } : {}
680
+ }
584
681
  });
585
682
  }
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;
683
+ async function deleteRoleSafe(ctx, roleId) {
684
+ await request(ctx, `${ADMIN}/roles/${encodeURIComponent(roleId)}`, { method: "DELETE" });
685
+ return { ok: true };
602
686
  }
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 "}`));
687
+ async function setRolePermissionSafe(ctx, roleId, grant, perm) {
688
+ await request(ctx, `${ADMIN}/roles/${encodeURIComponent(roleId)}/permissions`, {
689
+ method: grant ? "POST" : "DELETE",
690
+ body: {
691
+ resource: { type: perm.resourceType, id: perm.resourceId },
692
+ operations: perm.operations
610
693
  }
611
694
  });
612
- return lines.join("\n");
695
+ return { ok: true };
613
696
  }
614
697
 
615
698
  // src/resources/admin.ts
699
+ var DEFAULT_NEW_USER_PASSWORD = "openbkn";
616
700
  function admin(ctx) {
617
701
  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)
702
+ // ── departments ──
703
+ orgList: (opts) => listDepartmentsSafe(ctx, { search: opts?.name, offset: opts?.offset, limit: opts?.limit }),
704
+ orgGet: (deptId) => getDepartmentSafe(ctx, deptId),
705
+ orgTree: (_role) => buildDepartmentTree(ctx),
706
+ orgMembers: (deptId, _opts) => getDepartmentMembersSafe(ctx, deptId),
707
+ orgCreate: (input) => createDepartmentSafe(ctx, { name: input.name, parentId: input.parentId }),
708
+ orgUpdate: (deptId, input) => updateDepartmentSafe(ctx, deptId, { name: input.name }),
709
+ orgDelete: (deptId) => deleteDepartmentSafe(ctx, deptId),
710
+ // ── users ──
711
+ userList: (opts) => listUsersSafe(ctx, { search: opts?.name, offset: opts?.offset, limit: opts?.limit }),
712
+ userGet: (userId) => getUserSafe(ctx, userId),
713
+ userRoles: async (userId) => {
714
+ const [bound, all] = await Promise.all([getUserRolesSafe(ctx, userId), listRolesSafe(ctx)]);
715
+ const ids = bound.role_ids ?? [];
716
+ const nameById = new Map(
717
+ (all.roles ?? []).map((r) => [
718
+ r.id,
719
+ r.name
720
+ ])
721
+ );
722
+ return { roles: ids.map((id) => ({ name: nameById.get(id) ?? id, id })) };
723
+ },
724
+ userCreate: (input) => createUserSafe(ctx, {
725
+ account: input.loginName,
726
+ password: DEFAULT_NEW_USER_PASSWORD,
727
+ name: input.displayName,
728
+ email: input.email
729
+ }),
730
+ userUpdate: (userId, input) => updateUserSafe(ctx, userId, {
731
+ name: input.displayName,
732
+ email: input.email,
733
+ telephone: input.telNumber
734
+ }),
735
+ userDelete: (userId) => deleteUserSafe(ctx, userId),
736
+ userResetPassword: (userId, newPassword) => setUserPasswordSafe(ctx, userId, newPassword),
737
+ // ── roles ──
738
+ roleList: (_opts) => listRolesSafe(ctx),
739
+ roleGet: (roleId) => getRoleSafe(ctx, roleId),
740
+ roleMembers: async (roleId, _opts) => {
741
+ const [mem, users] = await Promise.all([
742
+ roleMembersSafe(ctx, roleId),
743
+ listUsersSafe(ctx, { limit: 500 })
744
+ ]);
745
+ const ids = mem.accessor_ids ?? [];
746
+ const nameById = new Map(
747
+ (users.users ?? []).map((u) => [u.id, u.account ?? u.name ?? u.id])
748
+ );
749
+ return { members: ids.map((id) => ({ account: nameById.get(id) ?? id, id })) };
750
+ },
751
+ addRoleMember: (roleId, id, _type = "user") => assignRoleSafe(ctx, id, roleId),
752
+ removeRoleMember: (roleId, id, _type = "user") => removeRoleSafe(ctx, id, roleId),
753
+ roleCreate: (name, description) => createRoleSafe(ctx, { name, description }),
754
+ roleUpdate: (roleId, input) => updateRoleSafe(ctx, roleId, input),
755
+ roleDelete: (roleId) => deleteRoleSafe(ctx, roleId),
756
+ rolePermission: (roleId, grant, resourceType, resourceId, operations) => setRolePermissionSafe(ctx, roleId, grant, { resourceType, resourceId, operations }),
757
+ auditList: (_opts) => notOnSafe("audit list")
638
758
  };
639
759
  }
640
760
 
761
+ // src/api/auth-fetch.ts
762
+ async function authFetch(ctx, send) {
763
+ let res = await send();
764
+ if (res.status === 401 && ctx.refresh && await tryRefresh(ctx)) {
765
+ res = await send();
766
+ }
767
+ return res;
768
+ }
769
+
641
770
  // src/api/agent-chat.ts
642
771
  var FACTORY = "/api/agent-factory";
643
772
  async function fetchAgentInfo(ctx, agentId, version = "v0") {
@@ -706,16 +835,19 @@ async function sendChat(ctx, info, query, opts = {}) {
706
835
  stream: Boolean(opts.stream)
707
836
  };
708
837
  if (opts.conversationId) body.conversation_id = opts.conversationId;
709
- const res = await fetch(`${ctx.baseUrl}${FACTORY}/v1/app/${info.key}/chat/completion`, {
710
- method: "POST",
711
- headers: {
712
- ...buildHeaders(ctx),
713
- "content-type": "application/json",
714
- accept: opts.stream ? "text/event-stream" : "application/json",
715
- "x-language": "zh-CN"
716
- },
717
- body: JSON.stringify(body)
718
- });
838
+ const res = await authFetch(
839
+ ctx,
840
+ () => fetch(`${ctx.baseUrl}${FACTORY}/v1/app/${info.key}/chat/completion`, {
841
+ method: "POST",
842
+ headers: {
843
+ ...buildHeaders(ctx),
844
+ "content-type": "application/json",
845
+ accept: opts.stream ? "text/event-stream" : "application/json",
846
+ "x-language": "zh-CN"
847
+ },
848
+ body: JSON.stringify(body)
849
+ })
850
+ );
719
851
  if (!res.ok) throw new HttpError(res.status, res.statusText, await res.text());
720
852
  const contentType = res.headers.get("content-type") ?? "";
721
853
  if (opts.stream && contentType.includes("text/event-stream")) {
@@ -907,6 +1039,9 @@ function nextId() {
907
1039
  function mcpUrl(ctx) {
908
1040
  return `${ctx.baseUrl}${MCP_PATH}`;
909
1041
  }
1042
+ function mcpInfo(ctx) {
1043
+ return request(ctx, `${MCP_PATH}/info`);
1044
+ }
910
1045
  function headers(ctx, knId, sessionId) {
911
1046
  const h = {
912
1047
  "content-type": "application/json",
@@ -929,11 +1064,14 @@ function parseBody(text) {
929
1064
  }
930
1065
  async function post(ctx, knId, sessionId, body) {
931
1066
  applyTls(ctx);
932
- const res = await fetch(mcpUrl(ctx), {
933
- method: "POST",
934
- headers: headers(ctx, knId, sessionId),
935
- body: JSON.stringify(body)
936
- });
1067
+ const res = await authFetch(
1068
+ ctx,
1069
+ () => fetch(mcpUrl(ctx), {
1070
+ method: "POST",
1071
+ headers: headers(ctx, knId, sessionId),
1072
+ body: JSON.stringify(body)
1073
+ })
1074
+ );
937
1075
  const text = await res.text();
938
1076
  if (!res.ok) throw new HttpError(res.status, res.statusText, text);
939
1077
  return { res, text };
@@ -1044,8 +1182,12 @@ function context(ctx) {
1044
1182
  searchSchema: (knId, query, opts) => searchSchema(ctx, knId, query, opts),
1045
1183
  queryObjectInstance: (knId, args) => queryObjectInstance(ctx, knId, args),
1046
1184
  findSkills: (knId, objectTypeId, topK) => findSkills(ctx, knId, objectTypeId, topK),
1185
+ info: () => mcpInfo(ctx),
1047
1186
  tools: (knId) => listTools(ctx, knId),
1048
1187
  toolCall: (knId, name, args) => callTool(ctx, knId, name, args),
1188
+ // Generic MCP method passthrough — covers methods not yet wrapped, so the
1189
+ // surface doesn't have to grow every time the server adds one.
1190
+ callMethod: (knId, method, params) => callMethod(ctx, knId, method, params),
1049
1191
  queryInstanceSubgraph: (knId, args) => queryInstanceSubgraph(ctx, knId, args),
1050
1192
  logicProperties: (knId, args) => getLogicProperties(ctx, knId, args),
1051
1193
  actionInfo: (knId, args) => getActionInfo(ctx, knId, args),
@@ -2611,13 +2753,16 @@ async function uploadBkn(ctx, tarBuffer, opts = {}) {
2611
2753
  applyTls(ctx);
2612
2754
  const url = new URL(`${ctx.baseUrl}${BKNS}`);
2613
2755
  url.searchParams.set("branch", opts.branch ?? "main");
2614
- const form = new FormData();
2615
- form.append(
2756
+ const form2 = new FormData();
2757
+ form2.append(
2616
2758
  "file",
2617
2759
  new Blob([new Uint8Array(tarBuffer)], { type: "application/octet-stream" }),
2618
2760
  "bkn.tar"
2619
2761
  );
2620
- const res = await fetch(url, { method: "POST", headers: buildHeaders(ctx), body: form });
2762
+ const res = await authFetch(
2763
+ ctx,
2764
+ () => fetch(url, { method: "POST", headers: buildHeaders(ctx), body: form2 })
2765
+ );
2621
2766
  const text = await res.text();
2622
2767
  if (!res.ok) throw new HttpError(res.status, res.statusText, text);
2623
2768
  return text ? JSON.parse(text) : void 0;
@@ -2626,7 +2771,7 @@ async function downloadBkn(ctx, knId, opts = {}) {
2626
2771
  applyTls(ctx);
2627
2772
  const url = new URL(`${ctx.baseUrl}${BKNS}/${encodeURIComponent(knId)}`);
2628
2773
  url.searchParams.set("branch", opts.branch ?? "main");
2629
- const res = await fetch(url, { method: "GET", headers: buildHeaders(ctx) });
2774
+ const res = await authFetch(ctx, () => fetch(url, { method: "GET", headers: buildHeaders(ctx) }));
2630
2775
  if (!res.ok) throw new HttpError(res.status, res.statusText, await res.text());
2631
2776
  return Buffer.from(await res.arrayBuffer());
2632
2777
  }
@@ -2751,6 +2896,9 @@ async function getBuildTask(ctx, taskId) {
2751
2896
  const res = await request(ctx, `${VEGA_BASE}/build-tasks/${encodeURIComponent(taskId)}`);
2752
2897
  return BuildTask.parse(res);
2753
2898
  }
2899
+ function runSql(ctx, body) {
2900
+ return request(ctx, `${VEGA_BASE}/resources/query`, { method: "POST", body });
2901
+ }
2754
2902
  async function listCatalogs(ctx, opts = {}) {
2755
2903
  return request(ctx, `${VEGA_BASE}/catalogs`, {
2756
2904
  query: { limit: opts.limit, offset: opts.offset }
@@ -2973,9 +3121,9 @@ function extractTarToDirectory(tarBuffer, dirPath) {
2973
3121
  }
2974
3122
 
2975
3123
  // 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";
3124
+ import { readdirSync as readdirSync3, statSync as statSync2 } from "fs";
3125
+ import { readFile } from "fs/promises";
3126
+ import { basename, dirname, resolve as resolve2 } from "path";
2979
3127
  import { parse } from "csv-parse/sync";
2980
3128
  async function parseCsvFile(filePath) {
2981
3129
  let content = await readFile(filePath, "utf8");
@@ -3039,13 +3187,25 @@ function buildImportDag(opts) {
3039
3187
  ]
3040
3188
  };
3041
3189
  }
3190
+ function globOne(pattern) {
3191
+ const dir = dirname(pattern);
3192
+ const re = new RegExp(
3193
+ `^${basename(pattern).replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`
3194
+ );
3195
+ let entries;
3196
+ try {
3197
+ entries = readdirSync3(resolve2(dir));
3198
+ } catch {
3199
+ return [];
3200
+ }
3201
+ return entries.filter((f) => re.test(f)).map((f) => resolve2(dir, f));
3202
+ }
3042
3203
  async function resolveFiles(pattern) {
3043
3204
  const out = [];
3044
3205
  for (const part of pattern.split(",").map((p) => p.trim()).filter(Boolean)) {
3045
3206
  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));
3207
+ for (const p of globOne(part)) {
3208
+ if (/\.csv$/i.test(p)) out.push(p);
3049
3209
  }
3050
3210
  } else {
3051
3211
  statSync2(resolve2(part));
@@ -3524,15 +3684,18 @@ function deltaContent(chunk) {
3524
3684
  }
3525
3685
  async function chatCompletionsStream(ctx, model, messages, onDelta) {
3526
3686
  applyTls(ctx);
3527
- const res = await fetch(`${ctx.baseUrl}${API}/chat/completions`, {
3528
- method: "POST",
3529
- headers: {
3530
- ...buildHeaders(ctx),
3531
- "content-type": "application/json",
3532
- accept: "text/event-stream"
3533
- },
3534
- body: JSON.stringify({ model, messages, stream: true })
3535
- });
3687
+ const res = await authFetch(
3688
+ ctx,
3689
+ () => fetch(`${ctx.baseUrl}${API}/chat/completions`, {
3690
+ method: "POST",
3691
+ headers: {
3692
+ ...buildHeaders(ctx),
3693
+ "content-type": "application/json",
3694
+ accept: "text/event-stream"
3695
+ },
3696
+ body: JSON.stringify({ model, messages, stream: true })
3697
+ })
3698
+ );
3536
3699
  if (!res.ok) throw new HttpError(res.status, res.statusText, await res.text());
3537
3700
  const reader = res.body?.getReader();
3538
3701
  if (!reader) throw new Error("No response body for stream");
@@ -3628,45 +3791,54 @@ function resources(ctx) {
3628
3791
 
3629
3792
  // src/resources/skills.ts
3630
3793
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
3631
- import { basename as basename2, dirname as dirname2, resolve as resolve5 } from "path";
3794
+ import { basename as basename2, dirname as dirname3, resolve as resolve5 } from "path";
3632
3795
 
3633
3796
  // src/api/skills.ts
3634
3797
  var BASE5 = "/api/agent-operator-integration/v1";
3635
3798
  async function registerSkillZip(ctx, bytes, opts = {}) {
3636
3799
  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));
3642
- const res = await fetch(`${ctx.baseUrl}${BASE5}/skills`, {
3643
- method: "POST",
3644
- headers: buildHeaders(ctx),
3645
- body: form
3646
- });
3800
+ const form2 = new FormData();
3801
+ form2.set("file_type", "zip");
3802
+ form2.set("file", new Blob([bytes]), opts.filename ?? "skill.zip");
3803
+ if (opts.source) form2.set("source", opts.source);
3804
+ if (opts.extendInfo) form2.set("extend_info", JSON.stringify(opts.extendInfo));
3805
+ const res = await authFetch(
3806
+ ctx,
3807
+ () => fetch(`${ctx.baseUrl}${BASE5}/skills`, {
3808
+ method: "POST",
3809
+ headers: buildHeaders(ctx),
3810
+ body: form2
3811
+ })
3812
+ );
3647
3813
  const text = await res.text();
3648
3814
  if (!res.ok) throw new HttpError(res.status, res.statusText, text);
3649
3815
  return text ? JSON.parse(text) : void 0;
3650
3816
  }
3651
3817
  async function updateSkillPackageZip(ctx, skillId, bytes, filename = "skill.zip") {
3652
3818
  applyTls(ctx);
3653
- const form = new FormData();
3654
- form.set("file_type", "zip");
3655
- form.set("file", new Blob([bytes]), filename);
3656
- const res = await fetch(`${ctx.baseUrl}${BASE5}/skills/${encodeURIComponent(skillId)}/package`, {
3657
- method: "PUT",
3658
- headers: buildHeaders(ctx),
3659
- body: form
3660
- });
3819
+ const form2 = new FormData();
3820
+ form2.set("file_type", "zip");
3821
+ form2.set("file", new Blob([bytes]), filename);
3822
+ const res = await authFetch(
3823
+ ctx,
3824
+ () => fetch(`${ctx.baseUrl}${BASE5}/skills/${encodeURIComponent(skillId)}/package`, {
3825
+ method: "PUT",
3826
+ headers: buildHeaders(ctx),
3827
+ body: form2
3828
+ })
3829
+ );
3661
3830
  const text = await res.text();
3662
3831
  if (!res.ok) throw new HttpError(res.status, res.statusText, text);
3663
3832
  return text ? JSON.parse(text) : void 0;
3664
3833
  }
3665
3834
  async function downloadSkill(ctx, skillId) {
3666
3835
  applyTls(ctx);
3667
- const res = await fetch(`${ctx.baseUrl}${BASE5}/skills/${encodeURIComponent(skillId)}/download`, {
3668
- headers: buildHeaders(ctx)
3669
- });
3836
+ const res = await authFetch(
3837
+ ctx,
3838
+ () => fetch(`${ctx.baseUrl}${BASE5}/skills/${encodeURIComponent(skillId)}/download`, {
3839
+ headers: buildHeaders(ctx)
3840
+ })
3841
+ );
3670
3842
  if (!res.ok) throw new HttpError(res.status, res.statusText, await res.text());
3671
3843
  return new Uint8Array(await res.arrayBuffer());
3672
3844
  }
@@ -3730,11 +3902,11 @@ function setSkillStatus(ctx, skillId, status2) {
3730
3902
  }
3731
3903
 
3732
3904
  // 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";
3905
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync4, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
3906
+ import { dirname as dirname2, join as join3, relative, resolve as resolve4, sep } from "path";
3735
3907
  import JSZip from "jszip";
3736
3908
  function walk(dir, base, files) {
3737
- for (const entry of readdirSync3(dir)) {
3909
+ for (const entry of readdirSync4(dir)) {
3738
3910
  const full = join3(dir, entry);
3739
3911
  const st = statSync3(full);
3740
3912
  if (st.isDirectory()) walk(full, base, files);
@@ -3762,7 +3934,7 @@ async function unzipToDirectory(bytes, dir) {
3762
3934
  for (const entry of entries) {
3763
3935
  if (entry.dir) continue;
3764
3936
  const out = join3(abs, entry.name);
3765
- mkdirSync3(dirname(out), { recursive: true });
3937
+ mkdirSync3(dirname2(out), { recursive: true });
3766
3938
  writeFileSync2(out, await entry.async("nodebuffer"));
3767
3939
  written.push(out);
3768
3940
  }
@@ -3795,7 +3967,7 @@ function skills(ctx) {
3795
3967
  download: async (skillId, outPath) => {
3796
3968
  const bytes = await downloadSkill(ctx, skillId);
3797
3969
  const dest = resolve5(outPath ?? `${skillId}.zip`);
3798
- mkdirSync4(dirname2(dest), { recursive: true });
3970
+ mkdirSync4(dirname3(dest), { recursive: true });
3799
3971
  writeFileSync3(dest, bytes);
3800
3972
  return { skillId, path: dest, bytes: bytes.length };
3801
3973
  },
@@ -3811,7 +3983,7 @@ function skills(ctx) {
3811
3983
 
3812
3984
  // src/resources/toolboxes.ts
3813
3985
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
3814
- import { dirname as dirname3, resolve as resolve6 } from "path";
3986
+ import { dirname as dirname4, resolve as resolve6 } from "path";
3815
3987
 
3816
3988
  // src/api/toolboxes.ts
3817
3989
  import { readFile as readFile2 } from "fs/promises";
@@ -3820,9 +3992,11 @@ var PATH = "/api/agent-operator-integration/v1/tool-box";
3820
3992
  var IMPEX = "/api/agent-operator-integration/v1/impex";
3821
3993
  async function exportConfig(ctx, id, type = "toolbox") {
3822
3994
  applyTls(ctx);
3823
- const res = await fetch(
3824
- `${ctx.baseUrl}${IMPEX}/export/${encodeURIComponent(type)}/${encodeURIComponent(id)}`,
3825
- { headers: buildHeaders(ctx) }
3995
+ const res = await authFetch(
3996
+ ctx,
3997
+ () => fetch(`${ctx.baseUrl}${IMPEX}/export/${encodeURIComponent(type)}/${encodeURIComponent(id)}`, {
3998
+ headers: buildHeaders(ctx)
3999
+ })
3826
4000
  );
3827
4001
  const buf = new Uint8Array(await res.arrayBuffer());
3828
4002
  if (!res.ok) throw new HttpError(res.status, res.statusText, new TextDecoder().decode(buf));
@@ -3831,13 +4005,16 @@ async function exportConfig(ctx, id, type = "toolbox") {
3831
4005
  async function importConfig(ctx, filePath, type = "toolbox") {
3832
4006
  applyTls(ctx);
3833
4007
  const buf = await readFile2(filePath);
3834
- const form = new FormData();
3835
- form.append("data", new Blob([new Uint8Array(buf)]), basename3(filePath));
3836
- const res = await fetch(`${ctx.baseUrl}${IMPEX}/import/${encodeURIComponent(type)}`, {
3837
- method: "POST",
3838
- headers: buildHeaders(ctx),
3839
- body: form
3840
- });
4008
+ const form2 = new FormData();
4009
+ form2.append("data", new Blob([new Uint8Array(buf)]), basename3(filePath));
4010
+ const res = await authFetch(
4011
+ ctx,
4012
+ () => fetch(`${ctx.baseUrl}${IMPEX}/import/${encodeURIComponent(type)}`, {
4013
+ method: "POST",
4014
+ headers: buildHeaders(ctx),
4015
+ body: form2
4016
+ })
4017
+ );
3841
4018
  const text = await res.text();
3842
4019
  if (!res.ok) throw new HttpError(res.status, res.statusText, text);
3843
4020
  return text ? JSON.parse(text) : text;
@@ -3845,14 +4022,17 @@ async function importConfig(ctx, filePath, type = "toolbox") {
3845
4022
  async function uploadTool(ctx, boxId, filePath, metadataType = "openapi") {
3846
4023
  applyTls(ctx);
3847
4024
  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));
3851
- const res = await fetch(`${ctx.baseUrl}${PATH}/${encodeURIComponent(boxId)}/tool`, {
3852
- method: "POST",
3853
- headers: buildHeaders(ctx),
3854
- body: form
3855
- });
4025
+ const form2 = new FormData();
4026
+ form2.append("metadata_type", metadataType);
4027
+ form2.append("data", new Blob([new Uint8Array(buf)]), basename3(filePath));
4028
+ const res = await authFetch(
4029
+ ctx,
4030
+ () => fetch(`${ctx.baseUrl}${PATH}/${encodeURIComponent(boxId)}/tool`, {
4031
+ method: "POST",
4032
+ headers: buildHeaders(ctx),
4033
+ body: form2
4034
+ })
4035
+ );
3856
4036
  const text = await res.text();
3857
4037
  if (!res.ok) throw new HttpError(res.status, res.statusText, text);
3858
4038
  return text ? JSON.parse(text) : text;
@@ -3937,7 +4117,7 @@ function toolboxes(ctx) {
3937
4117
  export: async (id, outPath, type) => {
3938
4118
  const bytes = await exportConfig(ctx, id, type);
3939
4119
  const dest = resolve6(outPath);
3940
- mkdirSync5(dirname3(dest), { recursive: true });
4120
+ mkdirSync5(dirname4(dest), { recursive: true });
3941
4121
  writeFileSync4(dest, bytes);
3942
4122
  return { id, path: dest, bytes: bytes.length };
3943
4123
  },
@@ -4017,7 +4197,7 @@ async function getSpansByConversation(ctx, conversationId, opts = {}) {
4017
4197
  }
4018
4198
 
4019
4199
  // src/trace-ai/claude-judge.ts
4020
- import { spawn, spawnSync as spawnSync2 } from "child_process";
4200
+ import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
4021
4201
  var ClaudeJudgeError = class extends Error {
4022
4202
  constructor(message, reason) {
4023
4203
  super(message);
@@ -4063,7 +4243,7 @@ async function judgeJson(prompt, opts = {}) {
4063
4243
  const timeoutMs = opts.timeoutMs ?? 12e4;
4064
4244
  const args = ["-p", "--output-format=json", "--dangerously-skip-permissions"];
4065
4245
  const stdout = await new Promise((resolve7, reject) => {
4066
- const child = spawn(binary, args, { stdio: ["pipe", "pipe", "pipe"] });
4246
+ const child = spawn2(binary, args, { stdio: ["pipe", "pipe", "pipe"] });
4067
4247
  let out = "";
4068
4248
  let timedOut = false;
4069
4249
  const killer = setTimeout(() => {
@@ -4762,6 +4942,8 @@ function vega(ctx) {
4762
4942
  catalogHealth: (ids) => catalogHealthStatus(ctx, ids),
4763
4943
  connectorTypes: () => listConnectorTypes(ctx),
4764
4944
  connectorType: (type) => getConnectorType(ctx, type),
4945
+ /** Run SQL / OpenSearch DSL directly against a data source. */
4946
+ sql: (body) => runSql(ctx, body),
4765
4947
  /** Build a resource's index. With `wait`, polls until terminal. */
4766
4948
  build: async (req, opts = {}) => {
4767
4949
  const task = await createBuildTask(ctx, req);
@@ -4825,22 +5007,69 @@ async function rawCall(ctx, path, opts = {}) {
4825
5007
  if (!extra["content-type"]) extra["content-type"] = "application/json";
4826
5008
  }
4827
5009
  const method = opts.method ?? (body !== void 0 ? "POST" : "GET");
4828
- const headers2 = buildHeaders(
4829
- { ...ctx, businessDomain: opts.businessDomain ?? ctx.businessDomain },
4830
- extra
4831
- );
5010
+ const headersFor = () => buildHeaders({ ...ctx, businessDomain: opts.businessDomain ?? ctx.businessDomain }, extra);
4832
5011
  if (opts.verbose) process.stderr.write(`> ${method} ${url}
4833
5012
  `);
4834
5013
  const controller = new AbortController();
4835
5014
  const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 3e4);
4836
5015
  try {
4837
- const res = await fetch(url, { method, headers: headers2, body, signal: controller.signal });
5016
+ const res = await authFetch(
5017
+ ctx,
5018
+ () => fetch(url, { method, headers: headersFor(), body, signal: controller.signal })
5019
+ );
4838
5020
  return { status: res.status, statusText: res.statusText, body: await res.text() };
4839
5021
  } finally {
4840
5022
  clearTimeout(timer);
4841
5023
  }
4842
5024
  }
4843
5025
 
5026
+ // src/api/app-keys.ts
5027
+ var ME = "/api/safe/v1/me/api-keys";
5028
+ var ADMIN2 = "/api/safe/v1/admin/api-keys";
5029
+ function listMyApiKeys(ctx) {
5030
+ return request(ctx, ME);
5031
+ }
5032
+ function createMyApiKey(ctx, input) {
5033
+ return request(ctx, ME, {
5034
+ method: "POST",
5035
+ body: {
5036
+ name: input.name,
5037
+ ...input.expiresAt ? { expires_at: input.expiresAt } : {},
5038
+ ...input.neverExpire ? { never_expire: true } : {}
5039
+ }
5040
+ });
5041
+ }
5042
+ async function revokeMyApiKey(ctx, id) {
5043
+ await request(ctx, `${ME}/${encodeURIComponent(id)}`, { method: "DELETE" });
5044
+ }
5045
+ function regenerateMyApiKey(ctx, id) {
5046
+ return request(ctx, `${ME}/${encodeURIComponent(id)}/regenerate`, { method: "POST" });
5047
+ }
5048
+ function listApiKeysAdmin(ctx, ownerId) {
5049
+ return request(ctx, ADMIN2, { query: { owner_id: ownerId || void 0 } });
5050
+ }
5051
+ async function revokeApiKeyAdmin(ctx, id) {
5052
+ await request(ctx, `${ADMIN2}/${encodeURIComponent(id)}`, { method: "DELETE" });
5053
+ }
5054
+
5055
+ // src/resources/app-keys.ts
5056
+ function appKeys(ctx) {
5057
+ return {
5058
+ /** List the caller's own keys (no secrets). */
5059
+ list: () => listMyApiKeys(ctx),
5060
+ /** Issue a key — the result's `key` is the plaintext, shown only once. */
5061
+ create: (input) => createMyApiKey(ctx, input),
5062
+ /** Revoke one of the caller's keys (immediate). */
5063
+ revoke: (id) => revokeMyApiKey(ctx, id),
5064
+ /** Rotate a key in place — new plaintext (shown once); old secret dies now. */
5065
+ regenerate: (id) => regenerateMyApiKey(ctx, id),
5066
+ /** Admin: list all keys, or one owner's (adds `owner_user_id`). */
5067
+ adminList: (ownerId) => listApiKeysAdmin(ctx, ownerId),
5068
+ /** Admin: revoke any key. */
5069
+ adminRevoke: (id) => revokeApiKeyAdmin(ctx, id)
5070
+ };
5071
+ }
5072
+
4844
5073
  // src/client.ts
4845
5074
  function createClient(opts = {}) {
4846
5075
  const ctx = resolveContext(opts);
@@ -4856,6 +5085,7 @@ function createClient(opts = {}) {
4856
5085
  toolboxes: toolboxes(ctx),
4857
5086
  trace: trace(ctx),
4858
5087
  admin: admin(ctx),
5088
+ appKeys: appKeys(ctx),
4859
5089
  vega: vega(ctx),
4860
5090
  call: (path, callOpts) => rawCall(ctx, path, callOpts)
4861
5091
  };
@@ -4864,8 +5094,10 @@ function createClient(opts = {}) {
4864
5094
  // src/resources/auth.ts
4865
5095
  var auth_exports = {};
4866
5096
  __export(auth_exports, {
5097
+ attachNoAuth: () => attachNoAuth,
4867
5098
  attachToken: () => attachToken,
4868
5099
  currentToken: () => currentToken,
5100
+ currentTokenFresh: () => currentTokenFresh,
4869
5101
  deletePlatform: () => deletePlatform,
4870
5102
  exportCreds: () => exportCreds,
4871
5103
  hostOf: () => hostOf,
@@ -4901,12 +5133,19 @@ function attachToken(baseUrl, accessToken, opts = {}) {
4901
5133
  refreshToken: opts.refreshToken,
4902
5134
  idToken: opts.idToken,
4903
5135
  tlsInsecure: opts.insecure,
4904
- username: decodeJwt(opts.idToken ?? accessToken)?.preferred_username
5136
+ // Prefer the account the user typed (-u); device tokens carry no username.
5137
+ username: opts.username ?? decodeJwt(opts.idToken ?? accessToken)?.preferred_username
4905
5138
  };
4906
5139
  const userId = writeToken(url, token);
4907
5140
  setActivePlatform(url);
4908
5141
  return { baseUrl: url, userId, username: usernameOf(token) };
4909
5142
  }
5143
+ function attachNoAuth(baseUrl, opts = {}) {
5144
+ const url = normalize(baseUrl);
5145
+ writeToken(url, { baseUrl: url, accessToken: "", noAuth: true, tlsInsecure: opts.insecure });
5146
+ setActivePlatform(url);
5147
+ return { baseUrl: url, noAuth: true };
5148
+ }
4910
5149
  function status() {
4911
5150
  const baseUrl = activePlatform();
4912
5151
  if (!baseUrl) return { hasToken: false };
@@ -4925,6 +5164,30 @@ function currentToken() {
4925
5164
  if (!token) throw new InputError("Not logged in. Run `openbkn auth login <url> --token <t>`.");
4926
5165
  return token.accessToken;
4927
5166
  }
5167
+ async function currentTokenFresh() {
5168
+ const baseUrl = activePlatform();
5169
+ const token = baseUrl ? readToken(baseUrl) : void 0;
5170
+ if (!baseUrl || !token) {
5171
+ throw new InputError("Not logged in. Run `openbkn auth login <url> --token <t>`.");
5172
+ }
5173
+ const claims = decodeJwt(token.accessToken);
5174
+ const decodable = claims?.exp !== void 0;
5175
+ const needsRefresh = decodable ? isExpired(claims) : true;
5176
+ if (token.refreshToken && needsRefresh) {
5177
+ try {
5178
+ const t = await refreshAccessToken(baseUrl, token.refreshToken);
5179
+ writeToken(baseUrl, {
5180
+ ...token,
5181
+ accessToken: t.accessToken,
5182
+ refreshToken: t.refreshToken ?? token.refreshToken,
5183
+ idToken: t.idToken ?? token.idToken
5184
+ });
5185
+ return t.accessToken;
5186
+ } catch {
5187
+ }
5188
+ }
5189
+ return token.accessToken;
5190
+ }
4928
5191
  function whoami() {
4929
5192
  const baseUrl = activePlatform();
4930
5193
  const token = baseUrl ? readToken(baseUrl) : void 0;
@@ -4934,7 +5197,12 @@ function whoami() {
4934
5197
  "No decodable identity (token is opaque and no id_token saved). Use `auth status`."
4935
5198
  );
4936
5199
  }
4937
- return claims;
5200
+ return {
5201
+ ...claims,
5202
+ baseUrl: baseUrl ?? void 0,
5203
+ userId: baseUrl ? activeUserId(baseUrl) : void 0,
5204
+ username: usernameOf(token)
5205
+ };
4938
5206
  }
4939
5207
  function listPlatforms2() {
4940
5208
  const current = activePlatform();
@@ -4961,14 +5229,16 @@ function logout() {
4961
5229
  function deletePlatform(baseUrl, userId) {
4962
5230
  return deleteToken(normalize(baseUrl), userId);
4963
5231
  }
4964
- function switchUser(baseUrl, userId) {
5232
+ function switchUser(baseUrl, userOrName) {
4965
5233
  const url = normalize(baseUrl);
4966
- if (!readToken(url, userId)) {
4967
- throw new InputError(`No saved token for user '${userId}' on ${url}.`);
5234
+ const users = usersOf(url);
5235
+ const match = users.find((u) => u.userId === userOrName) ?? users.find((u) => (u.username ?? u.displayName) === userOrName);
5236
+ if (!match) {
5237
+ throw new InputError(`No saved user '${userOrName}' on ${url}. Try \`auth users ${url}\`.`);
4968
5238
  }
4969
- setActiveUser(url, userId);
5239
+ setActiveUser(url, match.userId);
4970
5240
  setActivePlatform(url);
4971
- return { baseUrl: url, userId };
5241
+ return { baseUrl: url, userId: match.userId, username: match.username ?? match.displayName };
4972
5242
  }
4973
5243
  function usersOf(baseUrl) {
4974
5244
  const url = normalize(baseUrl);
@@ -4988,22 +5258,27 @@ function exportCreds() {
4988
5258
  }
4989
5259
 
4990
5260
  export {
4991
- rawCall,
4992
- DEFAULT_BUSINESS_DOMAIN,
4993
- DEFAULT_LIST_LIMIT,
4994
- DEFAULT_QUERY_LIMIT,
4995
5261
  HttpError,
4996
5262
  InputError,
4997
5263
  toExitCode,
4998
5264
  formatError,
5265
+ isHeadless,
5266
+ openBrowser,
5267
+ fetchAuthStatus,
5268
+ deviceLogin,
5269
+ credentialDeviceLogin,
5270
+ request,
5271
+ rawCall,
5272
+ DEFAULT_BUSINESS_DOMAIN,
5273
+ DEFAULT_LIST_LIMIT,
5274
+ DEFAULT_QUERY_LIMIT,
5275
+ decodeJwt,
4999
5276
  activePlatform,
5000
5277
  setActivePlatform,
5001
5278
  readPlatformConfig,
5002
5279
  writePlatformConfig,
5003
5280
  resolveContext,
5004
- request,
5005
- changePassword,
5006
- renderOrgTree,
5281
+ getUserSafe,
5007
5282
  admin,
5008
5283
  agents,
5009
5284
  context,
@@ -5020,16 +5295,17 @@ export {
5020
5295
  vega,
5021
5296
  createClient,
5022
5297
  attachToken,
5298
+ attachNoAuth,
5023
5299
  status,
5024
5300
  currentToken,
5301
+ currentTokenFresh,
5025
5302
  whoami,
5026
5303
  listPlatforms2 as listPlatforms,
5027
5304
  use,
5028
5305
  logout,
5029
5306
  deletePlatform,
5030
5307
  switchUser,
5031
- usersOf,
5032
5308
  exportCreds,
5033
5309
  auth_exports
5034
5310
  };
5035
- //# sourceMappingURL=chunk-ADZ23DPF.js.map
5311
+ //# sourceMappingURL=chunk-SEKM54NB.js.map