@kweaver-ai/kweaver-sdk 0.5.1 → 0.6.0

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 (95) hide show
  1. package/README.md +25 -2
  2. package/README.zh.md +24 -1
  3. package/dist/api/agent-chat.d.ts +8 -2
  4. package/dist/api/agent-chat.js +150 -44
  5. package/dist/api/agent-list.d.ts +35 -0
  6. package/dist/api/agent-list.js +95 -21
  7. package/dist/api/bkn-backend.d.ts +60 -0
  8. package/dist/api/bkn-backend.js +103 -10
  9. package/dist/api/business-domains.js +9 -5
  10. package/dist/api/context-loader.js +4 -1
  11. package/dist/api/conversations.d.ts +6 -3
  12. package/dist/api/conversations.js +29 -35
  13. package/dist/api/dataflow.js +1 -10
  14. package/dist/api/dataflow2.d.ts +95 -0
  15. package/dist/api/dataflow2.js +80 -0
  16. package/dist/api/datasources.js +1 -10
  17. package/dist/api/dataviews.js +1 -10
  18. package/dist/api/headers.d.ts +11 -0
  19. package/dist/api/headers.js +30 -0
  20. package/dist/api/knowledge-networks.d.ts +41 -0
  21. package/dist/api/knowledge-networks.js +69 -22
  22. package/dist/api/ontology-query.d.ts +14 -1
  23. package/dist/api/ontology-query.js +63 -49
  24. package/dist/api/semantic-search.js +2 -12
  25. package/dist/api/skills.d.ts +141 -0
  26. package/dist/api/skills.js +208 -0
  27. package/dist/api/vega.d.ts +54 -7
  28. package/dist/api/vega.js +112 -25
  29. package/dist/auth/oauth.d.ts +5 -1
  30. package/dist/auth/oauth.js +351 -95
  31. package/dist/cli.js +49 -5
  32. package/dist/client.d.ts +12 -0
  33. package/dist/client.js +52 -8
  34. package/dist/commands/agent.d.ts +33 -1
  35. package/dist/commands/agent.js +721 -49
  36. package/dist/commands/auth.js +226 -55
  37. package/dist/commands/bkn-ops.d.ts +77 -0
  38. package/dist/commands/bkn-ops.js +1056 -0
  39. package/dist/commands/bkn-query.d.ts +14 -0
  40. package/dist/commands/bkn-query.js +370 -0
  41. package/dist/commands/bkn-schema.d.ts +135 -0
  42. package/dist/commands/bkn-schema.js +1483 -0
  43. package/dist/commands/bkn-utils.d.ts +36 -0
  44. package/dist/commands/bkn-utils.js +102 -0
  45. package/dist/commands/bkn.d.ts +7 -113
  46. package/dist/commands/bkn.js +175 -2429
  47. package/dist/commands/call.js +8 -5
  48. package/dist/commands/dataflow.d.ts +1 -0
  49. package/dist/commands/dataflow.js +251 -0
  50. package/dist/commands/dataview.d.ts +7 -0
  51. package/dist/commands/dataview.js +38 -2
  52. package/dist/commands/ds.d.ts +1 -0
  53. package/dist/commands/ds.js +8 -1
  54. package/dist/commands/explore-bkn.d.ts +79 -0
  55. package/dist/commands/explore-bkn.js +273 -0
  56. package/dist/commands/explore-chat.d.ts +3 -0
  57. package/dist/commands/explore-chat.js +193 -0
  58. package/dist/commands/explore-vega.d.ts +3 -0
  59. package/dist/commands/explore-vega.js +71 -0
  60. package/dist/commands/explore.d.ts +9 -0
  61. package/dist/commands/explore.js +258 -0
  62. package/dist/commands/import-csv.d.ts +2 -0
  63. package/dist/commands/import-csv.js +3 -2
  64. package/dist/commands/skill.d.ts +26 -0
  65. package/dist/commands/skill.js +524 -0
  66. package/dist/commands/vega.js +372 -117
  67. package/dist/config/jwt.d.ts +6 -0
  68. package/dist/config/jwt.js +21 -0
  69. package/dist/config/no-auth.d.ts +3 -0
  70. package/dist/config/no-auth.js +5 -0
  71. package/dist/config/store.d.ts +45 -5
  72. package/dist/config/store.js +385 -30
  73. package/dist/index.d.ts +6 -1
  74. package/dist/index.js +5 -1
  75. package/dist/kweaver.d.ts +5 -0
  76. package/dist/kweaver.js +32 -2
  77. package/dist/resources/bkn.d.ts +4 -0
  78. package/dist/resources/bkn.js +6 -3
  79. package/dist/resources/conversations.d.ts +5 -2
  80. package/dist/resources/conversations.js +17 -3
  81. package/dist/resources/knowledge-networks.js +3 -8
  82. package/dist/resources/skills.d.ts +47 -0
  83. package/dist/resources/skills.js +47 -0
  84. package/dist/resources/vega.d.ts +11 -6
  85. package/dist/resources/vega.js +37 -10
  86. package/dist/templates/explorer/app.js +136 -0
  87. package/dist/templates/explorer/bkn.js +747 -0
  88. package/dist/templates/explorer/chat.js +980 -0
  89. package/dist/templates/explorer/dashboard.js +82 -0
  90. package/dist/templates/explorer/index.html +35 -0
  91. package/dist/templates/explorer/style.css +2440 -0
  92. package/dist/templates/explorer/vega.js +291 -0
  93. package/dist/utils/http.d.ts +3 -0
  94. package/dist/utils/http.js +37 -1
  95. package/package.json +9 -5
