@kweaver-ai/kweaver-sdk 0.5.0 → 0.5.2

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.
Files changed (66) hide show
  1. package/README.md +6 -1
  2. package/README.zh.md +5 -0
  3. package/dist/api/agent-chat.d.ts +1 -1
  4. package/dist/api/agent-chat.js +4 -4
  5. package/dist/api/agent-list.d.ts +35 -0
  6. package/dist/api/agent-list.js +86 -12
  7. package/dist/api/bkn-backend.d.ts +60 -0
  8. package/dist/api/bkn-backend.js +103 -10
  9. package/dist/api/conversations.d.ts +6 -3
  10. package/dist/api/conversations.js +26 -27
  11. package/dist/api/dataflow.js +1 -10
  12. package/dist/api/datasources.js +1 -10
  13. package/dist/api/dataviews.js +1 -10
  14. package/dist/api/headers.d.ts +9 -0
  15. package/dist/api/headers.js +25 -0
  16. package/dist/api/knowledge-networks.d.ts +41 -0
  17. package/dist/api/knowledge-networks.js +69 -22
  18. package/dist/api/ontology-query.d.ts +14 -1
  19. package/dist/api/ontology-query.js +63 -49
  20. package/dist/api/semantic-search.js +2 -12
  21. package/dist/api/skills.d.ts +141 -0
  22. package/dist/api/skills.js +216 -0
  23. package/dist/api/vega.d.ts +63 -0
  24. package/dist/api/vega.js +131 -10
  25. package/dist/auth/oauth.d.ts +5 -1
  26. package/dist/auth/oauth.js +293 -94
  27. package/dist/cli.js +29 -4
  28. package/dist/client.d.ts +3 -0
  29. package/dist/client.js +4 -0
  30. package/dist/commands/agent.d.ts +33 -1
  31. package/dist/commands/agent.js +721 -49
  32. package/dist/commands/auth.js +211 -21
  33. package/dist/commands/bkn-ops.d.ts +77 -0
  34. package/dist/commands/bkn-ops.js +1056 -0
  35. package/dist/commands/bkn-query.d.ts +14 -0
  36. package/dist/commands/bkn-query.js +370 -0
  37. package/dist/commands/bkn-schema.d.ts +135 -0
  38. package/dist/commands/bkn-schema.js +1461 -0
  39. package/dist/commands/bkn-utils.d.ts +36 -0
  40. package/dist/commands/bkn-utils.js +102 -0
  41. package/dist/commands/bkn.d.ts +7 -113
  42. package/dist/commands/bkn.js +175 -2429
  43. package/dist/commands/dataview.d.ts +7 -0
  44. package/dist/commands/dataview.js +38 -2
  45. package/dist/commands/ds.d.ts +1 -0
  46. package/dist/commands/ds.js +8 -1
  47. package/dist/commands/import-csv.d.ts +2 -0
  48. package/dist/commands/import-csv.js +3 -2
  49. package/dist/commands/skill.d.ts +26 -0
  50. package/dist/commands/skill.js +524 -0
  51. package/dist/commands/vega.js +371 -14
  52. package/dist/config/jwt.d.ts +6 -0
  53. package/dist/config/jwt.js +21 -0
  54. package/dist/config/store.d.ts +37 -5
  55. package/dist/config/store.js +363 -30
  56. package/dist/index.d.ts +6 -1
  57. package/dist/index.js +5 -1
  58. package/dist/resources/bkn.d.ts +4 -0
  59. package/dist/resources/bkn.js +4 -0
  60. package/dist/resources/conversations.d.ts +5 -2
  61. package/dist/resources/conversations.js +17 -3
  62. package/dist/resources/skills.d.ts +47 -0
  63. package/dist/resources/skills.js +47 -0
  64. package/dist/resources/vega.d.ts +11 -0
  65. package/dist/resources/vega.js +37 -1
  66. package/package.json +1 -1
@@ -1,10 +1,31 @@
1
- import { getCurrentPlatform, loadClientConfig, loadTokenConfig, saveClientConfig, saveTokenConfig, setCurrentPlatform, } from "../config/store.js";
1
+ import { deleteClientConfig, getCurrentPlatform, loadClientConfig, loadTokenConfig, loadUserTokenConfig, resolveUserId, saveClientConfig, saveTokenConfig, setCurrentPlatform, } from "../config/store.js";
2
2
  import { HttpError, NetworkRequestError } from "../utils/http.js";
3
3
  const TOKEN_TTL_SECONDS = 3600;
