@kweaver-ai/kweaver-sdk 0.4.2 → 0.4.4
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/vega.js +6 -3
- package/dist/auth/oauth.d.ts +6 -72
- package/dist/auth/oauth.js +92 -339
- 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 +68 -32
- package/dist/commands/auth.d.ts +0 -8
- package/dist/commands/auth.js +59 -80
- package/dist/commands/bkn.js +172 -75
- package/dist/commands/call.js +26 -9
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +52 -0
- package/dist/commands/context-loader.js +46 -40
- package/dist/commands/ds.js +29 -13
- package/dist/commands/vega.js +31 -10
- package/dist/config/store.d.ts +12 -25
- package/dist/config/store.js +43 -42
- package/dist/index.d.ts +1 -1
- package/package.json +9 -1
package/dist/api/vega.js
CHANGED
|
@@ -13,8 +13,11 @@ function buildHeaders(accessToken, businessDomain) {
|
|
|
13
13
|
export async function vegaHealth(options) {
|
|
14
14
|
const { baseUrl, accessToken, businessDomain = "bd_public", } = options;
|
|
15
15
|
const base = baseUrl.replace(/\/+$/, "");
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// Vega backend has no dedicated /health endpoint.
|
|
17
|
+
// Probe the catalogs list as a lightweight reachability check.
|
|
18
|
+
const url = new URL(`${base}${VEGA_BASE}/catalogs`);
|
|
19
|
+
url.searchParams.set("limit", "1");
|
|
20
|
+
const response = await fetch(url.toString(), {
|
|
18
21
|
method: "GET",
|
|
19
22
|
headers: buildHeaders(accessToken, businessDomain),
|
|
20
23
|
});
|
|
@@ -22,7 +25,7 @@ export async function vegaHealth(options) {
|
|
|
22
25
|
if (!response.ok) {
|
|
23
26
|
throw new HttpError(response.status, response.statusText, body);
|
|
24
27
|
}
|
|
25
|
-
return
|
|
28
|
+
return JSON.stringify({ status: "healthy", probe: "catalogs", statusCode: response.status });
|
|
26
29
|
}
|
|
27
30
|
export async function listVegaCatalogs(options) {
|
|
28
31
|
const { baseUrl, accessToken, status, limit, offset, businessDomain = "bd_public", } = options;
|
package/dist/auth/oauth.d.ts
CHANGED
|
@@ -1,77 +1,11 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
interface RegisterClientOptions {
|
|
3
|
-
baseUrl: string;
|
|
4
|
-
clientName: string;
|
|
5
|
-
redirectUri: string;
|
|
6
|
-
logoutRedirectUri: string;
|
|
7
|
-
lang?: string;
|
|
8
|
-
product?: string;
|
|
9
|
-
xForwardedPrefix?: string;
|
|
10
|
-
}
|
|
1
|
+
import { type TokenConfig } from "../config/store.js";
|
|
11
2
|
export declare function normalizeBaseUrl(value: string): string;
|
|
12
|
-
export declare function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
port: number;
|
|
17
|
-
clientName: string;
|
|
18
|
-
forceRegister: boolean;
|
|
19
|
-
host?: string;
|
|
20
|
-
redirectUriOverride?: string;
|
|
21
|
-
lang?: string;
|
|
22
|
-
product?: string;
|
|
23
|
-
xForwardedPrefix?: string;
|
|
24
|
-
}
|
|
25
|
-
export interface EnsuredClientConfig {
|
|
26
|
-
client: ClientConfig;
|
|
27
|
-
created: boolean;
|
|
28
|
-
}
|
|
29
|
-
export interface AuthRedirectConfig {
|
|
30
|
-
redirectUri: string;
|
|
31
|
-
logoutRedirectUri: string;
|
|
32
|
-
listenHost: string;
|
|
33
|
-
listenPort: number;
|
|
34
|
-
callbackPath: string;
|
|
35
|
-
}
|
|
36
|
-
export declare function buildAuthRedirectConfig(options: {
|
|
37
|
-
port: number;
|
|
38
|
-
host?: string;
|
|
39
|
-
redirectUriOverride?: string;
|
|
40
|
-
}): AuthRedirectConfig;
|
|
41
|
-
export declare function ensureClientConfig(options: EnsureClientOptions): Promise<EnsuredClientConfig>;
|
|
42
|
-
export declare function buildAuthorizationUrl(client: ClientConfig, state?: string): string;
|
|
43
|
-
/**
|
|
44
|
-
* Call the platform's end-session endpoint so the server invalidates the session.
|
|
45
|
-
* Best-effort: failures are ignored so local logout still proceeds.
|
|
46
|
-
*/
|
|
47
|
-
export declare function callLogoutEndpoint(client: ClientConfig, token: TokenConfig | null): Promise<void>;
|
|
48
|
-
export declare function refreshAccessToken(client: ClientConfig, refreshToken: string): Promise<TokenConfig>;
|
|
3
|
+
export declare function playwrightLogin(baseUrl: string, options?: {
|
|
4
|
+
username?: string;
|
|
5
|
+
password?: string;
|
|
6
|
+
}): Promise<TokenConfig>;
|
|
49
7
|
export declare function ensureValidToken(opts?: {
|
|
50
8
|
forceRefresh?: boolean;
|
|
51
9
|
}): Promise<TokenConfig>;
|
|
52
|
-
export
|
|
53
|
-
baseUrl: string;
|
|
54
|
-
port: number;
|
|
55
|
-
clientName: string;
|
|
56
|
-
open: boolean;
|
|
57
|
-
forceRegister: boolean;
|
|
58
|
-
host?: string;
|
|
59
|
-
redirectUriOverride?: string;
|
|
60
|
-
lang?: string;
|
|
61
|
-
product?: string;
|
|
62
|
-
xForwardedPrefix?: string;
|
|
63
|
-
}
|
|
64
|
-
export declare function login(options: AuthLoginOptions): Promise<{
|
|
65
|
-
client: ClientConfig;
|
|
66
|
-
token: TokenConfig;
|
|
67
|
-
authorizationUrl: string;
|
|
68
|
-
callback: CallbackSession;
|
|
69
|
-
created: boolean;
|
|
70
|
-
}>;
|
|
71
|
-
export declare function getStoredAuthSummary(baseUrl?: string): {
|
|
72
|
-
client: ClientConfig | null;
|
|
73
|
-
token: TokenConfig | null;
|
|
74
|
-
callback: CallbackSession | null;
|
|
75
|
-
};
|
|
10
|
+
export declare function withTokenRetry<T>(fn: (token: TokenConfig) => Promise<T>): Promise<T>;
|
|
76
11
|
export declare function formatHttpError(error: unknown): string;
|
|
77
|
-
export {};
|
package/dist/auth/oauth.js
CHANGED
|
@@ -1,276 +1,85 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import { openBrowser } from "../utils/browser.js";
|
|
5
|
-
import { fetchTextOrThrow, HttpError, NetworkRequestError } from "../utils/http.js";
|
|
6
|
-
import { getCurrentPlatform, loadCallbackSession, loadClientConfig, loadTokenConfig, saveCallbackSession, saveClientConfig, saveTokenConfig, setCurrentPlatform, } from "../config/store.js";
|
|
1
|
+
import { getCurrentPlatform, loadTokenConfig, saveTokenConfig, setCurrentPlatform, } from "../config/store.js";
|
|
2
|
+
import { HttpError, NetworkRequestError } from "../utils/http.js";
|
|
3
|
+
const TOKEN_TTL_SECONDS = 3600;
|
|
7
4
|
export function normalizeBaseUrl(value) {
|
|
8
5
|
return value.replace(/\/+$/, "");
|
|
9
6
|
}
|
|
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`;
|
|
81
|
-
}
|
|
82
|
-
return `${pathname.replace(/\/$/, "")}/successful-logout`;
|
|
83
|
-
}
|
|
84
|
-
function normalizeListenHost(host) {
|
|
85
|
-
return host?.trim() || "127.0.0.1";
|
|
86
|
-
}
|
|
87
|
-
export function buildAuthRedirectConfig(options) {
|
|
88
|
-
const listenHost = normalizeListenHost(options.host);
|
|
89
|
-
const listenPort = options.port;
|
|
90
|
-
if (options.redirectUriOverride) {
|
|
91
|
-
const redirect = new URL(options.redirectUriOverride);
|
|
92
|
-
const logout = new URL(options.redirectUriOverride);
|
|
93
|
-
logout.pathname = toSuccessfulLogoutPath(redirect.pathname);
|
|
94
|
-
logout.search = "";
|
|
95
|
-
logout.hash = "";
|
|
96
|
-
return {
|
|
97
|
-
redirectUri: redirect.toString(),
|
|
98
|
-
logoutRedirectUri: logout.toString(),
|
|
99
|
-
listenHost,
|
|
100
|
-
listenPort,
|
|
101
|
-
callbackPath: redirect.pathname,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
return {
|
|
105
|
-
redirectUri: `http://${listenHost}:${listenPort}/callback`,
|
|
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 };
|
|
7
|
+
export async function playwrightLogin(baseUrl, options) {
|
|
8
|
+
let chromium;
|
|
9
|
+
try {
|
|
10
|
+
const modName = "playwright";
|
|
11
|
+
const pw = await import(/* webpackIgnore: true */ modName);
|
|
12
|
+
chromium = pw.chromium;
|
|
128
13
|
}
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
}
|
|
143
|
-
export function buildAuthorizationUrl(client, state = randomValue(12)) {
|
|
144
|
-
const authorizationUrl = new URL(`${client.baseUrl}/oauth2/auth`);
|
|
145
|
-
authorizationUrl.searchParams.set("redirect_uri", client.redirectUri);
|
|
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);
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error("Playwright is not installed. Run:\n npm install playwright && npx playwright install chromium");
|
|
154
16
|
}
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
response.statusCode = 400;
|
|
183
|
-
response.end("State mismatch");
|
|
184
|
-
server.close();
|
|
185
|
-
reject(new Error("State mismatch in OAuth callback"));
|
|
186
|
-
return;
|
|
17
|
+
const hasCredentials = options?.username && options?.password;
|
|
18
|
+
const browser = await chromium.launch({ headless: hasCredentials ? true : false });
|
|
19
|
+
try {
|
|
20
|
+
const context = await browser.newContext();
|
|
21
|
+
const page = await context.newPage();
|
|
22
|
+
await page.goto(`${baseUrl}/api/dip-hub/v1/login`, {
|
|
23
|
+
waitUntil: "networkidle",
|
|
24
|
+
timeout: 30_000,
|
|
25
|
+
});
|
|
26
|
+
if (hasCredentials) {
|
|
27
|
+
// Headless mode: auto-fill credentials
|
|
28
|
+
await page.waitForSelector('input[name="account"]', { timeout: 10_000 });
|
|
29
|
+
await page.fill('input[name="account"]', options.username);
|
|
30
|
+
await page.fill('input[name="password"]', options.password);
|
|
31
|
+
await page.click("button.ant-btn-primary");
|
|
32
|
+
}
|
|
33
|
+
// else: headed mode — user logs in manually in the browser window
|
|
34
|
+
const TIMEOUT_SECONDS = hasCredentials ? 30 : 120;
|
|
35
|
+
let accessToken = null;
|
|
36
|
+
for (let i = 0; i < TIMEOUT_SECONDS; i++) {
|
|
37
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
38
|
+
// Check cookies (works even after navigation)
|
|
39
|
+
for (const cookie of await context.cookies()) {
|
|
40
|
+
if (cookie.name === "dip.oauth2_token") {
|
|
41
|
+
accessToken = decodeURIComponent(cookie.value);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
187
44
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
45
|
+
if (accessToken)
|
|
46
|
+
break;
|
|
47
|
+
// In headless mode, check for login error messages
|
|
48
|
+
if (hasCredentials) {
|
|
49
|
+
try {
|
|
50
|
+
const errorEl = await page.$(".ant-message-error, .ant-alert-error");
|
|
51
|
+
if (errorEl) {
|
|
52
|
+
const errorText = await errorEl.textContent();
|
|
53
|
+
throw new Error(`Login failed: ${errorText?.trim() || "unknown error"}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
if (e instanceof Error && e.message.startsWith("Login failed:"))
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
195
60
|
}
|
|
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;
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* Call the platform's end-session endpoint so the server invalidates the session.
|
|
231
|
-
* Best-effort: failures are ignored so local logout still proceeds.
|
|
232
|
-
*/
|
|
233
|
-
export async function callLogoutEndpoint(client, token) {
|
|
234
|
-
const url = new URL(`${client.baseUrl}/oauth2/signout`);
|
|
235
|
-
if (token?.idToken) {
|
|
236
|
-
url.searchParams.set("id_token_hint", token.idToken);
|
|
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
61
|
}
|
|
62
|
+
if (!accessToken) {
|
|
63
|
+
throw new Error(`Login timed out: dip.oauth2_token cookie not received within ${TIMEOUT_SECONDS} seconds.`);
|
|
64
|
+
}
|
|
65
|
+
const now = new Date();
|
|
66
|
+
const tokenConfig = {
|
|
67
|
+
baseUrl,
|
|
68
|
+
accessToken,
|
|
69
|
+
tokenType: "bearer",
|
|
70
|
+
scope: "",
|
|
71
|
+
expiresIn: TOKEN_TTL_SECONDS,
|
|
72
|
+
expiresAt: new Date(now.getTime() + TOKEN_TTL_SECONDS * 1000).toISOString(),
|
|
73
|
+
obtainedAt: now.toISOString(),
|
|
74
|
+
};
|
|
75
|
+
saveTokenConfig(tokenConfig);
|
|
76
|
+
setCurrentPlatform(baseUrl);
|
|
77
|
+
return tokenConfig;
|
|
246
78
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
console.error(`Logout endpoint request failed: ${msg}`);
|
|
79
|
+
finally {
|
|
80
|
+
await browser.close();
|
|
250
81
|
}
|
|
251
82
|
}
|
|
252
|
-
export async function refreshAccessToken(client, refreshToken) {
|
|
253
|
-
const payload = new URLSearchParams({
|
|
254
|
-
grant_type: "refresh_token",
|
|
255
|
-
refresh_token: refreshToken,
|
|
256
|
-
});
|
|
257
|
-
const { body } = await fetchTextOrThrow(`${client.baseUrl}/oauth2/token`, {
|
|
258
|
-
method: "POST",
|
|
259
|
-
headers: {
|
|
260
|
-
"content-type": "application/x-www-form-urlencoded",
|
|
261
|
-
accept: "application/json",
|
|
262
|
-
authorization: toBasicAuth(client.clientId, client.clientSecret),
|
|
263
|
-
},
|
|
264
|
-
body: payload.toString(),
|
|
265
|
-
});
|
|
266
|
-
const parsed = JSON.parse(body);
|
|
267
|
-
const tokenConfig = buildTokenConfig(client.baseUrl, {
|
|
268
|
-
...parsed,
|
|
269
|
-
refresh_token: parsed.refresh_token ?? refreshToken,
|
|
270
|
-
});
|
|
271
|
-
saveTokenConfig(tokenConfig);
|
|
272
|
-
return tokenConfig;
|
|
273
|
-
}
|
|
274
83
|
export async function ensureValidToken(opts) {
|
|
275
84
|
const envToken = process.env.KWEAVER_TOKEN;
|
|
276
85
|
const envBaseUrl = process.env.KWEAVER_BASE_URL;
|
|
@@ -280,98 +89,42 @@ export async function ensureValidToken(opts) {
|
|
|
280
89
|
baseUrl: normalizeBaseUrl(envBaseUrl),
|
|
281
90
|
accessToken: rawToken,
|
|
282
91
|
tokenType: "bearer",
|
|
283
|
-
scope: "
|
|
92
|
+
scope: "",
|
|
284
93
|
obtainedAt: new Date().toISOString(),
|
|
285
94
|
};
|
|
286
95
|
}
|
|
287
96
|
const currentPlatform = getCurrentPlatform();
|
|
288
97
|
if (!currentPlatform) {
|
|
289
|
-
throw new Error("No active platform selected. Run `kweaver auth <platform-url>` first.");
|
|
98
|
+
throw new Error("No active platform selected. Run `kweaver auth login <platform-url>` first.");
|
|
99
|
+
}
|
|
100
|
+
if (opts?.forceRefresh) {
|
|
101
|
+
throw new Error(`Token refresh is not supported. Run \`kweaver auth login ${currentPlatform}\` again.`);
|
|
290
102
|
}
|
|
291
|
-
const client = loadClientConfig(currentPlatform);
|
|
292
103
|
const token = loadTokenConfig(currentPlatform);
|
|
293
|
-
if (!
|
|
294
|
-
throw new Error(`
|
|
104
|
+
if (!token) {
|
|
105
|
+
throw new Error(`No saved token for ${currentPlatform}. Run \`kweaver auth login ${currentPlatform}\` first.`);
|
|
295
106
|
}
|
|
296
|
-
if (
|
|
297
|
-
if (!token.expiresAt) {
|
|
298
|
-
return token;
|
|
299
|
-
}
|
|
107
|
+
if (token.expiresAt) {
|
|
300
108
|
const expiresAtMs = Date.parse(token.expiresAt);
|
|
301
|
-
if (Number.isNaN(expiresAtMs)
|
|
302
|
-
|
|
109
|
+
if (!Number.isNaN(expiresAtMs) && expiresAtMs - 60_000 <= Date.now()) {
|
|
110
|
+
throw new Error(`Access token expired. Run \`kweaver auth login ${currentPlatform}\` again.`);
|
|
303
111
|
}
|
|
304
112
|
}
|
|
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);
|
|
113
|
+
return token;
|
|
309
114
|
}
|
|
310
|
-
export async function
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
redirectUriOverride: options.redirectUriOverride,
|
|
315
|
-
});
|
|
316
|
-
const { client, created } = await ensureClientConfig({
|
|
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
|
-
}
|
|
115
|
+
export async function withTokenRetry(fn) {
|
|
116
|
+
const token = await ensureValidToken();
|
|
117
|
+
try {
|
|
118
|
+
return await fn(token);
|
|
341
119
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
console.log(`Waiting for OAuth callback on http://${redirect.listenHost}:${redirect.listenPort}${redirect.callbackPath}`);
|
|
348
|
-
if (redirect.listenHost === "127.0.0.1" || redirect.listenHost === "localhost") {
|
|
349
|
-
console.log("");
|
|
350
|
-
console.log("If your browser is on another machine, use SSH port forwarding first:");
|
|
351
|
-
console.log(`ssh -L ${redirect.listenPort}:127.0.0.1:${redirect.listenPort} user@server`);
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error instanceof HttpError && error.status === 401) {
|
|
122
|
+
const platform = token.baseUrl;
|
|
123
|
+
throw new Error(`Authentication failed (401). Token may be expired or revoked.\n` +
|
|
124
|
+
`Run \`kweaver auth login ${platform}\` again.`);
|
|
352
125
|
}
|
|
126
|
+
throw error;
|
|
353
127
|
}
|
|
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
|
-
}
|
|
368
|
-
export function getStoredAuthSummary(baseUrl) {
|
|
369
|
-
const targetBaseUrl = baseUrl ?? getCurrentPlatform() ?? undefined;
|
|
370
|
-
return {
|
|
371
|
-
client: loadClientConfig(targetBaseUrl),
|
|
372
|
-
token: loadTokenConfig(targetBaseUrl),
|
|
373
|
-
callback: loadCallbackSession(targetBaseUrl),
|
|
374
|
-
};
|
|
375
128
|
}
|
|
376
129
|
function formatOAuthErrorBody(body) {
|
|
377
130
|
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 {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ensureValidToken, formatHttpError } from "../auth/oauth.js";
|
|
2
2
|
import { fetchAgentInfo, sendChatRequest } from "../api/agent-chat.js";
|
|
3
|
+
import { resolveBusinessDomain } from "../config/store.js";
|
|
3
4
|
function formatCliArg(value) {
|
|
4
5
|
if (/^[A-Za-z0-9_./:=+-]+$/.test(value)) {
|
|
5
6
|
return value;
|
|
@@ -42,7 +43,7 @@ export function parseChatArgs(args) {
|
|
|
42
43
|
let conversationId;
|
|
43
44
|
let stream;
|
|
44
45
|
let verbose = false;
|
|
45
|
-
let businessDomain = "
|
|
46
|
+
let businessDomain = "";
|
|
46
47
|
for (let i = 0; i < args.length; i += 1) {
|
|
47
48
|
const arg = args[i];
|
|
48
49
|
if (arg === "-m" || arg === "--message") {
|
|
@@ -104,6 +105,8 @@ export function parseChatArgs(args) {
|
|
|
104
105
|
if (!agentId) {
|
|
105
106
|
throw new Error("Missing agent_id. Usage: kweaver agent chat <agent_id> [-m \"message\"]");
|
|
106
107
|
}
|
|
108
|
+
if (!businessDomain)
|
|
109
|
+
businessDomain = resolveBusinessDomain();
|
|
107
110
|
return { agentId, version, message, conversationId, stream, verbose, businessDomain };
|
|
108
111
|
}
|
|
109
112
|
export async function runAgentChatCommand(args) {
|