@openbkn/bkn-sdk 0.1.1-alpha.1 → 0.1.1-alpha.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ADZ23DPF.js → chunk-SEKM54NB.js} +751 -475
- package/dist/chunk-SEKM54NB.js.map +1 -0
- package/dist/cli.js +584 -418
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +178 -37
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/dist/chunk-ADZ23DPF.js.map +0 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
|
224
|
-
|
|
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
|
|
528
|
+
insecure,
|
|
529
|
+
...refresh ? { refresh } : {}
|
|
232
530
|
};
|
|
233
531
|
}
|
|
234
532
|
|
|
235
|
-
// src/api/
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
317
|
-
|
|
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
|
-
|
|
361
|
-
|
|
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
|
|
374
|
-
return request(ctx, `${
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
|
385
|
-
return request(ctx, `${
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
|
394
|
-
|
|
573
|
+
async function deleteUserSafe(ctx, userId) {
|
|
574
|
+
await request(ctx, `${ADMIN}/users/${encodeURIComponent(userId)}`, { method: "DELETE" });
|
|
575
|
+
return { ok: true };
|
|
395
576
|
}
|
|
396
|
-
function
|
|
397
|
-
|
|
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
|
-
|
|
400
|
-
|
|
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
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
-
|
|
533
|
-
|
|
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
|
-
|
|
542
|
-
|
|
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
|
-
|
|
549
|
-
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
|
562
|
-
return request(ctx, `${
|
|
563
|
-
method: "
|
|
628
|
+
function updateDepartmentSafe(ctx, deptId, input) {
|
|
629
|
+
return request(ctx, `${ADMIN}/departments/${encodeURIComponent(deptId)}`, {
|
|
630
|
+
method: "PUT",
|
|
564
631
|
body: {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
|
581
|
-
return request(ctx, `${
|
|
582
|
-
method: "
|
|
583
|
-
body: {
|
|
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
|
-
|
|
588
|
-
|
|
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
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
orgTree:
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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
|
|
2615
|
-
|
|
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
|
|
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 {
|
|
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
|
|
3047
|
-
|
|
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
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
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
|
|
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
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
if (opts.source)
|
|
3641
|
-
if (opts.extendInfo)
|
|
3642
|
-
const res = await
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
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
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
const res = await
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
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
|
|
3668
|
-
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
3824
|
-
|
|
3825
|
-
|
|
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
|
|
3835
|
-
|
|
3836
|
-
const res = await
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
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
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
const res = await
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
5232
|
+
function switchUser(baseUrl, userOrName) {
|
|
4965
5233
|
const url = normalize(baseUrl);
|
|
4966
|
-
|
|
4967
|
-
|
|
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
|
-
|
|
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-
|
|
5311
|
+
//# sourceMappingURL=chunk-SEKM54NB.js.map
|