4
4
  /** Seconds before access token expiry to trigger refresh (matches Python ConfigAuth). */
5
5
  const REFRESH_THRESHOLD_SEC = 60;
6
6
  const DEFAULT_REDIRECT_PORT = 9010;
7
7
  const DEFAULT_SCOPE = "openid offline all";
8
+ /** Best-effort fetch of display name via EACP userinfo (ShareServer). */
9
+ async function fetchDisplayName(baseUrl, accessToken, tlsInsecure) {
10
+ try {
11
+ const res = await runWithTlsInsecure(tlsInsecure, () => fetch(`${baseUrl}/api/eacp/v1/user/get`, {
12
+ headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/json" },
13
+ }));
14
+ if (!res.ok)
15
+ return null;
16
+ const info = (await res.json());
17
+ if (typeof info.account === "string")
18
+ return info.account;
19
+ if (typeof info.name === "string")
20
+ return info.name;
21
+ if (typeof info.mail === "string")
22
+ return info.mail;
23
+ }
24
+ catch {
25
+ /* Non-critical — displayName will be absent. */
26
+ }
27
+ return null;
28
+ }
8
29
  /** POSIX shell single-quote escaping for copy-paste commands. */
9
30
  export function shellQuoteForShell(value) {
10
31
  return `'${value.replace(/'/g, `'\\''`)}'`;
@@ -118,11 +139,132 @@ async function generatePkce() {
118
139
  const challenge = createHash("sha256").update(verifier).digest("base64url");
119
140
  return { verifier, challenge };
120
141
  }
142
+ /**
143
+ * Pre-flight check: verify that a cached OAuth2 client is still recognised
144
+ * by the server. Fetches the authorization endpoint with `redirect: "manual"`
145
+ * and inspects the Location header. Returns false when Hydra redirects to an
146
+ * error page containing `invalid_client` or similar indicators.
147
+ */
148
+ async function isClientStillValid(baseUrl, clientId, redirectUri) {
149
+ try {
150
+ const params = new URLSearchParams({
151
+ client_id: clientId,
152
+ response_type: "code",
153
+ scope: "openid",
154
+ redirect_uri: redirectUri,
155
+ state: "preflight",
156
+ });
157
+ const resp = await fetch(`${baseUrl}/oauth2/auth?${params}`, {
158
+ redirect: "manual",
159
+ });
160
+ if (resp.status === 302) {
161
+ const location = resp.headers.get("location") ?? "";
162
+ if (location.includes("error=invalid_client") ||
163
+ location.includes("error=error")) {
164
+ return false;
165
+ }
166
+ return true;
167
+ }
168
+ // Non-redirect — Hydra might serve an error page directly
169
+ if (resp.status >= 400)
170
+ return false;
171
+ return true;
172
+ }
173
+ catch {
174
+ // Network error — cannot pre-validate; let the real flow proceed
175
+ return true;
176
+ }
177
+ }
178
+ /**
179
+ * Resolve a cached client or register a new one. When a cached client fails
180
+ * pre-flight validation (stale registration after server reset), the local
181
+ * client.json is deleted and a fresh registration is performed.
182
+ */
183
+ async function resolveOrRegisterClient(baseUrl, redirectUri, scope, options) {
184
+ if (options?.clientId) {
185
+ const client = {
186
+ baseUrl,
187
+ clientId: options.clientId,
188
+ clientSecret: options.clientSecret ?? "",
189
+ redirectUri,
190
+ logoutRedirectUri: redirectUri.replace("/callback", "/successful-logout"),
191
+ scope,
192
+ lang: "zh-cn",
193
+ product: "adp",
194
+ xForwardedPrefix: "",
195
+ };
196
+ saveClientConfig(baseUrl, client);
197
+ return client;
198
+ }
199
+ let client = loadClientConfig(baseUrl);
200
+ if (client?.clientId) {
201
+ const valid = await isClientStillValid(baseUrl, client.clientId, redirectUri);
202
+ if (valid)
203
+ return client;
204
+ process.stderr.write("Cached OAuth2 client is no longer valid on the server. Re-registering…\n");
205
+ deleteClientConfig(baseUrl);
206
+ client = null;
207
+ }
208
+ const registered = await registerOAuth2Client(baseUrl, redirectUri, scope);
209
+ saveClientConfig(baseUrl, registered);
210
+ return registered;
211
+ }
212
+ /**
213
+ * Parse a redirect URI to extract host, port, and pathname.
214
+ * Returns null if the URI is not a valid HTTP(S) URL.
215
+ */
216
+ function parseRedirectUri(uri) {
217
+ try {
218
+ const parsed = new URL(uri);
219
+ const host = parsed.hostname;
220
+ const port = parsed.port ? Number(parsed.port) : (parsed.protocol === "https:" ? 443 : 80);
221
+ const isLocalhost = host === "127.0.0.1" || host === "localhost" || host === "::1";
222
+ return { host, port, pathname: parsed.pathname, isLocalhost };
223
+ }
224
+ catch {
225
+ return null;
226
+ }
227
+ }
228
+ /**
229
+ * Manual code flow for non-localhost redirect URIs.
230
+ * Prints the auth URL, then reads the full callback URL from stdin
231
+ * to extract the authorization code.
232
+ */
233
+ async function waitForManualCode(authUrl, state) {
234
+ const { createInterface } = await import("node:readline");
235
+ process.stderr.write("\nSince the redirect URI is not localhost, you need to complete login manually.\n" +
236
+ "1. Open this URL in your browser:\n\n" +
237
+ ` ${authUrl}\n\n` +
238
+ "2. After login, the browser will redirect to your callback URL.\n" +
239
+ "3. Copy the full callback URL and paste it here:\n\n");
240
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
241
+ const callbackUrl = await new Promise((resolve) => {
242
+ rl.question("Callback URL> ", (answer) => {
243
+ rl.close();
244
+ resolve(answer.trim());
245
+ });
246
+ });
247
+ const parsed = new URL(callbackUrl);
248
+ const receivedState = parsed.searchParams.get("state");
249
+ if (receivedState !== state) {
250
+ throw new Error("OAuth2 state mismatch — possible CSRF attack.");
251
+ }
252
+ const error = parsed.searchParams.get("error");
253
+ if (error) {
254
+ const desc = parsed.searchParams.get("error_description") ?? "";
255
+ throw new Error(desc ? `Authorization failed: ${error} — ${desc}` : `Authorization failed: ${error}`);
256
+ }
257
+ const code = parsed.searchParams.get("code");
258
+ if (!code) {
259
+ throw new Error("No authorization code found in the callback URL.");
260
+ }
261
+ return code;
262
+ }
121
263
  /**
122
264
  * OAuth2 Authorization Code login flow.
123
265
  * 1. Register client (if not already registered), OR use a provided client ID
124
266
  * 2. Open browser to /oauth2/auth
125
- * 3. Receive authorization code via local HTTP callback
267
+ * 3. Receive authorization code via local HTTP callback (or manual paste for non-localhost)
126
268
  * 4. Exchange code for access_token + refresh_token
127
269
  * 5. Save token.json + client.json to ~/.kweaver/
128
270
  */
@@ -133,29 +275,14 @@ export async function oauth2Login(baseUrl, options) {
133
275
  const base = normalizeBaseUrl(baseUrl);
134
276
  const port = options?.port ?? DEFAULT_REDIRECT_PORT;
135
277
  const scope = options?.scope ?? DEFAULT_SCOPE;
136
- const redirectUri = `http://127.0.0.1:${port}/callback`;
278
+ // Determine redirect URI: explicit option > port-based default
279
+ const redirectUri = options?.redirectUri ?? `http://127.0.0.1:${port}/callback`;
280
+ const parsedRedirect = parseRedirectUri(redirectUri);
281
+ const isLocalRedirect = parsedRedirect?.isLocalhost ?? true;
282
+ const listenPort = parsedRedirect?.port ?? port;
283
+ const callbackPathname = parsedRedirect?.pathname ?? "/callback";
137
284
  // Step 1: Determine client — use provided client ID or fall back to dynamic registration
138
- let client = loadClientConfig(base);
139
- if (options?.clientId) {
140
- // Use the platform's existing client (e.g. the web app client).
141
- // Persist it so future logins reuse it without re-registering.
142
- client = {
143
- baseUrl: base,
144
- clientId: options.clientId,
145
- clientSecret: options.clientSecret ?? "",
146
- redirectUri,
147
- logoutRedirectUri: redirectUri.replace("/callback", "/successful-logout"),
148
- scope,
149
- lang: "zh-cn",
150
- product: "adp",
151
- xForwardedPrefix: "",
152
- };
153
- saveClientConfig(base, client);
154
- }
155
- else if (!client?.clientId) {
156
- client = await registerOAuth2Client(base, redirectUri, scope);
157
- saveClientConfig(base, client);
158
- }
285
+ let client = await resolveOrRegisterClient(base, redirectUri, scope, options);
159
286
  // Use PKCE when no client secret is available (public client / platform client).
160
287
  const usePkce = !client.clientSecret;
161
288
  const pkce = usePkce ? await generatePkce() : null;
@@ -177,71 +304,91 @@ export async function oauth2Login(baseUrl, options) {
177
304
  authParams.set("code_challenge_method", "S256");
178
305
  }
179
306
  const authUrl = `${base}/oauth2/auth?${authParams.toString()}`;
180
- // Step 4: Start local callback server; exchange code inside handler, then show credentials HTML
181
- const token = await new Promise((resolve, reject) => {
182
- let server;
183
- const timeoutId = setTimeout(() => {
184
- server?.close();
185
- reject(new Error("OAuth2 login timed out (120s). No authorization code received."));
186
- }, 120_000);
187
- server = createServer((req, res) => {
188
- void (async () => {
189
- try {
190
- const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
191
- if (url.pathname !== "/callback") {
192
- res.writeHead(404);
193
- res.end();
194
- return;
195
- }
196
- const receivedState = url.searchParams.get("state");
197
- const receivedCode = url.searchParams.get("code");
198
- if (receivedState !== state) {
199
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
200
- res.end(buildCallbackExchangeErrorHtml("OAuth2 state mismatch — possible CSRF attack."));
307
+ let token;
308
+ if (isLocalRedirect) {
309
+ // Step 4a: Local callback — start HTTP server to receive the authorization code
310
+ token = await new Promise((resolve, reject) => {
311
+ let server;
312
+ const timeoutId = setTimeout(() => {
313
+ server?.close();
314
+ reject(new Error("OAuth2 login timed out (120s). No authorization code received."));
315
+ }, 120_000);
316
+ server = createServer((req, res) => {
317
+ void (async () => {
318
+ try {
319
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${listenPort}`);
320
+ if (url.pathname !== callbackPathname) {
321
+ res.writeHead(404);
322
+ res.end();
323
+ return;
324
+ }
325
+ const receivedState = url.searchParams.get("state");
326
+ const code = url.searchParams.get("code");
327
+ const callbackError = url.searchParams.get("error");
328
+ const callbackErrorDesc = url.searchParams.get("error_description");
329
+ if (receivedState !== state) {
330
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
331
+ res.end(buildCallbackExchangeErrorHtml("OAuth2 state mismatch — possible CSRF attack."));
332
+ clearTimeout(timeoutId);
333
+ server.close();
334
+ reject(new Error("OAuth2 state mismatch — possible CSRF attack."));
335
+ return;
336
+ }
337
+ if (callbackError) {
338
+ const msg = callbackErrorDesc
339
+ ? `Authorization failed: ${callbackError} — ${callbackErrorDesc}`
340
+ : `Authorization failed: ${callbackError}`;
341
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
342
+ res.end(buildCallbackExchangeErrorHtml(msg));
343
+ clearTimeout(timeoutId);
344
+ server.close();
345
+ reject(new Error(msg));
346
+ return;
347
+ }
348
+ if (!code) {
349
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
350
+ res.end(buildCallbackExchangeErrorHtml("No authorization code received in callback."));
351
+ clearTimeout(timeoutId);
352
+ server.close();
353
+ reject(new Error("No authorization code received in callback."));
354
+ return;
355
+ }
356
+ const exchanged = await exchangeCodeForToken(base, code, client.clientId, client.clientSecret, redirectUri, pkce?.verifier, options?.tlsInsecure);
357
+ const copyCommand = buildCopyCommand(base, client.clientId, client.clientSecret, exchanged.refreshToken, options?.tlsInsecure);
358
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
359
+ res.end(buildCallbackHtml(copyCommand));
201
360
  clearTimeout(timeoutId);
202
361
  server.close();
203
- reject(new Error("OAuth2 state mismatch — possible CSRF attack."));
204
- return;
362
+ resolve(exchanged);
205
363
  }
206
- if (!receivedCode) {
207
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
208
- res.end(buildCallbackExchangeErrorHtml("No authorization code received in callback."));
364
+ catch (err) {
365
+ const message = err instanceof Error ? err.message : String(err);
366
+ try {
367
+ res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
368
+ res.end(buildCallbackExchangeErrorHtml(message));
369
+ }
370
+ catch {
371
+ /* response may already be sent */
372
+ }
209
373
  clearTimeout(timeoutId);
210
374
  server.close();
211
- reject(new Error("No authorization code received in callback."));
212
- return;
213
- }
214
- const exchanged = await exchangeCodeForToken(base, receivedCode, client.clientId, client.clientSecret, redirectUri, pkce?.verifier, options?.tlsInsecure);
215
- const copyCommand = buildCopyCommand(base, client.clientId, client.clientSecret, exchanged.refreshToken, options?.tlsInsecure);
216
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
217
- res.end(buildCallbackHtml(copyCommand));
218
- clearTimeout(timeoutId);
219
- server.close();
220
- resolve(exchanged);
221
- }
222
- catch (err) {
223
- const message = err instanceof Error ? err.message : String(err);
224
- try {
225
- res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
226
- res.end(buildCallbackExchangeErrorHtml(message));
227
- }
228
- catch {
229
- /* response may already be sent */
375
+ reject(err instanceof Error ? err : new Error(message));
230
376
  }
231
- clearTimeout(timeoutId);
232
- server.close();
233
- reject(err instanceof Error ? err : new Error(message));
234
- }
235
- })();
236
- });
237
- server.listen(port, "127.0.0.1", () => {
238
- // Step 5: Open browser (uses spawn with proper Windows quoting)
239
- import("../utils/browser.js").then(({ openBrowser }) => {
240
- openBrowser(authUrl);
377
+ })();
378
+ });
379
+ server.listen(listenPort, "127.0.0.1", () => {
380
+ import("../utils/browser.js").then(({ openBrowser }) => {
381
+ openBrowser(authUrl);
382
+ });
383
+ process.stderr.write(`If the wrong browser opens, copy this URL to your correct browser:\n ${authUrl}\n`);
241
384
  });
242
- process.stderr.write(`If the wrong browser opens, copy this URL to your correct browser:\n ${authUrl}\n`);
243
385
  });
244
- });
386
+ }
387
+ else {
388
+ // Step 4b: Non-localhost redirect — manual code entry flow
389
+ const code = await waitForManualCode(authUrl, state);
390
+ token = await exchangeCodeForToken(base, code, client.clientId, client.clientSecret, redirectUri, pkce?.verifier, options?.tlsInsecure);
391
+ }
245
392
  setCurrentPlatform(base);
246
393
  return token;
247
394
  });
@@ -329,6 +476,9 @@ async function exchangeCodeForToken(baseUrl, code, clientId, clientSecret, redir
329
476
  obtainedAt: now.toISOString(),
330
477
  ...(tlsInsecure ? { tlsInsecure: true } : {}),
331
478
  };
479
+ const displayName = await fetchDisplayName(baseUrl, data.access_token, tlsInsecure);
480
+ if (displayName)
481
+ token.displayName = displayName;
332
482
  saveTokenConfig(token);
333
483
  return token;
334
484
  }
@@ -359,14 +509,13 @@ export async function playwrightLogin(baseUrl, options) {
359
509
  const base = normalizeBaseUrl(baseUrl);
360
510
  const port = options?.port ?? DEFAULT_REDIRECT_PORT;
361
511
  const scope = options?.scope ?? DEFAULT_SCOPE;
362
- const redirectUri = `http://127.0.0.1:${port}/callback`;
512
+ const redirectUri = options?.redirectUri ?? `http://127.0.0.1:${port}/callback`;
513
+ const parsedRedirect = parseRedirectUri(redirectUri);
514
+ const listenPort = parsedRedirect?.port ?? port;
515
+ const callbackPathname = parsedRedirect?.pathname ?? "/callback";
363
516
  const hasCredentials = !!(options?.username && options?.password);
364
- // Step 1: Ensure registered OAuth2 client
365
- let client = loadClientConfig(base);
366
- if (!client?.clientId) {
367
- client = await registerOAuth2Client(base, redirectUri, scope);
368
- saveClientConfig(base, client);
369
- }
517
+ // Step 1: Ensure registered OAuth2 client (with stale-client auto-recovery)
518
+ let client = await resolveOrRegisterClient(base, redirectUri, scope);
370
519
  // Step 2: Generate CSRF state
371
520
  const state = randomBytes(12).toString("hex");
372
521
  // Step 3: Build authorization URL
@@ -394,14 +543,16 @@ export async function playwrightLogin(baseUrl, options) {
394
543
  server = createServer((req, res) => {
395
544
  void (async () => {
396
545
  try {
397
- const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
398
- if (url.pathname !== "/callback") {
546
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${listenPort}`);
547
+ if (url.pathname !== callbackPathname) {
399
548
  res.writeHead(404);
400
549
  res.end();
401
550
  return;
402
551
  }
403
552
  const receivedState = url.searchParams.get("state");
404
553
  const receivedCode = url.searchParams.get("code");
554
+ const callbackError = url.searchParams.get("error");
555
+ const callbackErrorDesc = url.searchParams.get("error_description");
405
556
  if (receivedState !== state) {
406
557
  res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
407
558
  res.end(buildCallbackExchangeErrorHtml("OAuth2 state mismatch — possible CSRF attack."));
@@ -411,6 +562,18 @@ export async function playwrightLogin(baseUrl, options) {
411
562
  reject(new Error("OAuth2 state mismatch — possible CSRF attack."));
412
563
  return;
413
564
  }
565
+ if (callbackError) {
566
+ const msg = callbackErrorDesc
567
+ ? `Authorization failed: ${callbackError} — ${callbackErrorDesc}`
568
+ : `Authorization failed: ${callbackError}`;
569
+ res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
570
+ res.end(buildCallbackExchangeErrorHtml(msg));
571
+ clearTimeout(timeoutId);
572
+ server.close();
573
+ browser?.close();
574
+ reject(new Error(msg));
575
+ return;
576
+ }
414
577
  if (!receivedCode) {
415
578
  res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
416
579
  res.end(buildCallbackExchangeErrorHtml("No authorization code received in callback."));
@@ -445,7 +608,7 @@ export async function playwrightLogin(baseUrl, options) {
445
608
  }
446
609
  })();
447
610
  });
448
- server.listen(port, "127.0.0.1", async () => {
611
+ server.listen(listenPort, "127.0.0.1", async () => {
449
612
  try {
450
613
  browser = await chromium.launch({ headless: hasCredentials });
451
614
  const context = await browser.newContext({ ignoreHTTPSErrors: !!options?.tlsInsecure });
@@ -577,6 +740,10 @@ export async function refreshAccessToken(token) {
577
740
  }
578
741
  const now = new Date();
579
742
  const expiresIn = typeof data.expires_in === "number" ? data.expires_in : 3600;
743
+ let displayName = token.displayName;
744
+ if (!displayName) {
745
+ displayName = (await fetchDisplayName(baseUrl, data.access_token, token.tlsInsecure)) ?? undefined;
746
+ }
580
747
  const newToken = {
581
748
  baseUrl,
582
749
  accessToken: data.access_token,
@@ -588,6 +755,7 @@ export async function refreshAccessToken(token) {
588
755
  idToken: data.id_token ?? token.idToken ?? "",
589
756
  obtainedAt: now.toISOString(),
590
757
  ...(token.tlsInsecure ? { tlsInsecure: true } : {}),
758
+ ...(displayName ? { displayName } : {}),
591
759
  };
592
760
  saveTokenConfig(newToken);
593
761
  return newToken;
@@ -618,7 +786,20 @@ export async function ensureValidToken(opts) {
618
786
  if (!currentPlatform) {
619
787
  throw new Error("No active platform selected. Run `kweaver auth login <platform-url>` first.");
620
788
  }
621
- let token = loadTokenConfig(currentPlatform);
789
+ // KWEAVER_USER: load a specific user's token without switching active user
790
+ const envUser = process.env.KWEAVER_USER;
791
+ let token;
792
+ if (envUser) {
793
+ const userId = resolveUserId(currentPlatform, envUser);
794
+ if (!userId) {
795
+ throw new Error(`User '${envUser}' not found for ${currentPlatform}. ` +
796
+ "Run `kweaver auth users` to see available users.");
797
+ }
798
+ token = loadUserTokenConfig(currentPlatform, userId);
799
+ }
800
+ else {
801
+ token = loadTokenConfig(currentPlatform);
802
+ }
622
803
  if (!token) {
623
804
  throw new Error(`No saved token for ${currentPlatform}. Run \`kweaver auth login ${currentPlatform}\` first.`);
624
805
  }
@@ -652,7 +833,15 @@ export async function with401RefreshRetry(fn) {
652
833
  throw error;
653
834
  }
654
835
  const platformUrl = normalizeBaseUrl(currentPlatform);
655
- const latest = loadTokenConfig(platformUrl);
836
+ const envUser = process.env.KWEAVER_USER;
837
+ let latest;
838
+ if (envUser) {
839
+ const userId = resolveUserId(platformUrl, envUser);
840
+ latest = userId ? loadUserTokenConfig(platformUrl, userId) : null;
841
+ }
842
+ else {
843
+ latest = loadTokenConfig(platformUrl);
844
+ }
656
845
  if (!latest) {
657
846
  throw error;
658
847
  }
@@ -681,7 +870,17 @@ export async function withTokenRetry(fn) {
681
870
  catch (error) {
682
871
  if (error instanceof HttpError && error.status === 401) {
683
872
  const platformUrl = normalizeBaseUrl(token.baseUrl);
684
- const latest = loadTokenConfig(platformUrl) ?? token;
873
+ const envUser = process.env.KWEAVER_USER;
874
+ let latest;
875
+ if (envUser) {
876
+ const userId = resolveUserId(platformUrl, envUser);
877
+ latest = userId ? loadUserTokenConfig(platformUrl, userId) : null;
878
+ }
879
+ else {
880
+ latest = loadTokenConfig(platformUrl);
881
+ }
882
+ if (!latest)
883
+ latest = token;
685
884
  try {
686
885
  const refreshed = await refreshAccessToken(latest);
687
886
  return await fn(refreshed);
package/dist/cli.js CHANGED
@@ -7,24 +7,29 @@ import { runConfigCommand } from "./commands/config.js";
7
7
  import { runContextLoaderCommand } from "./commands/context-loader.js";
8
8
  import { runDsCommand } from "./commands/ds.js";
9
9
  import { runDataviewCommand } from "./commands/dataview.js";
10
+ import { runSkillCommand } from "./commands/skill.js";
10
11
  import { runTokenCommand } from "./commands/token.js";
11
12
  import { runVegaCommand } from "./commands/vega.js";
12
13
  function printHelp() {
13
14
  console.log(`kweaver
14
15
 
15
16
  Usage:
17
+ kweaver [--user <userId|username>] <command> [options]
16
18
  kweaver --version | -V
17
19
  kweaver --help | -h
18
20
 
19
21
  kweaver auth <platform-url> [--alias name] [-u user] [-p pass] [--playwright] [--insecure|-k]
20
22
  kweaver auth login <platform-url> (alias for auth <url>)
21
23
  kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (run on host without browser)
24
+ kweaver auth whoami [platform-url|alias] [--json]
22
25
  kweaver auth export [platform-url|alias] [--json]
23
26
  kweaver auth status [platform-url|alias]
24
27
  kweaver auth list
25
28
  kweaver auth use <platform-url|alias>
26
- kweaver auth logout [platform-url|alias]
27
- kweaver auth delete <platform-url|alias>
29
+ kweaver auth users [platform-url|alias]
30
+ kweaver auth switch [platform-url|alias] --user <userId|username>
31
+ kweaver auth logout [platform-url|alias] [--user <userId>]
32
+ kweaver auth delete <platform-url|alias> [--user <userId>]
28
33
  kweaver token
29
34
 
30
35
  kweaver call <url> [-X METHOD] [-H "Name: value"] [-d BODY] [--data-raw BODY]
@@ -53,7 +58,7 @@ Usage:
53
58
  kweaver dataview list [--datasource-id id] [--type atomic|custom] [--limit n] [-bd value] [--pretty]
54
59
  kweaver dataview find --name <name> [--exact] [--datasource-id id] [--wait] [--timeout ms] [-bd value] [--pretty]
55
60
  kweaver dataview get <id> [-bd value] [--pretty]
56
- kweaver dataview query <id> [--sql sql] [--limit n] [--offset n] [--need-total] [-bd value] [--pretty]
61
+ kweaver dataview query <id> [--sql sql] [--limit n] [--offset n] [--need-total] [--raw-sql] [-bd value] [--pretty]
57
62
  kweaver dataview delete <id> [-y] [-bd value]
58
63
 
59
64
  kweaver bkn list [options]
@@ -80,6 +85,12 @@ Usage:
80
85
  kweaver config list-bd
81
86
  kweaver config show
82
87
 
88
+ kweaver skill list|get|register|status|delete [options]
89
+ kweaver skill market [options]
90
+ kweaver skill content <skill-id> [--raw] [--output file]
91
+ kweaver skill read-file <skill-id> <rel-path> [--raw] [--output file]
92
+ kweaver skill download|install <skill-id> [path] [options]
93
+
83
94
  kweaver vega health|stats|inspect
84
95
  kweaver vega catalog list|get|health|test-connection|discover|resources [options]
85
96
  kweaver vega resource list|get|query [options]
@@ -94,6 +105,9 @@ Usage:
94
105
  kweaver context-loader query-object-instance|query-instance-subgraph|get-logic-properties|get-action-info ...
95
106
  (alias: kweaver context ...)
96
107
 
108
+ Global options:
109
+ --user <id|name> Use a specific user's credentials for this command (env: KWEAVER_USER)
110
+
97
111
  Commands:
98
112
  auth Login, list, inspect, and switch saved platform auth profiles
99
113
  token Print the current access token, refreshing it first if needed
@@ -104,13 +118,21 @@ Commands:
104
118
  bkn Knowledge network (CRUD, build, validate, export, stats, push/pull,
105
119
  object-type, relation-type, subgraph, action-type, action-execution, action-log)
106
120
  config Per-platform configuration (business domain)
121
+ skill Skill registry and market (register, search, progressive read, download/install)
107
122
  vega Vega observability (catalog, resource, connector-type, health/stats/inspect)
108
123
  context-loader Context-loader MCP (config, tools, resources, prompts, kn-search, query-*, etc.)
109
124
  help Show this message`);
110
125
  }
111
126
  export async function run(argv) {
112
127
  applyTlsEnvFromSavedTokens();
113
- const [command, ...rest] = argv;
128
+ // Global --user flag: override active user for this invocation
129
+ const userIdx = argv.indexOf("--user");
130
+ let filteredArgv = argv;
131
+ if (userIdx !== -1 && userIdx + 1 < argv.length) {
132
+ process.env.KWEAVER_USER = argv[userIdx + 1];
133
+ filteredArgv = [...argv.slice(0, userIdx), ...argv.slice(userIdx + 2)];
134
+ }
135
+ const [command, ...rest] = filteredArgv;
114
136
  if (command === "--version" || command === "-V" || command === "version") {
115
137
  const { createRequire } = await import("node:module");
116
138
  const require = createRequire(import.meta.url);
@@ -149,6 +171,9 @@ export async function run(argv) {
149
171
  if (command === "config") {
150
172
  return runConfigCommand(rest);
151
173
  }
174
+ if (command === "skill") {
175
+ return runSkillCommand(rest);
176
+ }
152
177
  if (command === "context-loader" || command === "context") {
153
178
  return runContextLoaderCommand(rest);
154
179
  }
package/dist/client.d.ts CHANGED
@@ -6,6 +6,7 @@ import { DataSourcesResource } from "./resources/datasources.js";
6
6
  import { DataViewsResource } from "./resources/dataviews.js";
7
7
  import { KnowledgeNetworksResource } from "./resources/knowledge-networks.js";
8
8
  import { BknResource } from "./resources/bkn.js";
9
+ import { SkillsResource } from "./resources/skills.js";
9
10
  import { VegaResource } from "./resources/vega.js";
10
11
  /**
11
12
  * Shared credentials passed to every resource method.
@@ -90,6 +91,8 @@ export declare class KWeaverClient implements ClientContext {
90
91
  readonly dataviews: DataViewsResource;
91
92
  /** Vega observability platform (catalogs, resources, connector types). */
92
93
  readonly vega: VegaResource;
94
+ /** ADP/KWeaver skill registry, market, progressive read, and install helpers. */
95
+ readonly skills: SkillsResource;
93
96
  constructor(opts?: KWeaverClientOptions);
94
97
  /**
95
98
  * Async factory that auto-refreshes expired or revoked tokens.
package/dist/client.js CHANGED
@@ -9,6 +9,7 @@ import { DataSourcesResource } from "./resources/datasources.js";
9
9
  import { DataViewsResource } from "./resources/dataviews.js";
10
10
  import { KnowledgeNetworksResource } from "./resources/knowledge-networks.js";
11
11
  import { BknResource } from "./resources/bkn.js";
12
+ import { SkillsResource } from "./resources/skills.js";
12
13
  import { VegaResource } from "./resources/vega.js";
13
14
  // ── KWeaverClient ─────────────────────────────────────────────────────────────
14
15
  /**
@@ -59,6 +60,8 @@ export class KWeaverClient {
59
60
  dataviews;
60
61
  /** Vega observability platform (catalogs, resources, connector types). */
61
62
  vega;
63
+ /** ADP/KWeaver skill registry, market, progressive read, and install helpers. */
64
+ skills;
62
65
  constructor(opts = {}) {
63
66
  const envDomain = process.env.KWEAVER_BUSINESS_DOMAIN;
64
67
  let baseUrl;
@@ -114,6 +117,7 @@ export class KWeaverClient {
114
117
  this.datasources = new DataSourcesResource(this);
115
118
  this.dataviews = new DataViewsResource(this);
116
119
  this.vega = new VegaResource(this);
120
+ this.skills = new SkillsResource(this);
117
121
  }
118
122
  /**
119
123
  * Async factory that auto-refreshes expired or revoked tokens.