@kweaver-ai/kweaver-sdk 0.4.2 → 0.4.5
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/api/agent-chat.js +4 -2
- package/dist/api/context-loader.js +6 -2
- package/dist/api/conversations.js +2 -2
- package/dist/api/datasources.js +2 -1
- package/dist/api/vega.js +6 -3
- package/dist/auth/oauth.d.ts +17 -70
- package/dist/auth/oauth.js +215 -332
- package/dist/cli.js +6 -0
- package/dist/client.js +1 -12
- package/dist/commands/agent-chat.js +4 -1
- package/dist/commands/agent.js +60 -33
- package/dist/commands/auth.d.ts +0 -8
- package/dist/commands/auth.js +59 -80
- package/dist/commands/bkn.js +162 -76
- package/dist/commands/call.js +17 -10
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +52 -0
- package/dist/commands/context-loader.js +39 -42
- package/dist/commands/ds.js +22 -15
- package/dist/commands/vega.js +23 -11
- package/dist/config/store.d.ts +23 -22
- package/dist/config/store.js +48 -34
- package/dist/index.d.ts +1 -1
- package/package.json +9 -1
package/dist/auth/oauth.js
CHANGED
|
@@ -1,275 +1,165 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { getCurrentPlatform, loadCallbackSession, loadClientConfig, loadTokenConfig, saveCallbackSession, saveClientConfig, saveTokenConfig, setCurrentPlatform, } from "../config/store.js";
|
|
1
|
+
import { getCurrentPlatform, loadClientConfig, loadTokenConfig, saveTokenConfig, setCurrentPlatform, } from "../config/store.js";
|
|
2
|
+
import { HttpError, NetworkRequestError } from "../utils/http.js";
|
|
3
|
+
const TOKEN_TTL_SECONDS = 3600;
|
|
4
|
+
/** Seconds before access token expiry to trigger refresh (matches Python ConfigAuth). */
|
|
5
|
+
const REFRESH_THRESHOLD_SEC = 60;
|
|
7
6
|
export function normalizeBaseUrl(value) {
|
|
8
7
|
return value.replace(/\/+$/, "");
|
|
9
8
|
}
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
function toBasicAuth(clientId, clientSecret) {
|
|
17
|
-
const user = encodeURIComponent(clientId);
|
|
18
|
-
const password = encodeURIComponent(clientSecret);
|
|
19
|
-
return `Basic ${Buffer.from(`${user}:${password}`).toString("base64")}`;
|
|
20
|
-
}
|
|
21
|
-
function buildTokenConfig(baseUrl, token) {
|
|
22
|
-
const obtainedAt = new Date().toISOString();
|
|
23
|
-
const expiresAt = token.expires_in === undefined
|
|
24
|
-
? undefined
|
|
25
|
-
: new Date(Date.now() + token.expires_in * 1000).toISOString();
|
|
26
|
-
return {
|
|
27
|
-
baseUrl,
|
|
28
|
-
accessToken: token.access_token,
|
|
29
|
-
tokenType: token.token_type,
|
|
30
|
-
scope: token.scope,
|
|
31
|
-
expiresIn: token.expires_in,
|
|
32
|
-
expiresAt,
|
|
33
|
-
refreshToken: token.refresh_token,
|
|
34
|
-
idToken: token.id_token,
|
|
35
|
-
obtainedAt,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
export async function registerClient(options) {
|
|
39
|
-
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
40
|
-
const payload = {
|
|
41
|
-
client_name: options.clientName,
|
|
42
|
-
grant_types: ["authorization_code", "implicit", "refresh_token"],
|
|
43
|
-
response_types: ["token id_token", "code", "token"],
|
|
44
|
-
scope: "openid offline all",
|
|
45
|
-
redirect_uris: [options.redirectUri],
|
|
46
|
-
post_logout_redirect_uris: [options.logoutRedirectUri],
|
|
47
|
-
metadata: {
|
|
48
|
-
device: {
|
|
49
|
-
name: options.clientName,
|
|
50
|
-
client_type: "web",
|
|
51
|
-
description: "kweaver CLI",
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
const { body } = await fetchTextOrThrow(`${baseUrl}/oauth2/clients`, {
|
|
56
|
-
method: "POST",
|
|
57
|
-
headers: {
|
|
58
|
-
"content-type": "application/json",
|
|
59
|
-
accept: "application/json",
|
|
60
|
-
},
|
|
61
|
-
body: JSON.stringify(payload),
|
|
62
|
-
});
|
|
63
|
-
const data = JSON.parse(body);
|
|
64
|
-
const clientConfig = {
|
|
65
|
-
baseUrl,
|
|
66
|
-
clientId: data.client_id,
|
|
67
|
-
clientSecret: data.client_secret,
|
|
68
|
-
redirectUri: options.redirectUri,
|
|
69
|
-
logoutRedirectUri: options.logoutRedirectUri,
|
|
70
|
-
scope: payload.scope,
|
|
71
|
-
lang: options.lang,
|
|
72
|
-
product: options.product,
|
|
73
|
-
xForwardedPrefix: options.xForwardedPrefix,
|
|
74
|
-
};
|
|
75
|
-
saveClientConfig(clientConfig);
|
|
76
|
-
return clientConfig;
|
|
77
|
-
}
|
|
78
|
-
function toSuccessfulLogoutPath(pathname) {
|
|
79
|
-
if (pathname.endsWith("/callback")) {
|
|
80
|
-
return `${pathname.slice(0, -"/callback".length)}/successful-logout`;
|
|
9
|
+
export async function playwrightLogin(baseUrl, options) {
|
|
10
|
+
let chromium;
|
|
11
|
+
try {
|
|
12
|
+
const modName = "playwright";
|
|
13
|
+
const pw = await import(/* webpackIgnore: true */ modName);
|
|
14
|
+
chromium = pw.chromium;
|
|
81
15
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
16
|
+
catch {
|
|
17
|
+
throw new Error("Playwright is not installed. Run:\n npm install playwright && npx playwright install chromium");
|
|
18
|
+
}
|
|
19
|
+
const hasCredentials = options?.username && options?.password;
|
|
20
|
+
const browser = await chromium.launch({ headless: hasCredentials ? true : false });
|
|
21
|
+
try {
|
|
22
|
+
const context = await browser.newContext();
|
|
23
|
+
const page = await context.newPage();
|
|
24
|
+
await page.goto(`${baseUrl}/api/dip-hub/v1/login`, {
|
|
25
|
+
waitUntil: "networkidle",
|
|
26
|
+
timeout: 30_000,
|
|
27
|
+
});
|
|
28
|
+
if (hasCredentials) {
|
|
29
|
+
// Headless mode: auto-fill credentials
|
|
30
|
+
await page.waitForSelector('input[name="account"]', { timeout: 10_000 });
|
|
31
|
+
await page.fill('input[name="account"]', options.username);
|
|
32
|
+
await page.fill('input[name="password"]', options.password);
|
|
33
|
+
await page.click("button.ant-btn-primary");
|
|
34
|
+
}
|
|
35
|
+
// else: headed mode — user logs in manually in the browser window
|
|
36
|
+
const TIMEOUT_SECONDS = hasCredentials ? 30 : 120;
|
|
37
|
+
let accessToken = null;
|
|
38
|
+
for (let i = 0; i < TIMEOUT_SECONDS; i++) {
|
|
39
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
40
|
+
// Check cookies (works even after navigation)
|
|
41
|
+
for (const cookie of await context.cookies()) {
|
|
42
|
+
if (cookie.name === "dip.oauth2_token") {
|
|
43
|
+
accessToken = decodeURIComponent(cookie.value);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (accessToken)
|
|
48
|
+
break;
|
|
49
|
+
// In headless mode, check for login error messages
|
|
50
|
+
if (hasCredentials) {
|
|
51
|
+
try {
|
|
52
|
+
const errorEl = await page.$(".ant-message-error, .ant-alert-error");
|
|
53
|
+
if (errorEl) {
|
|
54
|
+
const errorText = await errorEl.textContent();
|
|
55
|
+
throw new Error(`Login failed: ${errorText?.trim() || "unknown error"}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
if (e instanceof Error && e.message.startsWith("Login failed:"))
|
|
60
|
+
throw e;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (!accessToken) {
|
|
65
|
+
throw new Error(`Login timed out: dip.oauth2_token cookie not received within ${TIMEOUT_SECONDS} seconds.`);
|
|
66
|
+
}
|
|
67
|
+
const now = new Date();
|
|
68
|
+
const tokenConfig = {
|
|
69
|
+
baseUrl,
|
|
70
|
+
accessToken,
|
|
71
|
+
tokenType: "bearer",
|
|
72
|
+
scope: "",
|
|
73
|
+
expiresIn: TOKEN_TTL_SECONDS,
|
|
74
|
+
expiresAt: new Date(now.getTime() + TOKEN_TTL_SECONDS * 1000).toISOString(),
|
|
75
|
+
obtainedAt: now.toISOString(),
|
|
102
76
|
};
|
|
77
|
+
saveTokenConfig(tokenConfig);
|
|
78
|
+
setCurrentPlatform(baseUrl);
|
|
79
|
+
return tokenConfig;
|
|
103
80
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
logoutRedirectUri: `http://${listenHost}:${listenPort}/successful-logout`,
|
|
107
|
-
listenHost,
|
|
108
|
-
listenPort,
|
|
109
|
-
callbackPath: "/callback",
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
export async function ensureClientConfig(options) {
|
|
113
|
-
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
114
|
-
const redirect = buildAuthRedirectConfig({
|
|
115
|
-
port: options.port,
|
|
116
|
-
host: options.host,
|
|
117
|
-
redirectUriOverride: options.redirectUriOverride,
|
|
118
|
-
});
|
|
119
|
-
const { redirectUri, logoutRedirectUri } = redirect;
|
|
120
|
-
const client = loadClientConfig(baseUrl);
|
|
121
|
-
if (client &&
|
|
122
|
-
!options.forceRegister &&
|
|
123
|
-
client.baseUrl === baseUrl &&
|
|
124
|
-
client.redirectUri === redirectUri &&
|
|
125
|
-
client.clientId &&
|
|
126
|
-
client.clientSecret) {
|
|
127
|
-
return { client, created: false };
|
|
81
|
+
finally {
|
|
82
|
+
await browser.close();
|
|
128
83
|
}
|
|
129
|
-
const createdClient = await registerClient({
|
|
130
|
-
baseUrl,
|
|
131
|
-
clientName: options.clientName,
|
|
132
|
-
redirectUri,
|
|
133
|
-
logoutRedirectUri,
|
|
134
|
-
lang: options.lang,
|
|
135
|
-
product: options.product,
|
|
136
|
-
xForwardedPrefix: options.xForwardedPrefix,
|
|
137
|
-
});
|
|
138
|
-
return {
|
|
139
|
-
client: createdClient,
|
|
140
|
-
created: true,
|
|
141
|
-
};
|
|
142
84
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
authorizationUrl.searchParams.set("x-forwarded-prefix", client.xForwardedPrefix ?? "");
|
|
147
|
-
authorizationUrl.searchParams.set("client_id", client.clientId);
|
|
148
|
-
authorizationUrl.searchParams.set("scope", client.scope);
|
|
149
|
-
authorizationUrl.searchParams.set("response_type", "code");
|
|
150
|
-
authorizationUrl.searchParams.set("state", state);
|
|
151
|
-
authorizationUrl.searchParams.set("lang", client.lang ?? "zh-cn");
|
|
152
|
-
if (client.product) {
|
|
153
|
-
authorizationUrl.searchParams.set("product", client.product);
|
|
85
|
+
function tokenNeedsRefresh(token) {
|
|
86
|
+
if (!token.expiresAt) {
|
|
87
|
+
return false;
|
|
154
88
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (!request.url) {
|
|
162
|
-
response.statusCode = 400;
|
|
163
|
-
response.end("Missing request URL");
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const callbackUrl = new URL(request.url, `http://${request.headers.host ?? `${listenHost}:${listenPort}`}`);
|
|
167
|
-
if (callbackUrl.pathname !== callbackPath) {
|
|
168
|
-
response.statusCode = 404;
|
|
169
|
-
response.end("Not Found");
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
const error = callbackUrl.searchParams.get("error");
|
|
173
|
-
if (error) {
|
|
174
|
-
response.statusCode = 400;
|
|
175
|
-
response.end(`Authorization failed: ${error}`);
|
|
176
|
-
server.close();
|
|
177
|
-
reject(new Error(`Authorization failed: ${error}`));
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
const state = callbackUrl.searchParams.get("state");
|
|
181
|
-
if (state !== expectedState) {
|
|
182
|
-
response.statusCode = 400;
|
|
183
|
-
response.end("State mismatch");
|
|
184
|
-
server.close();
|
|
185
|
-
reject(new Error("State mismatch in OAuth callback"));
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
const code = callbackUrl.searchParams.get("code");
|
|
189
|
-
if (!code) {
|
|
190
|
-
response.statusCode = 400;
|
|
191
|
-
response.end("Missing authorization code");
|
|
192
|
-
server.close();
|
|
193
|
-
reject(new Error("Missing authorization code"));
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
response.statusCode = 200;
|
|
197
|
-
response.setHeader("content-type", "text/plain; charset=utf-8");
|
|
198
|
-
response.end(getAuthorizationSuccessMessage());
|
|
199
|
-
server.close();
|
|
200
|
-
resolve({
|
|
201
|
-
code,
|
|
202
|
-
state,
|
|
203
|
-
scope: callbackUrl.searchParams.get("scope") ?? undefined,
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
server.on("error", (error) => reject(error));
|
|
207
|
-
server.listen(listenPort, listenHost);
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
async function exchangeAuthorizationCode(client, code) {
|
|
211
|
-
const payload = new URLSearchParams({
|
|
212
|
-
grant_type: "authorization_code",
|
|
213
|
-
code,
|
|
214
|
-
redirect_uri: client.redirectUri,
|
|
215
|
-
});
|
|
216
|
-
const { body } = await fetchTextOrThrow(`${client.baseUrl}/oauth2/token`, {
|
|
217
|
-
method: "POST",
|
|
218
|
-
headers: {
|
|
219
|
-
"content-type": "application/x-www-form-urlencoded",
|
|
220
|
-
accept: "application/json",
|
|
221
|
-
authorization: toBasicAuth(client.clientId, client.clientSecret),
|
|
222
|
-
},
|
|
223
|
-
body: payload.toString(),
|
|
224
|
-
});
|
|
225
|
-
const tokenConfig = buildTokenConfig(client.baseUrl, JSON.parse(body));
|
|
226
|
-
saveTokenConfig(tokenConfig);
|
|
227
|
-
return tokenConfig;
|
|
89
|
+
const expiresAtMs = Date.parse(token.expiresAt);
|
|
90
|
+
if (Number.isNaN(expiresAtMs)) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
const thresholdMs = REFRESH_THRESHOLD_SEC * 1000;
|
|
94
|
+
return expiresAtMs - thresholdMs <= Date.now();
|
|
228
95
|
}
|
|
229
96
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
97
|
+
* Exchange refresh_token for a new access token (OAuth2 password grant style, same as Python ConfigAuth).
|
|
98
|
+
* Persists the new token to ~/.kweaver/ and returns it.
|
|
232
99
|
*/
|
|
233
|
-
export async function
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
url.searchParams.set("post_logout_redirect_uri", client.logoutRedirectUri);
|
|
239
|
-
url.searchParams.set("client_id", client.clientId);
|
|
240
|
-
try {
|
|
241
|
-
const response = await fetch(url.toString(), { method: "GET", redirect: "manual" });
|
|
242
|
-
if (!response.ok && response.status !== 302) {
|
|
243
|
-
const body = await response.text();
|
|
244
|
-
console.error(`Logout endpoint returned ${response.status}: ${body.slice(0, 200)}`);
|
|
245
|
-
}
|
|
100
|
+
export async function refreshAccessToken(token) {
|
|
101
|
+
const baseUrl = normalizeBaseUrl(token.baseUrl);
|
|
102
|
+
const refreshToken = token.refreshToken?.trim();
|
|
103
|
+
if (!refreshToken) {
|
|
104
|
+
throw new Error(`Token expired and no refresh_token available for ${baseUrl}. Run \`kweaver auth login ${baseUrl}\` again.`);
|
|
246
105
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
106
|
+
const client = loadClientConfig(baseUrl);
|
|
107
|
+
const clientId = client?.clientId?.trim() ?? "";
|
|
108
|
+
const clientSecret = client?.clientSecret?.trim() ?? "";
|
|
109
|
+
if (!clientId || !clientSecret) {
|
|
110
|
+
throw new Error(`Token refresh requires OAuth client credentials (client.json). Run \`kweaver auth login ${baseUrl}\` again.`);
|
|
250
111
|
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const
|
|
112
|
+
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
|
113
|
+
const url = `${baseUrl}/oauth2/token`;
|
|
114
|
+
const body = new URLSearchParams({
|
|
254
115
|
grant_type: "refresh_token",
|
|
255
116
|
refresh_token: refreshToken,
|
|
256
117
|
});
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
118
|
+
let response;
|
|
119
|
+
try {
|
|
120
|
+
response = await fetch(url, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: {
|
|
123
|
+
Authorization: `Basic ${credentials}`,
|
|
124
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
125
|
+
Accept: "application/json",
|
|
126
|
+
},
|
|
127
|
+
body: body.toString(),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
catch (cause) {
|
|
131
|
+
const hint = cause instanceof Error ? cause.message : String(cause);
|
|
132
|
+
throw new NetworkRequestError("POST", url, hint, "Check network connectivity and that the platform exposes /oauth2/token.");
|
|
133
|
+
}
|
|
134
|
+
const text = await response.text();
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
throw new HttpError(response.status, response.statusText, text);
|
|
137
|
+
}
|
|
138
|
+
let data;
|
|
139
|
+
try {
|
|
140
|
+
data = JSON.parse(text);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
throw new Error(`Invalid JSON from ${url} during token refresh.`);
|
|
144
|
+
}
|
|
145
|
+
if (typeof data.access_token !== "string") {
|
|
146
|
+
throw new Error(`Token refresh response missing access_token from ${url}.`);
|
|
147
|
+
}
|
|
148
|
+
const now = new Date();
|
|
149
|
+
const expiresIn = typeof data.expires_in === "number" ? data.expires_in : 3600;
|
|
150
|
+
const newToken = {
|
|
151
|
+
baseUrl,
|
|
152
|
+
accessToken: data.access_token,
|
|
153
|
+
tokenType: data.token_type ?? "Bearer",
|
|
154
|
+
scope: data.scope ?? token.scope ?? "",
|
|
155
|
+
expiresIn,
|
|
156
|
+
expiresAt: new Date(now.getTime() + expiresIn * 1000).toISOString(),
|
|
157
|
+
refreshToken: data.refresh_token ?? refreshToken,
|
|
158
|
+
idToken: data.id_token ?? token.idToken ?? "",
|
|
159
|
+
obtainedAt: now.toISOString(),
|
|
160
|
+
};
|
|
161
|
+
saveTokenConfig(newToken);
|
|
162
|
+
return newToken;
|
|
273
163
|
}
|
|
274
164
|
export async function ensureValidToken(opts) {
|
|
275
165
|
const envToken = process.env.KWEAVER_TOKEN;
|
|
@@ -280,98 +170,91 @@ export async function ensureValidToken(opts) {
|
|
|
280
170
|
baseUrl: normalizeBaseUrl(envBaseUrl),
|
|
281
171
|
accessToken: rawToken,
|
|
282
172
|
tokenType: "bearer",
|
|
283
|
-
scope: "
|
|
173
|
+
scope: "",
|
|
284
174
|
obtainedAt: new Date().toISOString(),
|
|
285
175
|
};
|
|
286
176
|
}
|
|
287
177
|
const currentPlatform = getCurrentPlatform();
|
|
288
178
|
if (!currentPlatform) {
|
|
289
|
-
throw new Error("No active platform selected. Run `kweaver auth <platform-url>` first.");
|
|
179
|
+
throw new Error("No active platform selected. Run `kweaver auth login <platform-url>` first.");
|
|
290
180
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
throw new Error(`Missing saved credentials for ${currentPlatform}. Run \`kweaver auth ${currentPlatform}\` first.`);
|
|
181
|
+
let token = loadTokenConfig(currentPlatform);
|
|
182
|
+
if (!token) {
|
|
183
|
+
throw new Error(`No saved token for ${currentPlatform}. Run \`kweaver auth login ${currentPlatform}\` first.`);
|
|
295
184
|
}
|
|
296
|
-
if (
|
|
297
|
-
|
|
298
|
-
|
|
185
|
+
if (opts?.forceRefresh) {
|
|
186
|
+
return refreshAccessToken(token);
|
|
187
|
+
}
|
|
188
|
+
if (tokenNeedsRefresh(token)) {
|
|
189
|
+
try {
|
|
190
|
+
return await refreshAccessToken(token);
|
|
299
191
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
192
|
+
catch (err) {
|
|
193
|
+
throw new Error(`Access token expired or near expiry and refresh failed for ${currentPlatform}.\n` +
|
|
194
|
+
(err instanceof Error ? `${err.message}\n` : "") +
|
|
195
|
+
`Run \`kweaver auth login ${currentPlatform}\` again.`, { cause: err });
|
|
303
196
|
}
|
|
304
197
|
}
|
|
305
|
-
|
|
306
|
-
throw new Error("Access token expired and no refresh token is available. Run auth login again.");
|
|
307
|
-
}
|
|
308
|
-
return refreshAccessToken(client, token.refreshToken);
|
|
198
|
+
return token;
|
|
309
199
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
baseUrl: options.baseUrl,
|
|
318
|
-
port: options.port,
|
|
319
|
-
clientName: options.clientName,
|
|
320
|
-
forceRegister: options.forceRegister,
|
|
321
|
-
host: options.host,
|
|
322
|
-
redirectUriOverride: options.redirectUriOverride,
|
|
323
|
-
lang: options.lang,
|
|
324
|
-
product: options.product,
|
|
325
|
-
xForwardedPrefix: options.xForwardedPrefix,
|
|
326
|
-
});
|
|
327
|
-
const state = randomValue(12);
|
|
328
|
-
const authorizationUrl = buildAuthorizationUrl(client, state);
|
|
329
|
-
const waitForCode = waitForAuthorizationCode({
|
|
330
|
-
listenHost: redirect.listenHost,
|
|
331
|
-
listenPort: redirect.listenPort,
|
|
332
|
-
callbackPath: redirect.callbackPath,
|
|
333
|
-
}, state);
|
|
334
|
-
if (options.open) {
|
|
335
|
-
console.log(`Opening browser for authorization: ${authorizationUrl}`);
|
|
336
|
-
const opened = await openBrowser(authorizationUrl);
|
|
337
|
-
if (!opened) {
|
|
338
|
-
console.error("Failed to open a browser automatically. Open this URL manually:");
|
|
339
|
-
console.error(authorizationUrl);
|
|
340
|
-
}
|
|
200
|
+
/**
|
|
201
|
+
* Run an operation; on HTTP 401, refresh the access token once and retry.
|
|
202
|
+
* Does not call `ensureValidToken` first — use for CLI routers so `--help` works without login.
|
|
203
|
+
*/
|
|
204
|
+
export async function with401RefreshRetry(fn) {
|
|
205
|
+
try {
|
|
206
|
+
return await fn();
|
|
341
207
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
208
|
+
catch (error) {
|
|
209
|
+
if (error instanceof HttpError && error.status === 401) {
|
|
210
|
+
const currentPlatform = getCurrentPlatform();
|
|
211
|
+
if (!currentPlatform) {
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
const platformUrl = normalizeBaseUrl(currentPlatform);
|
|
215
|
+
const latest = loadTokenConfig(platformUrl);
|
|
216
|
+
if (!latest) {
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
await refreshAccessToken(latest);
|
|
221
|
+
}
|
|
222
|
+
catch (retryErr) {
|
|
223
|
+
const oauthHint = formatOAuthErrorBody(retryErr instanceof HttpError ? retryErr.body : "");
|
|
224
|
+
const extra = oauthHint ? `\n\n${oauthHint}` : "";
|
|
225
|
+
throw new Error(`Authentication failed (401). Token refresh did not succeed for ${platformUrl}.${extra}\n` +
|
|
226
|
+
`Run \`kweaver auth login ${platformUrl}\` again.`, { cause: retryErr });
|
|
227
|
+
}
|
|
228
|
+
return await fn();
|
|
352
229
|
}
|
|
230
|
+
throw error;
|
|
353
231
|
}
|
|
354
|
-
const callbackResult = await waitForCode;
|
|
355
|
-
const callback = {
|
|
356
|
-
baseUrl: client.baseUrl,
|
|
357
|
-
redirectUri: client.redirectUri,
|
|
358
|
-
code: callbackResult.code,
|
|
359
|
-
state: callbackResult.state,
|
|
360
|
-
scope: callbackResult.scope,
|
|
361
|
-
receivedAt: new Date().toISOString(),
|
|
362
|
-
};
|
|
363
|
-
saveCallbackSession(callback);
|
|
364
|
-
const token = await exchangeAuthorizationCode(client, callbackResult.code);
|
|
365
|
-
setCurrentPlatform(client.baseUrl);
|
|
366
|
-
return { client, token, authorizationUrl, callback, created };
|
|
367
232
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
233
|
+
/**
|
|
234
|
+
* Load a valid token, run `fn(token)`, and on 401 refresh once and retry with the new token.
|
|
235
|
+
*/
|
|
236
|
+
export async function withTokenRetry(fn) {
|
|
237
|
+
const token = await ensureValidToken();
|
|
238
|
+
try {
|
|
239
|
+
return await fn(token);
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
if (error instanceof HttpError && error.status === 401) {
|
|
243
|
+
const platformUrl = normalizeBaseUrl(token.baseUrl);
|
|
244
|
+
const latest = loadTokenConfig(platformUrl) ?? token;
|
|
245
|
+
try {
|
|
246
|
+
const refreshed = await refreshAccessToken(latest);
|
|
247
|
+
return await fn(refreshed);
|
|
248
|
+
}
|
|
249
|
+
catch (retryErr) {
|
|
250
|
+
const oauthHint = formatOAuthErrorBody(retryErr instanceof HttpError ? retryErr.body : "");
|
|
251
|
+
const extra = oauthHint ? `\n\n${oauthHint}` : "";
|
|
252
|
+
throw new Error(`Authentication failed (401). Token refresh did not succeed for ${platformUrl}.${extra}\n` +
|
|
253
|
+
`Run \`kweaver auth login ${platformUrl}\` again.`, { cause: retryErr });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
375
258
|
}
|
|
376
259
|
function formatOAuthErrorBody(body) {
|
|
377
260
|
let data;
|
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@ import { runAgentCommand } from "./commands/agent.js";
|
|
|
2
2
|
import { runAuthCommand } from "./commands/auth.js";
|
|
3
3
|
import { runKnCommand } from "./commands/bkn.js";
|
|
4
4
|
import { runCallCommand } from "./commands/call.js";
|
|
5
|
+
import { runConfigCommand } from "./commands/config.js";
|
|
5
6
|
import { runContextLoaderCommand } from "./commands/context-loader.js";
|
|
6
7
|
import { runDsCommand } from "./commands/ds.js";
|
|
7
8
|
import { runTokenCommand } from "./commands/token.js";
|
|
@@ -32,6 +33,7 @@ Usage:
|
|
|
32
33
|
kweaver bkn create [options]
|
|
33
34
|
kweaver bkn update <kn-id> [options]
|
|
34
35
|
kweaver bkn delete <kn-id>
|
|
36
|
+
kweaver config [set-bd|show]
|
|
35
37
|
kweaver vega [health|stats|inspect|catalog|resource|connector-type]
|
|
36
38
|
kweaver context-loader [config|kn-search|...]
|
|
37
39
|
kweaver --help
|
|
@@ -43,6 +45,7 @@ Commands:
|
|
|
43
45
|
ds Manage datasources (list, get, delete, tables, connect)
|
|
44
46
|
agent Chat with a KWeaver agent (agent chat <id>), list published agents (agent list)
|
|
45
47
|
bkn Business knowledge network (list/get/create/update/delete/export/stats; object-type, subgraph, action-type, action-log)
|
|
48
|
+
config Per-platform configuration (business domain)
|
|
46
49
|
vega Vega observability platform (catalogs, resources, connector-types, health)
|
|
47
50
|
context-loader Call context-loader MCP (tools, resources, prompts; kn-search, query-*, etc.)
|
|
48
51
|
help Show this message`);
|
|
@@ -74,6 +77,9 @@ export async function run(argv) {
|
|
|
74
77
|
if (command === "vega") {
|
|
75
78
|
return runVegaCommand(rest);
|
|
76
79
|
}
|
|
80
|
+
if (command === "config") {
|
|
81
|
+
return runConfigCommand(rest);
|
|
82
|
+
}
|
|
77
83
|
if (command === "context-loader" || command === "context") {
|
|
78
84
|
return runContextLoaderCommand(rest);
|
|
79
85
|
}
|
package/dist/client.js
CHANGED
|
@@ -122,18 +122,7 @@ export class KWeaverClient {
|
|
|
122
122
|
try {
|
|
123
123
|
const probe = await fetch(`${token.baseUrl.replace(/\/+$/, "")}/api/ontology-manager/v1/knowledge-networks?limit=1`, { headers: { authorization: `Bearer ${token.accessToken}`, token: token.accessToken } });
|
|
124
124
|
if (probe.status === 401) {
|
|
125
|
-
|
|
126
|
-
token = await ensureValidToken({ forceRefresh: true });
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
throw new Error("Access token revoked and refresh token also expired. " +
|
|
130
|
-
"Run `kweaver auth login` to re-authenticate.");
|
|
131
|
-
}
|
|
132
|
-
return new KWeaverClient({
|
|
133
|
-
baseUrl: token.baseUrl,
|
|
134
|
-
accessToken: token.accessToken,
|
|
135
|
-
...opts,
|
|
136
|
-
});
|
|
125
|
+
throw new Error("Access token revoked. Run `kweaver auth login` to re-authenticate.");
|
|
137
126
|
}
|
|
138
127
|
}
|
|
139
128
|
catch {
|