@@ -1,3 +1,11 @@
1
+ export { NO_AUTH_TOKEN, isNoAuth } from "./no-auth.js";
2
+ /**
3
+ * Persist a no-auth session for a platform (users/default/token.json) and set it as current.
4
+ * Used by `kweaver auth <url> --no-auth` and when OAuth registration returns 404.
5
+ */
6
+ export declare function saveNoAuthPlatform(baseUrl: string, opts?: {
7
+ tlsInsecure?: boolean;
8
+ }): TokenConfig;
1
9
  export interface TokenConfig {
2
10
  baseUrl: string;
3
11
  accessToken: string;
@@ -10,6 +18,8 @@ export interface TokenConfig {
10
18
  obtainedAt: string;
11
19
  /** When true, skip TLS certificate verification for this platform (saved by `kweaver auth --insecure`). */
12
20
  tlsInsecure?: boolean;
21
+ /** Human-readable display name fetched from userinfo at login time. */
22
+ displayName?: string;
13
23
  }
14
24
  /** OAuth2 client registration (per platform), used for refresh_token grant. */
15
25
  export interface ClientConfig {
@@ -38,7 +48,37 @@ export interface PlatformSummary {
38
48
  hasToken: boolean;
39
49
  isCurrent: boolean;
40
50
  alias?: string;
51
+ /** Active user ID for this platform (from id_token sub claim). */
52
+ userId?: string;
53
+ /** Human-readable name persisted from /oauth2/userinfo at login time. */
54
+ displayName?: string;
55
+ }
56
+ /** Extract userId from a TokenConfig (try idToken, then accessToken, fallback "default"). */
57
+ export declare function extractUserId(token: TokenConfig): string;
58
+ /** Get the active userId for a platform. */
59
+ export declare function getActiveUser(baseUrl: string): string | null;
60
+ /** Set the active userId for a platform. */
61
+ export declare function setActiveUser(baseUrl: string, userId: string): void;
62
+ /** List all user IDs stored under a platform. */
63
+ export declare function listUsers(baseUrl: string): string[];
64
+ /** Load a specific user's token (not necessarily the active user). */
65
+ export declare function loadUserTokenConfig(baseUrl: string, userId: string): TokenConfig | null;
66
+ export interface UserProfile {
67
+ userId: string;
68
+ username?: string;
69
+ email?: string;
41
70
  }
71
+ /**
72
+ * List all user profiles for a platform, enriched with display names.
73
+ *
74
+ * Resolution order for username:
75
+ * 1. ``displayName`` field persisted in token.json (set at login via /oauth2/userinfo)
76
+ * 2. ``preferred_username`` or ``name`` decoded from id_token JWT
77
+ */
78
+ export declare function listUserProfiles(baseUrl: string): UserProfile[];
79
+ /** Resolve a user identifier (userId, username, or email) to a userId for the given platform.
80
+ * userId and username are matched case-sensitively; email is case-insensitive. */
81
+ export declare function resolveUserId(baseUrl: string, identifier: string): string | null;
42
82
  export declare function getConfigDir(): string;
43
83
  export declare function getCurrentPlatform(): string | null;
44
84
  export declare function setCurrentPlatform(baseUrl: string): void;
@@ -47,9 +87,10 @@ export declare function deletePlatformAlias(baseUrl: string): void;
47
87
  export declare function getPlatformAlias(baseUrl: string): string | null;
48
88
  export declare function resolvePlatformIdentifier(value: string): string | null;
49
89
  export declare function loadTokenConfig(baseUrl?: string): TokenConfig | null;
50
- export declare function saveTokenConfig(config: TokenConfig): void;
90
+ export declare function saveTokenConfig(config: TokenConfig, userId?: string): void;
51
91
  export declare function loadClientConfig(baseUrl?: string): ClientConfig | null;
52
92
  export declare function saveClientConfig(baseUrl: string, config: ClientConfig): void;
93
+ export declare function deleteClientConfig(baseUrl: string): void;
53
94
  export declare function loadContextLoaderConfig(baseUrl?: string): ContextLoaderConfig | null;
54
95
  export declare function saveContextLoaderConfig(baseUrl: string, config: ContextLoaderConfig): void;
55
96
  export interface CurrentContextLoaderKn {
@@ -61,10 +102,9 @@ export declare function addContextLoaderEntry(baseUrl: string, name: string, knI
61
102
  export declare function setCurrentContextLoader(baseUrl: string, name: string): void;
62
103
  export declare function removeContextLoaderEntry(baseUrl: string, name: string): void;
63
104
  export declare function hasPlatform(baseUrl: string): boolean;
64
- /**
65
- * Remove token for a platform so the next auth will do a full login.
66
- */
67
- export declare function clearPlatformSession(baseUrl: string): void;
105
+ export declare function clearPlatformSession(baseUrl: string, userId?: string): void;
106
+ /** Delete a single user's profile directory under a platform. */
107
+ export declare function deleteUser(baseUrl: string, userId: string): void;
68
108
  export declare function deletePlatform(baseUrl: string): void;
69
109
  export declare function listPlatforms(): PlatformSummary[];
70
110
  /** Per-platform config (not auth — general settings). */
@@ -1,12 +1,34 @@
1
- import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { listBusinessDomains } from "../api/business-domains.js";
5
+ import { NO_AUTH_TOKEN } from "./no-auth.js";
6
+ import { decodeJwtPayload, extractUserIdFromJwt } from "./jwt.js";
7
+ export { NO_AUTH_TOKEN, isNoAuth } from "./no-auth.js";
8
+ /**
9
+ * Persist a no-auth session for a platform (users/default/token.json) and set it as current.
10
+ * Used by `kweaver auth <url> --no-auth` and when OAuth registration returns 404.
11
+ */
12
+ export function saveNoAuthPlatform(baseUrl, opts) {
13
+ const base = baseUrl.replace(/\/+$/, "");
14
+ const token = {
15
+ baseUrl: base,
16
+ accessToken: NO_AUTH_TOKEN,
17
+ tokenType: "none",
18
+ scope: "",
19
+ obtainedAt: new Date().toISOString(),
20
+ };
21
+ if (opts?.tlsInsecure) {
22
+ token.tlsInsecure = true;
23
+ }
24
+ saveTokenConfig(token);
25
+ setCurrentPlatform(base);
26
+ return token;
27
+ }
5
28
  const MCP_PATH = "/api/agent-retrieval/v1/mcp";
6
29
  function buildMcpUrl(baseUrl) {
7
30
  return baseUrl.replace(/\/+$/, "") + MCP_PATH;
8
31
  }
9
- const CONFIG_DIR = process.env.KWEAVERC_CONFIG_DIR || join(homedir(), ".kweaver");
10
32
  function getConfigDirPath() {
11
33
  return process.env.KWEAVERC_CONFIG_DIR || join(homedir(), ".kweaver");
12
34
  }
@@ -43,6 +65,8 @@ function readJsonFile(filePath) {
43
65
  }
44
66
  function writeJsonFile(filePath, value) {
45
67
  ensureConfigDir();
68
+ const dir = join(filePath, "..");
69
+ ensureDir(dir);
46
70
  writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, IS_WIN32 ? {} : { mode: 0o600 });
47
71
  if (!IS_WIN32)
48
72
  chmodSync(filePath, 0o600);
@@ -63,6 +87,56 @@ function getPlatformFile(baseUrl, filename) {
63
87
  function ensurePlatformDir(baseUrl) {
64
88
  ensureDir(getPlatformDir(baseUrl));
65
89
  }
90
+ // ---------------------------------------------------------------------------
91
+ // User-scoped file routing
92
+ // ---------------------------------------------------------------------------
93
+ /** Files that live under users/<userId>/ instead of at platform root. */
94
+ const USER_SCOPED_FILES = new Set(["token.json", "config.json", "context-loader.json"]);
95
+ function getUserDir(baseUrl, userId) {
96
+ return join(getPlatformDir(baseUrl), "users", userId);
97
+ }
98
+ function getUsersDirPath(baseUrl) {
99
+ return join(getPlatformDir(baseUrl), "users");
100
+ }
101
+ /**
102
+ * Resolve a per-platform file path, routing user-scoped files through the
103
+ * active user's subdirectory with auto-migration fallback.
104
+ */
105
+ function resolveFile(baseUrl, filename) {
106
+ if (!USER_SCOPED_FILES.has(filename)) {
107
+ return getPlatformFile(baseUrl, filename);
108
+ }
109
+ const userId = getActiveUserRaw(baseUrl);
110
+ if (userId) {
111
+ const userPath = join(getUserDir(baseUrl, userId), filename);
112
+ if (existsSync(userPath))
113
+ return userPath;
114
+ }
115
+ // Fallback: check platform root for legacy / partially-migrated files
116
+ const legacyPath = getPlatformFile(baseUrl, filename);
117
+ if (existsSync(legacyPath)) {
118
+ // If legacy token.json exists, trigger full on-demand migration
119
+ const legacyToken = getPlatformFile(baseUrl, "token.json");
120
+ if (existsSync(legacyToken)) {
121
+ migratePlatformToUserScoped(baseUrl);
122
+ const migratedUser = getActiveUserRaw(baseUrl);
123
+ if (migratedUser) {
124
+ const migratedPath = join(getUserDir(baseUrl, migratedUser), filename);
125
+ if (existsSync(migratedPath))
126
+ return migratedPath;
127
+ }
128
+ }
129
+ // File exists at legacy root but no migration possible (e.g. token already migrated).
130
+ // Return legacy path so the caller can still read it.
131
+ return legacyPath;
132
+ }
133
+ if (userId)
134
+ return join(getUserDir(baseUrl, userId), filename);
135
+ return legacyPath;
136
+ }
137
+ // ---------------------------------------------------------------------------
138
+ // State helpers
139
+ // ---------------------------------------------------------------------------
66
140
  function readState() {
67
141
  return readJsonFile(getStateFilePath()) ?? {};
68
142
  }
@@ -72,6 +146,9 @@ function writeState(state) {
72
146
  function normalizeAlias(value) {
73
147
  return value.trim().toLowerCase();
74
148
  }
149
+ // ---------------------------------------------------------------------------
150
+ // Migration: legacy flat files → platforms/<encoded>/
151
+ // ---------------------------------------------------------------------------
75
152
  function migrateLegacyFilesIfNeeded() {
76
153
  const legacyClientFile = getLegacyClientFilePath();
77
154
  const legacyTokenFile = getLegacyTokenFilePath();
@@ -105,10 +182,182 @@ function migrateLegacyFilesIfNeeded() {
105
182
  writeState({ ...state, currentPlatform: baseUrl });
106
183
  }
107
184
  }
185
+ // ---------------------------------------------------------------------------
186
+ // Migration: flat platform dir → users/<userId>/ scoped layout
187
+ // ---------------------------------------------------------------------------
188
+ /** Extract userId from a TokenConfig (try idToken, then accessToken, fallback "default"). */
189
+ export function extractUserId(token) {
190
+ if (token.idToken) {
191
+ const sub = extractUserIdFromJwt(token.idToken);
192
+ if (sub)
193
+ return sub;
194
+ }
195
+ if (token.accessToken) {
196
+ const sub = extractUserIdFromJwt(token.accessToken);
197
+ if (sub)
198
+ return sub;
199
+ }
200
+ return "default";
201
+ }
202
+ function migratePlatformToUserScoped(baseUrl) {
203
+ const platformDir = getPlatformDir(baseUrl);
204
+ const usersDir = getUsersDirPath(baseUrl);
205
+ const rootTokenFile = join(platformDir, "token.json");
206
+ if (!existsSync(rootTokenFile) || existsSync(usersDir)) {
207
+ return;
208
+ }
209
+ const token = readJsonFile(rootTokenFile);
210
+ if (!token)
211
+ return;
212
+ const userId = extractUserId(token);
213
+ const userDir = getUserDir(baseUrl, userId);
214
+ ensureDir(userDir);
215
+ renameSync(rootTokenFile, join(userDir, "token.json"));
216
+ const rootConfigFile = join(platformDir, "config.json");
217
+ if (existsSync(rootConfigFile)) {
218
+ renameSync(rootConfigFile, join(userDir, "config.json"));
219
+ }
220
+ const rootContextLoaderFile = join(platformDir, "context-loader.json");
221
+ if (existsSync(rootContextLoaderFile)) {
222
+ renameSync(rootContextLoaderFile, join(userDir, "context-loader.json"));
223
+ }
224
+ const resolvedUrl = token.baseUrl || baseUrl;
225
+ const state = readState();
226
+ const activeUsers = { ...(state.activeUsers ?? {}) };
227
+ activeUsers[resolvedUrl] = userId;
228
+ writeState({ ...state, activeUsers });
229
+ }
230
+ function migrateAllPlatformsToUserScoped() {
231
+ const platformsDir = getPlatformsDirPath();
232
+ if (!existsSync(platformsDir))
233
+ return;
234
+ for (const entry of readdirSync(platformsDir)) {
235
+ const dirPath = join(platformsDir, entry);
236
+ if (!statSync(dirPath).isDirectory())
237
+ continue;
238
+ const rootToken = join(dirPath, "token.json");
239
+ const usersDir = join(dirPath, "users");
240
+ if (!existsSync(rootToken) || existsSync(usersDir))
241
+ continue;
242
+ const token = readJsonFile(rootToken);
243
+ if (!token?.baseUrl)
244
+ continue;
245
+ migratePlatformToUserScoped(token.baseUrl);
246
+ }
247
+ }
248
+ // ---------------------------------------------------------------------------
249
+ // Store initialization
250
+ // ---------------------------------------------------------------------------
108
251
  function ensureStoreReady() {
109
252
  ensureConfigDir();
110
253
  migrateLegacyFilesIfNeeded();
254
+ migrateAllPlatformsToUserScoped();
255
+ }
256
+ // ---------------------------------------------------------------------------
257
+ // Active user management
258
+ // ---------------------------------------------------------------------------
259
+ /** Read active user from state without triggering ensureStoreReady (avoids recursion). */
260
+ function getActiveUserRaw(baseUrl) {
261
+ const state = readJsonFile(getStateFilePath()) ?? {};
262
+ const userId = state.activeUsers?.[baseUrl];
263
+ if (userId)
264
+ return userId;
265
+ // Fallback: scan users/ dir and pick the first one
266
+ const usersDir = getUsersDirPath(baseUrl);
267
+ if (!existsSync(usersDir))
268
+ return null;
269
+ for (const entry of readdirSync(usersDir)) {
270
+ const entryPath = join(usersDir, entry);
271
+ if (statSync(entryPath).isDirectory() && existsSync(join(entryPath, "token.json"))) {
272
+ return entry;
273
+ }
274
+ }
275
+ return null;
276
+ }
277
+ /** Get the active userId for a platform. */
278
+ export function getActiveUser(baseUrl) {
279
+ ensureStoreReady();
280
+ return getActiveUserRaw(baseUrl);
281
+ }
282
+ /** Set the active userId for a platform. */
283
+ export function setActiveUser(baseUrl, userId) {
284
+ ensureStoreReady();
285
+ const state = readState();
286
+ const activeUsers = { ...(state.activeUsers ?? {}) };
287
+ activeUsers[baseUrl] = userId;
288
+ writeState({ ...state, activeUsers });
289
+ }
290
+ /** List all user IDs stored under a platform. */
291
+ export function listUsers(baseUrl) {
292
+ ensureStoreReady();
293
+ const usersDir = getUsersDirPath(baseUrl);
294
+ if (!existsSync(usersDir))
295
+ return [];
296
+ const users = [];
297
+ for (const entry of readdirSync(usersDir)) {
298
+ const entryPath = join(usersDir, entry);
299
+ if (statSync(entryPath).isDirectory()) {
300
+ users.push(entry);
301
+ }
302
+ }
303
+ return users.sort();
304
+ }
305
+ /** Load a specific user's token (not necessarily the active user). */
306
+ export function loadUserTokenConfig(baseUrl, userId) {
307
+ ensureStoreReady();
308
+ return readJsonFile(join(getUserDir(baseUrl, userId), "token.json"));
309
+ }
310
+ /**
311
+ * List all user profiles for a platform, enriched with display names.
312
+ *
313
+ * Resolution order for username:
314
+ * 1. ``displayName`` field persisted in token.json (set at login via /oauth2/userinfo)
315
+ * 2. ``preferred_username`` or ``name`` decoded from id_token JWT
316
+ */
317
+ export function listUserProfiles(baseUrl) {
318
+ const userIds = listUsers(baseUrl);
319
+ return userIds.map((userId) => {
320
+ const token = loadUserTokenConfig(baseUrl, userId);
321
+ let username;
322
+ let email;
323
+ if (token?.displayName) {
324
+ username = token.displayName;
325
+ }
326
+ if (token?.idToken) {
327
+ const payload = decodeJwtPayload(token.idToken);
328
+ if (payload) {
329
+ if (!username) {
330
+ if (typeof payload.preferred_username === "string")
331
+ username = payload.preferred_username;
332
+ else if (typeof payload.name === "string")
333
+ username = payload.name;
334
+ }
335
+ if (typeof payload.email === "string")
336
+ email = payload.email;
337
+ }
338
+ }
339
+ return { userId, username, email };
340
+ });
111
341
  }
342
+ /** Resolve a user identifier (userId, username, or email) to a userId for the given platform.
343
+ * userId and username are matched case-sensitively; email is case-insensitive. */
344
+ export function resolveUserId(baseUrl, identifier) {
345
+ const users = listUsers(baseUrl);
346
+ if (users.includes(identifier))
347
+ return identifier;
348
+ const profiles = listUserProfiles(baseUrl);
349
+ // Exact match on username (case-sensitive)
350
+ const exact = profiles.find((p) => p.username === identifier);
351
+ if (exact)
352
+ return exact.userId;
353
+ // Email match (case-insensitive per RFC 5321)
354
+ const lower = identifier.toLowerCase();
355
+ const byEmail = profiles.find((p) => p.email?.toLowerCase() === lower);
356
+ return byEmail?.userId ?? null;
357
+ }
358
+ // ---------------------------------------------------------------------------
359
+ // Public API — platform & alias management
360
+ // ---------------------------------------------------------------------------
112
361
  export function getConfigDir() {
113
362
  return getConfigDirPath();
114
363
  }
@@ -178,19 +427,32 @@ export function resolvePlatformIdentifier(value) {
178
427
  }
179
428
  return normalized;
180
429
  }
430
+ // ---------------------------------------------------------------------------
431
+ // Token config (user-scoped)
432
+ // ---------------------------------------------------------------------------
181
433
  export function loadTokenConfig(baseUrl) {
182
434
  ensureStoreReady();
183
435
  const targetBaseUrl = baseUrl ?? getCurrentPlatform();
184
436
  if (!targetBaseUrl) {
185
437
  return null;
186
438
  }
187
- return readJsonFile(getPlatformFile(targetBaseUrl, "token.json"));
439
+ return readJsonFile(resolveFile(targetBaseUrl, "token.json"));
188
440
  }
189
- export function saveTokenConfig(config) {
441
+ export function saveTokenConfig(config, userId) {
190
442
  ensureStoreReady();
191
- ensurePlatformDir(config.baseUrl);
192
- writeJsonFile(getPlatformFile(config.baseUrl, "token.json"), config);
193
- }
443
+ const resolvedUser = userId ?? extractUserId(config);
444
+ const dir = getUserDir(config.baseUrl, resolvedUser);
445
+ ensureDir(dir);
446
+ writeJsonFile(join(dir, "token.json"), config);
447
+ // When KWEAVER_USER is set the caller is doing a one-off operation;
448
+ // don't change the persisted active user.
449
+ if (!process.env.KWEAVER_USER) {
450
+ setActiveUser(config.baseUrl, resolvedUser);
451
+ }
452
+ }
453
+ // ---------------------------------------------------------------------------
454
+ // Client config (platform-level — shared across users)
455
+ // ---------------------------------------------------------------------------
194
456
  export function loadClientConfig(baseUrl) {
195
457
  ensureStoreReady();
196
458
  const targetBaseUrl = baseUrl ?? getCurrentPlatform();
@@ -204,6 +466,11 @@ export function saveClientConfig(baseUrl, config) {
204
466
  ensurePlatformDir(baseUrl);
205
467
  writeJsonFile(getPlatformFile(baseUrl, "client.json"), { ...config, baseUrl });
206
468
  }
469
+ export function deleteClientConfig(baseUrl) {
470
+ const filePath = getPlatformFile(baseUrl, "client.json");
471
+ if (existsSync(filePath))
472
+ rmSync(filePath, { force: true });
473
+ }
207
474
  function migrateLegacyContextLoader(raw) {
208
475
  const leg = raw;
209
476
  if (leg?.knId && !Array.isArray(raw.configs)) {
@@ -220,7 +487,7 @@ export function loadContextLoaderConfig(baseUrl) {
220
487
  if (!targetBaseUrl) {
221
488
  return null;
222
489
  }
223
- const raw = readJsonFile(getPlatformFile(targetBaseUrl, "context-loader.json"));
490
+ const raw = readJsonFile(resolveFile(targetBaseUrl, "context-loader.json"));
224
491
  if (!raw)
225
492
  return null;
226
493
  const migrated = migrateLegacyContextLoader(raw);
@@ -240,8 +507,16 @@ export function loadContextLoaderConfig(baseUrl) {
240
507
  }
241
508
  export function saveContextLoaderConfig(baseUrl, config) {
242
509
  ensureStoreReady();
243
- ensurePlatformDir(baseUrl);
244
- writeJsonFile(getPlatformFile(baseUrl, "context-loader.json"), config);
510
+ const userId = getActiveUser(baseUrl);
511
+ if (userId) {
512
+ const dir = getUserDir(baseUrl, userId);
513
+ ensureDir(dir);
514
+ writeJsonFile(join(dir, "context-loader.json"), config);
515
+ }
516
+ else {
517
+ ensurePlatformDir(baseUrl);
518
+ writeJsonFile(getPlatformFile(baseUrl, "context-loader.json"), config);
519
+ }
245
520
  }
246
521
  export function getCurrentContextLoaderKn(baseUrl) {
247
522
  ensureStoreReady();
@@ -297,7 +572,7 @@ export function removeContextLoaderEntry(baseUrl, name) {
297
572
  return;
298
573
  const newConfigs = config.configs.filter((c) => c.name !== name);
299
574
  if (newConfigs.length === 0) {
300
- const file = getPlatformFile(baseUrl, "context-loader.json");
575
+ const file = resolveFile(baseUrl, "context-loader.json");
301
576
  if (existsSync(file))
302
577
  rmSync(file, { force: true });
303
578
  return;
@@ -308,18 +583,47 @@ export function removeContextLoaderEntry(baseUrl, name) {
308
583
  }
309
584
  saveContextLoaderConfig(baseUrl, { configs: newConfigs, current: newCurrent });
310
585
  }
586
+ // ---------------------------------------------------------------------------
587
+ // Platform existence / session management
588
+ // ---------------------------------------------------------------------------
311
589
  export function hasPlatform(baseUrl) {
312
590
  ensureStoreReady();
313
- return existsSync(getPlatformFile(baseUrl, "token.json"));
591
+ const file = resolveFile(baseUrl, "token.json");
592
+ if (existsSync(file))
593
+ return true;
594
+ return listUsers(baseUrl).length > 0;
314
595
  }
315
- /**
316
- * Remove token for a platform so the next auth will do a full login.
317
- */
318
- export function clearPlatformSession(baseUrl) {
596
+ export function clearPlatformSession(baseUrl, userId) {
319
597
  ensureStoreReady();
320
- const tokenFile = getPlatformFile(baseUrl, "token.json");
321
- if (existsSync(tokenFile)) {
322
- rmSync(tokenFile, { force: true });
598
+ const target = userId ?? getActiveUser(baseUrl);
599
+ if (target) {
600
+ const tokenFile = join(getUserDir(baseUrl, target), "token.json");
601
+ if (existsSync(tokenFile))
602
+ rmSync(tokenFile, { force: true });
603
+ return;
604
+ }
605
+ // Fallback: legacy flat layout
606
+ const legacyToken = getPlatformFile(baseUrl, "token.json");
607
+ if (existsSync(legacyToken))
608
+ rmSync(legacyToken, { force: true });
609
+ }
610
+ /** Delete a single user's profile directory under a platform. */
611
+ export function deleteUser(baseUrl, userId) {
612
+ ensureStoreReady();
613
+ const dir = getUserDir(baseUrl, userId);
614
+ if (existsSync(dir))
615
+ rmSync(dir, { recursive: true, force: true });
616
+ const state = readState();
617
+ if (state.activeUsers?.[baseUrl] === userId) {
618
+ const remaining = listUsers(baseUrl);
619
+ const au = { ...(state.activeUsers ?? {}) };
620
+ if (remaining.length > 0) {
621
+ au[baseUrl] = remaining[0];
622
+ }
623
+ else {
624
+ delete au[baseUrl];
625
+ }
626
+ writeState({ ...state, activeUsers: Object.keys(au).length > 0 ? au : undefined });
323
627
  }
324
628
  }
325
629
  export function deletePlatform(baseUrl) {
@@ -331,33 +635,76 @@ export function deletePlatform(baseUrl) {
331
635
  deletePlatformAlias(baseUrl);
332
636
  rmSync(platformDir, { recursive: true, force: true });
333
637
  const state = readState();
638
+ const au = { ...(state.activeUsers ?? {}) };
639
+ delete au[baseUrl];
334
640
  if (state.currentPlatform !== baseUrl) {
641
+ writeState({ ...state, activeUsers: Object.keys(au).length > 0 ? au : undefined });
335
642
  return;
336
643
  }
337
644
  const remainingPlatforms = listPlatforms();
338
645
  writeState({
339
646
  ...readState(),
340
647
  currentPlatform: remainingPlatforms[0]?.baseUrl,
648
+ activeUsers: Object.keys(au).length > 0 ? au : undefined,
341
649
  });
342
650
  }
343
651
  export function listPlatforms() {
344
652
  ensureStoreReady();
345
653
  const currentPlatform = getCurrentPlatform();
346
654
  const items = [];
347
- for (const entry of readdirSync(getPlatformsDirPath())) {
348
- const dirPath = join(getPlatformsDirPath(), entry);
655
+ const platformsDir = getPlatformsDirPath();
656
+ if (!existsSync(platformsDir))
657
+ return items;
658
+ for (const entry of readdirSync(platformsDir)) {
659
+ const dirPath = join(platformsDir, entry);
349
660
  if (!statSync(dirPath).isDirectory()) {
350
661
  continue;
351
662
  }
352
- const token = readJsonFile(join(dirPath, "token.json"));
353
- if (!token?.baseUrl) {
663
+ // Try to find the baseUrl from any available token
664
+ let baseUrl = null;
665
+ // Check users/ subdirectory first (new layout)
666
+ const usersDir = join(dirPath, "users");
667
+ if (existsSync(usersDir)) {
668
+ for (const userEntry of readdirSync(usersDir)) {
669
+ const userTokenPath = join(usersDir, userEntry, "token.json");
670
+ const userToken = readJsonFile(userTokenPath);
671
+ if (userToken?.baseUrl) {
672
+ baseUrl = userToken.baseUrl;
673
+ break;
674
+ }
675
+ }
676
+ }
677
+ // Fallback: legacy flat layout
678
+ if (!baseUrl) {
679
+ const token = readJsonFile(join(dirPath, "token.json"));
680
+ if (token?.baseUrl) {
681
+ baseUrl = token.baseUrl;
682
+ }
683
+ }
684
+ // Fallback: client.json
685
+ if (!baseUrl) {
686
+ const client = readJsonFile(join(dirPath, "client.json"));
687
+ if (client?.baseUrl) {
688
+ baseUrl = client.baseUrl;
689
+ }
690
+ }
691
+ if (!baseUrl)
354
692
  continue;
693
+ const hasToken = existsSync(resolveFile(baseUrl, "token.json"));
694
+ const activeUser = getActiveUserRaw(baseUrl);
695
+ let displayName;
696
+ if (activeUser) {
697
+ const tok = loadUserTokenConfig(baseUrl, activeUser);
698
+ if (tok?.displayName)
699
+ displayName = tok.displayName;
355
700
  }
356
701
  items.push({
357
- baseUrl: token.baseUrl,
358
- hasToken: true,
359
- isCurrent: token.baseUrl === currentPlatform,
360
- alias: getPlatformAlias(token.baseUrl) ?? undefined,
702
+ baseUrl,
703
+ hasToken,
704
+ isCurrent: baseUrl === currentPlatform,
705
+ alias: getPlatformAlias(baseUrl) ?? undefined,
706
+ userId: activeUser ?? undefined,
707
+ displayName,
361
708
  });
362
709
  }
363
710
  items.sort((a, b) => a.baseUrl.localeCompare(b.baseUrl));
@@ -365,12 +712,20 @@ export function listPlatforms() {
365
712
  }
366
713
  function loadPlatformConfig(baseUrl) {
367
714
  ensureStoreReady();
368
- return readJsonFile(getPlatformFile(baseUrl, "config.json"));
715
+ return readJsonFile(resolveFile(baseUrl, "config.json"));
369
716
  }
370
717
  function savePlatformConfig(baseUrl, config) {
371
718
  ensureStoreReady();
372
- ensurePlatformDir(baseUrl);
373
- writeJsonFile(getPlatformFile(baseUrl, "config.json"), config);
719
+ const userId = getActiveUser(baseUrl);
720
+ if (userId) {
721
+ const dir = getUserDir(baseUrl, userId);
722
+ ensureDir(dir);
723
+ writeJsonFile(join(dir, "config.json"), config);
724
+ }
725
+ else {
726
+ ensurePlatformDir(baseUrl);
727
+ writeJsonFile(getPlatformFile(baseUrl, "config.json"), config);
728
+ }
374
729
  }
375
730
  export function loadPlatformBusinessDomain(baseUrl) {
376
731
  return loadPlatformConfig(baseUrl)?.businessDomain ?? null;
package/dist/index.d.ts CHANGED
@@ -49,6 +49,9 @@ export type { AgentConfig, AgentInput, AgentInputField, AgentOutput, AgentLlmCon
49
49
  export { BknResource } from "./resources/bkn.js";
50
50
  export { ConversationsResource } from "./resources/conversations.js";
51
51
  export { ContextLoaderResource } from "./resources/context-loader.js";
52
+ export { SkillsResource } from "./resources/skills.js";
53
+ export type { SkillStatus, SkillSummary, SkillInfo, SkillFileSummary, SkillContentIndex, SkillFileReadResult, RegisterSkillResult, DeleteSkillResult, UpdateSkillStatusResult, SkillListResult, ListSkillsOptions, ListSkillMarketOptions, GetSkillOptions, RegisterSkillContentOptions, RegisterSkillZipOptions, UpdateSkillStatusOptions, ReadSkillFileOptions, DownloadSkillOptions, DownloadedSkillArchive, } from "./api/skills.js";
54
+ export { listSkills, listSkillMarket, getSkill, deleteSkill, updateSkillStatus, registerSkillContent, registerSkillZip, getSkillContentIndex, fetchSkillContent, readSkillFile, fetchSkillFile, downloadSkill, installSkillArchive, } from "./api/skills.js";
52
55
  export type { ViewField, DataView, CreateDataViewOptions, GetDataViewOptions, ListDataViewsOptions, DeleteDataViewOptions, FindDataViewOptions, QueryDataViewOptions, DataViewQueryResult, } from "./api/dataviews.js";
53
56
  export { parseDataView, createDataView, getDataView, listDataViews, deleteDataView, findDataView, queryDataView, } from "./api/dataviews.js";
54
57
  export { DataViewsResource } from "./resources/dataviews.js";
@@ -56,4 +59,6 @@ export type { BusinessDomain, ListBusinessDomainsOptions } from "./api/business-
56
59
  export { listBusinessDomains } from "./api/business-domains.js";
57
60
  export { HttpError, NetworkRequestError, fetchTextOrThrow } from "./utils/http.js";
58
61
  export type { TokenConfig, ContextLoaderEntry, ContextLoaderConfig, } from "./config/store.js";
59
- export { autoSelectBusinessDomain, getConfigDir, getCurrentPlatform } from "./config/store.js";
62
+ export type { UserProfile } from "./config/store.js";
63
+ export { NO_AUTH_TOKEN, isNoAuth, saveNoAuthPlatform, autoSelectBusinessDomain, getConfigDir, getCurrentPlatform, getActiveUser, setActiveUser, listUsers, listUserProfiles, resolveUserId, extractUserId, } from "./config/store.js";
64
+ export { decodeJwtPayload, extractUserIdFromJwt } from "./config/jwt.js";
package/dist/index.js CHANGED
@@ -39,9 +39,13 @@ export { AgentsResource } from "./resources/agents.js";
39
39
  export { BknResource } from "./resources/bkn.js";
40
40
  export { ConversationsResource } from "./resources/conversations.js";
41
41
  export { ContextLoaderResource } from "./resources/context-loader.js";
42
+ export { SkillsResource } from "./resources/skills.js";
43
+ export { listSkills, listSkillMarket, getSkill, deleteSkill, updateSkillStatus, registerSkillContent, registerSkillZip, getSkillContentIndex, fetchSkillContent, readSkillFile, fetchSkillFile, downloadSkill, installSkillArchive, } from "./api/skills.js";
42
44
  export { parseDataView, createDataView, getDataView, listDataViews, deleteDataView, findDataView, queryDataView, } from "./api/dataviews.js";
43
45
  export { DataViewsResource } from "./resources/dataviews.js";
44
46
  export { listBusinessDomains } from "./api/business-domains.js";
45
47
  // ── HTTP utilities ────────────────────────────────────────────────────────────
46
48
  export { HttpError, NetworkRequestError, fetchTextOrThrow } from "./utils/http.js";
47
- export { autoSelectBusinessDomain, getConfigDir, getCurrentPlatform } from "./config/store.js";
49
+ export { NO_AUTH_TOKEN, isNoAuth, saveNoAuthPlatform, autoSelectBusinessDomain, getConfigDir, getCurrentPlatform, getActiveUser, setActiveUser, listUsers, listUserProfiles, resolveUserId, extractUserId, } from "./config/store.js";
50
+ // ── JWT utilities ─────────────────────────────────────────────────────────────
51
+ export { decodeJwtPayload, extractUserIdFromJwt } from "./config/jwt.js";
package/dist/kweaver.d.ts CHANGED
@@ -37,6 +37,11 @@ export interface ConfigureOptions {
37
37
  * config, preventing accidental cross-environment credential leaks.
38
38
  */
39
39
  config?: boolean;
40
+ /**
41
+ * If false, use no-auth mode (no Authorization headers). Requires baseUrl or KWEAVER_BASE_URL.
42
+ * Incompatible with config=true.
43
+ */
44
+ auth?: boolean;
40
45
  /** Default BKN ID used by search() and weaver(). */
41
46
  bknId?: string;
42
47
  /** Default agent ID used by chat(). */