@kweaver-ai/kweaver-sdk 0.4.10 → 0.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -1
- package/README.zh.md +28 -1
- package/dist/api/dataflow.d.ts +78 -0
- package/dist/api/dataflow.js +135 -0
- package/dist/api/dataviews.js +49 -0
- package/dist/auth/oauth.d.ts +6 -1
- package/dist/auth/oauth.js +240 -166
- package/dist/cli.js +3 -1
- package/dist/client.js +2 -0
- package/dist/commands/auth.js +36 -16
- package/dist/commands/bkn.js +203 -18
- package/dist/commands/ds.d.ts +16 -0
- package/dist/commands/ds.js +204 -1
- package/dist/commands/import-csv.d.ts +47 -0
- package/dist/commands/import-csv.js +111 -0
- package/dist/config/store.d.ts +2 -0
- package/dist/config/tls-env.d.ts +8 -0
- package/dist/config/tls-env.js +22 -0
- package/package.json +2 -1
package/dist/auth/oauth.js
CHANGED
|
@@ -8,82 +8,137 @@ const DEFAULT_SCOPE = "openid offline all";
|
|
|
8
8
|
export function normalizeBaseUrl(value) {
|
|
9
9
|
return value.replace(/\/+$/, "");
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Temporarily disable TLS certificate verification for Node `fetch` (sets
|
|
13
|
+
* NODE_TLS_REJECT_UNAUTHORIZED). Used for `--insecure` login and token refresh.
|
|
14
|
+
*/
|
|
15
|
+
async function runWithTlsInsecure(tlsInsecure, fn) {
|
|
16
|
+
if (!tlsInsecure) {
|
|
17
|
+
return fn();
|
|
18
|
+
}
|
|
19
|
+
const prev = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
|
|
20
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
21
|
+
try {
|
|
22
|
+
return await fn();
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
if (prev === undefined) {
|
|
26
|
+
delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = prev;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Generate a PKCE code_verifier and code_challenge (S256). */
|
|
34
|
+
async function generatePkce() {
|
|
35
|
+
const { randomBytes, createHash } = await import("node:crypto");
|
|
36
|
+
const verifier = randomBytes(48).toString("base64url");
|
|
37
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
38
|
+
return { verifier, challenge };
|
|
39
|
+
}
|
|
11
40
|
/**
|
|
12
41
|
* OAuth2 Authorization Code login flow.
|
|
13
|
-
* 1. Register client (if not already registered)
|
|
42
|
+
* 1. Register client (if not already registered), OR use a provided client ID
|
|
14
43
|
* 2. Open browser to /oauth2/auth
|
|
15
44
|
* 3. Receive authorization code via local HTTP callback
|
|
16
45
|
* 4. Exchange code for access_token + refresh_token
|
|
17
46
|
* 5. Save token.json + client.json to ~/.kweaver/
|
|
18
47
|
*/
|
|
19
48
|
export async function oauth2Login(baseUrl, options) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
return runWithTlsInsecure(options?.tlsInsecure, async () => {
|
|
50
|
+
const { createServer } = await import("node:http");
|
|
51
|
+
const { randomBytes } = await import("node:crypto");
|
|
52
|
+
const base = normalizeBaseUrl(baseUrl);
|
|
53
|
+
const port = options?.port ?? DEFAULT_REDIRECT_PORT;
|
|
54
|
+
const scope = options?.scope ?? DEFAULT_SCOPE;
|
|
55
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
56
|
+
// Step 1: Determine client — use provided client ID or fall back to dynamic registration
|
|
57
|
+
let client = loadClientConfig(base);
|
|
58
|
+
if (options?.clientId) {
|
|
59
|
+
// Use the platform's existing client (e.g. the web app client).
|
|
60
|
+
// Persist it so future logins reuse it without re-registering.
|
|
61
|
+
client = {
|
|
62
|
+
baseUrl: base,
|
|
63
|
+
clientId: options.clientId,
|
|
64
|
+
clientSecret: options.clientSecret ?? "",
|
|
65
|
+
redirectUri,
|
|
66
|
+
logoutRedirectUri: redirectUri.replace("/callback", "/successful-logout"),
|
|
67
|
+
scope,
|
|
68
|
+
lang: "zh-cn",
|
|
69
|
+
product: "adp",
|
|
70
|
+
xForwardedPrefix: "",
|
|
71
|
+
};
|
|
72
|
+
saveClientConfig(base, client);
|
|
73
|
+
}
|
|
74
|
+
else if (!client?.clientId) {
|
|
75
|
+
client = await registerOAuth2Client(base, redirectUri, scope);
|
|
76
|
+
saveClientConfig(base, client);
|
|
77
|
+
}
|
|
78
|
+
// Use PKCE when no client secret is available (public client / platform client).
|
|
79
|
+
const usePkce = !client.clientSecret;
|
|
80
|
+
const pkce = usePkce ? await generatePkce() : null;
|
|
81
|
+
// Step 2: Generate CSRF state
|
|
82
|
+
const state = randomBytes(12).toString("hex");
|
|
83
|
+
// Step 3: Build authorization URL
|
|
84
|
+
const authParams = new URLSearchParams({
|
|
85
|
+
redirect_uri: redirectUri,
|
|
86
|
+
"x-forwarded-prefix": "",
|
|
87
|
+
client_id: client.clientId,
|
|
88
|
+
scope,
|
|
89
|
+
response_type: "code",
|
|
90
|
+
state,
|
|
91
|
+
lang: "zh-cn",
|
|
92
|
+
product: "adp",
|
|
93
|
+
});
|
|
94
|
+
if (pkce) {
|
|
95
|
+
authParams.set("code_challenge", pkce.challenge);
|
|
96
|
+
authParams.set("code_challenge_method", "S256");
|
|
97
|
+
}
|
|
98
|
+
const authUrl = `${base}/oauth2/auth?${authParams.toString()}`;
|
|
99
|
+
// Step 4: Start local callback server, wait for code
|
|
100
|
+
const code = await new Promise((resolve, reject) => {
|
|
101
|
+
const timeoutId = setTimeout(() => {
|
|
60
102
|
server.close();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
103
|
+
reject(new Error("OAuth2 login timed out (120s). No authorization code received."));
|
|
104
|
+
}, 120_000);
|
|
105
|
+
const server = createServer((req, res) => {
|
|
106
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
107
|
+
if (url.pathname === "/callback") {
|
|
108
|
+
const receivedState = url.searchParams.get("state");
|
|
109
|
+
const receivedCode = url.searchParams.get("code");
|
|
110
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
111
|
+
res.end("<html><body><h2>Login successful. You can close this tab.</h2></body></html>");
|
|
112
|
+
clearTimeout(timeoutId);
|
|
113
|
+
server.close();
|
|
114
|
+
if (receivedState !== state) {
|
|
115
|
+
reject(new Error("OAuth2 state mismatch — possible CSRF attack."));
|
|
116
|
+
}
|
|
117
|
+
else if (!receivedCode) {
|
|
118
|
+
reject(new Error("No authorization code received in callback."));
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
resolve(receivedCode);
|
|
122
|
+
}
|
|
66
123
|
}
|
|
67
124
|
else {
|
|
68
|
-
|
|
125
|
+
res.writeHead(404);
|
|
126
|
+
res.end();
|
|
69
127
|
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// Step 5: Open browser (uses spawn with proper Windows quoting)
|
|
78
|
-
import("../utils/browser.js").then(({ openBrowser }) => {
|
|
79
|
-
openBrowser(authUrl);
|
|
128
|
+
});
|
|
129
|
+
server.listen(port, "127.0.0.1", () => {
|
|
130
|
+
// Step 5: Open browser (uses spawn with proper Windows quoting)
|
|
131
|
+
import("../utils/browser.js").then(({ openBrowser }) => {
|
|
132
|
+
openBrowser(authUrl);
|
|
133
|
+
});
|
|
134
|
+
process.stderr.write(`If the wrong browser opens, copy this URL to your correct browser:\n ${authUrl}\n`);
|
|
80
135
|
});
|
|
81
136
|
});
|
|
137
|
+
// Step 6: Exchange code for tokens
|
|
138
|
+
const token = await exchangeCodeForToken(base, code, client.clientId, client.clientSecret, redirectUri, pkce?.verifier, options?.tlsInsecure);
|
|
139
|
+
setCurrentPlatform(base);
|
|
140
|
+
return token;
|
|
82
141
|
});
|
|
83
|
-
// Step 6: Exchange code for tokens
|
|
84
|
-
const token = await exchangeCodeForToken(base, code, client.clientId, client.clientSecret, redirectUri);
|
|
85
|
-
setCurrentPlatform(base);
|
|
86
|
-
return token;
|
|
87
142
|
}
|
|
88
143
|
async function registerOAuth2Client(baseUrl, redirectUri, scope) {
|
|
89
144
|
const logoutUri = redirectUri.replace("/callback", "/successful-logout");
|
|
@@ -123,20 +178,31 @@ async function registerOAuth2Client(baseUrl, redirectUri, scope) {
|
|
|
123
178
|
xForwardedPrefix: "",
|
|
124
179
|
};
|
|
125
180
|
}
|
|
126
|
-
async function exchangeCodeForToken(baseUrl, code, clientId, clientSecret, redirectUri) {
|
|
127
|
-
const
|
|
181
|
+
async function exchangeCodeForToken(baseUrl, code, clientId, clientSecret, redirectUri, codeVerifier, tlsInsecure) {
|
|
182
|
+
const params = {
|
|
183
|
+
grant_type: "authorization_code",
|
|
184
|
+
code,
|
|
185
|
+
redirect_uri: redirectUri,
|
|
186
|
+
};
|
|
187
|
+
if (codeVerifier) {
|
|
188
|
+
params.code_verifier = codeVerifier;
|
|
189
|
+
}
|
|
190
|
+
const headers = {
|
|
191
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
192
|
+
Accept: "application/json",
|
|
193
|
+
};
|
|
194
|
+
if (clientSecret) {
|
|
195
|
+
// Confidential client: use HTTP Basic auth
|
|
196
|
+
headers.Authorization = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`;
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// Public client (PKCE): send client_id in body
|
|
200
|
+
params.client_id = clientId;
|
|
201
|
+
}
|
|
128
202
|
const response = await fetch(`${baseUrl}/oauth2/token`, {
|
|
129
203
|
method: "POST",
|
|
130
|
-
headers
|
|
131
|
-
|
|
132
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
133
|
-
Accept: "application/json",
|
|
134
|
-
},
|
|
135
|
-
body: new URLSearchParams({
|
|
136
|
-
grant_type: "authorization_code",
|
|
137
|
-
code,
|
|
138
|
-
redirect_uri: redirectUri,
|
|
139
|
-
}).toString(),
|
|
204
|
+
headers,
|
|
205
|
+
body: new URLSearchParams(params).toString(),
|
|
140
206
|
});
|
|
141
207
|
const text = await response.text();
|
|
142
208
|
if (!response.ok) {
|
|
@@ -155,6 +221,7 @@ async function exchangeCodeForToken(baseUrl, code, clientId, clientSecret, redir
|
|
|
155
221
|
refreshToken: data.refresh_token ?? "",
|
|
156
222
|
idToken: data.id_token ?? "",
|
|
157
223
|
obtainedAt: now.toISOString(),
|
|
224
|
+
...(tlsInsecure ? { tlsInsecure: true } : {}),
|
|
158
225
|
};
|
|
159
226
|
saveTokenConfig(token);
|
|
160
227
|
return token;
|
|
@@ -171,105 +238,107 @@ async function exchangeCodeForToken(baseUrl, code, clientId, clientSecret, redir
|
|
|
171
238
|
* window for manual login (same UX as the old cookie-based flow).
|
|
172
239
|
*/
|
|
173
240
|
export async function playwrightLogin(baseUrl, options) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
browser?.close();
|
|
216
|
-
reject(new Error(`OAuth2 login timed out (${TIMEOUT_MS / 1000}s). No authorization code received.`));
|
|
217
|
-
}, TIMEOUT_MS);
|
|
218
|
-
const server = createServer((req, res) => {
|
|
219
|
-
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
220
|
-
if (url.pathname === "/callback") {
|
|
221
|
-
const receivedState = url.searchParams.get("state");
|
|
222
|
-
const receivedCode = url.searchParams.get("code");
|
|
223
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
224
|
-
res.end("<html><body><h2>Login successful. You can close this tab.</h2></body></html>");
|
|
225
|
-
clearTimeout(timeoutId);
|
|
241
|
+
return runWithTlsInsecure(options?.tlsInsecure, async () => {
|
|
242
|
+
const { createServer } = await import("node:http");
|
|
243
|
+
const { randomBytes } = await import("node:crypto");
|
|
244
|
+
let chromium;
|
|
245
|
+
try {
|
|
246
|
+
const modName = "playwright";
|
|
247
|
+
const pw = await import(/* webpackIgnore: true */ modName);
|
|
248
|
+
chromium = pw.chromium;
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
throw new Error("Playwright is not installed. Run:\n npm install playwright && npx playwright install chromium");
|
|
252
|
+
}
|
|
253
|
+
const base = normalizeBaseUrl(baseUrl);
|
|
254
|
+
const port = options?.port ?? DEFAULT_REDIRECT_PORT;
|
|
255
|
+
const scope = options?.scope ?? DEFAULT_SCOPE;
|
|
256
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
257
|
+
const hasCredentials = !!(options?.username && options?.password);
|
|
258
|
+
// Step 1: Ensure registered OAuth2 client
|
|
259
|
+
let client = loadClientConfig(base);
|
|
260
|
+
if (!client?.clientId) {
|
|
261
|
+
client = await registerOAuth2Client(base, redirectUri, scope);
|
|
262
|
+
saveClientConfig(base, client);
|
|
263
|
+
}
|
|
264
|
+
// Step 2: Generate CSRF state
|
|
265
|
+
const state = randomBytes(12).toString("hex");
|
|
266
|
+
// Step 3: Build authorization URL
|
|
267
|
+
const authParams = new URLSearchParams({
|
|
268
|
+
redirect_uri: redirectUri,
|
|
269
|
+
"x-forwarded-prefix": "",
|
|
270
|
+
client_id: client.clientId,
|
|
271
|
+
scope,
|
|
272
|
+
response_type: "code",
|
|
273
|
+
state,
|
|
274
|
+
lang: "zh-cn",
|
|
275
|
+
product: "adp",
|
|
276
|
+
});
|
|
277
|
+
const authUrl = `${base}/oauth2/auth?${authParams.toString()}`;
|
|
278
|
+
// Step 4: Start local callback server to capture the authorization code
|
|
279
|
+
const code = await new Promise((resolve, reject) => {
|
|
280
|
+
const TIMEOUT_MS = hasCredentials ? 30_000 : 120_000;
|
|
281
|
+
const timeoutId = setTimeout(() => {
|
|
226
282
|
server.close();
|
|
227
283
|
browser?.close();
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
284
|
+
reject(new Error(`OAuth2 login timed out (${TIMEOUT_MS / 1000}s). No authorization code received.`));
|
|
285
|
+
}, TIMEOUT_MS);
|
|
286
|
+
const server = createServer((req, res) => {
|
|
287
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
288
|
+
if (url.pathname === "/callback") {
|
|
289
|
+
const receivedState = url.searchParams.get("state");
|
|
290
|
+
const receivedCode = url.searchParams.get("code");
|
|
291
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
292
|
+
res.end("<html><body><h2>Login successful. You can close this tab.</h2></body></html>");
|
|
293
|
+
clearTimeout(timeoutId);
|
|
294
|
+
server.close();
|
|
295
|
+
browser?.close();
|
|
296
|
+
if (receivedState !== state) {
|
|
297
|
+
reject(new Error("OAuth2 state mismatch — possible CSRF attack."));
|
|
298
|
+
}
|
|
299
|
+
else if (!receivedCode) {
|
|
300
|
+
reject(new Error("No authorization code received in callback."));
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
resolve(receivedCode);
|
|
304
|
+
}
|
|
233
305
|
}
|
|
234
306
|
else {
|
|
235
|
-
|
|
307
|
+
res.writeHead(404);
|
|
308
|
+
res.end();
|
|
236
309
|
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
await page.fill('input[name="password"]', options.password);
|
|
256
|
-
await page.click("button.ant-btn-primary");
|
|
310
|
+
});
|
|
311
|
+
let browser;
|
|
312
|
+
server.listen(port, "127.0.0.1", async () => {
|
|
313
|
+
try {
|
|
314
|
+
browser = await chromium.launch({ headless: hasCredentials });
|
|
315
|
+
const context = await browser.newContext({ ignoreHTTPSErrors: !!options?.tlsInsecure });
|
|
316
|
+
const page = await context.newPage();
|
|
317
|
+
// Navigate to OAuth2 auth URL — redirects to signin page
|
|
318
|
+
await page.goto(authUrl, { waitUntil: "networkidle", timeout: 30_000 });
|
|
319
|
+
if (hasCredentials) {
|
|
320
|
+
// Auto-fill credentials
|
|
321
|
+
await page.waitForSelector('input[name="account"]', { timeout: 10_000 });
|
|
322
|
+
await page.fill('input[name="account"]', options.username);
|
|
323
|
+
await page.fill('input[name="password"]', options.password);
|
|
324
|
+
await page.click("button.ant-btn-primary");
|
|
325
|
+
}
|
|
326
|
+
// else: visible browser — user logs in manually
|
|
327
|
+
// The OAuth2 callback will fire when login completes, resolving the promise above
|
|
257
328
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
reject(err);
|
|
266
|
-
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
clearTimeout(timeoutId);
|
|
331
|
+
server.close();
|
|
332
|
+
browser?.close();
|
|
333
|
+
reject(err);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
267
336
|
});
|
|
337
|
+
// Step 5: Exchange authorization code for tokens (includes refresh_token)
|
|
338
|
+
const token = await exchangeCodeForToken(base, code, client.clientId, client.clientSecret, redirectUri, undefined, options?.tlsInsecure);
|
|
339
|
+
setCurrentPlatform(base);
|
|
340
|
+
return token;
|
|
268
341
|
});
|
|
269
|
-
// Step 5: Exchange authorization code for tokens (includes refresh_token)
|
|
270
|
-
const token = await exchangeCodeForToken(base, code, client.clientId, client.clientSecret, redirectUri);
|
|
271
|
-
setCurrentPlatform(base);
|
|
272
|
-
return token;
|
|
273
342
|
}
|
|
274
343
|
function tokenNeedsRefresh(token) {
|
|
275
344
|
if (!token.expiresAt) {
|
|
@@ -306,7 +375,7 @@ export async function refreshAccessToken(token) {
|
|
|
306
375
|
});
|
|
307
376
|
let response;
|
|
308
377
|
try {
|
|
309
|
-
response = await fetch(url, {
|
|
378
|
+
response = await runWithTlsInsecure(token.tlsInsecure, () => fetch(url, {
|
|
310
379
|
method: "POST",
|
|
311
380
|
headers: {
|
|
312
381
|
Authorization: `Basic ${credentials}`,
|
|
@@ -314,7 +383,7 @@ export async function refreshAccessToken(token) {
|
|
|
314
383
|
Accept: "application/json",
|
|
315
384
|
},
|
|
316
385
|
body: body.toString(),
|
|
317
|
-
});
|
|
386
|
+
}));
|
|
318
387
|
}
|
|
319
388
|
catch (cause) {
|
|
320
389
|
const hint = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -346,6 +415,7 @@ export async function refreshAccessToken(token) {
|
|
|
346
415
|
refreshToken: data.refresh_token ?? refreshToken,
|
|
347
416
|
idToken: data.id_token ?? token.idToken ?? "",
|
|
348
417
|
obtainedAt: now.toISOString(),
|
|
418
|
+
...(token.tlsInsecure ? { tlsInsecure: true } : {}),
|
|
349
419
|
};
|
|
350
420
|
saveTokenConfig(newToken);
|
|
351
421
|
return newToken;
|
|
@@ -486,6 +556,10 @@ export function formatHttpError(error) {
|
|
|
486
556
|
].join("\n").trim();
|
|
487
557
|
}
|
|
488
558
|
if (error instanceof Error) {
|
|
559
|
+
const cause = "cause" in error && error.cause instanceof Error ? error.cause.message : "";
|
|
560
|
+
if (cause && error.message === "fetch failed") {
|
|
561
|
+
return `${error.message}: ${cause}\nHint: use --insecure (-k) to skip TLS verification for self-signed certificates.`;
|
|
562
|
+
}
|
|
489
563
|
return error.message;
|
|
490
564
|
}
|
|
491
565
|
return String(error);
|
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { applyTlsEnvFromSavedTokens } from "./config/tls-env.js";
|
|
1
2
|
import { runAgentCommand } from "./commands/agent.js";
|
|
2
3
|
import { runAuthCommand } from "./commands/auth.js";
|
|
3
4
|
import { runKnCommand } from "./commands/bkn.js";
|
|
@@ -14,7 +15,7 @@ Usage:
|
|
|
14
15
|
kweaver --version | -V
|
|
15
16
|
kweaver --help | -h
|
|
16
17
|
|
|
17
|
-
kweaver auth <platform-url> [--alias name] [-u user] [-p pass] [--playwright]
|
|
18
|
+
kweaver auth <platform-url> [--alias name] [-u user] [-p pass] [--playwright] [--insecure|-k]
|
|
18
19
|
kweaver auth login <platform-url> (alias for auth <url>)
|
|
19
20
|
kweaver auth status [platform-url|alias]
|
|
20
21
|
kweaver auth list
|
|
@@ -97,6 +98,7 @@ Commands:
|
|
|
97
98
|
help Show this message`);
|
|
98
99
|
}
|
|
99
100
|
export async function run(argv) {
|
|
101
|
+
applyTlsEnvFromSavedTokens();
|
|
100
102
|
const [command, ...rest] = argv;
|
|
101
103
|
if (command === "--version" || command === "-V" || command === "version") {
|
|
102
104
|
const { createRequire } = await import("node:module");
|
package/dist/client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { applyTlsEnvFromSavedTokens } from "./config/tls-env.js";
|
|
1
2
|
import { getCurrentPlatform, loadTokenConfig, } from "./config/store.js";
|
|
2
3
|
import { ensureValidToken } from "./auth/oauth.js";
|
|
3
4
|
import { AgentsResource } from "./resources/agents.js";
|
|
@@ -111,6 +112,7 @@ export class KWeaverClient {
|
|
|
111
112
|
* ```
|
|
112
113
|
*/
|
|
113
114
|
static async connect(opts = {}) {
|
|
115
|
+
applyTlsEnvFromSavedTokens();
|
|
114
116
|
// Try with current token first
|
|
115
117
|
let token = await ensureValidToken();
|
|
116
118
|
const client = new KWeaverClient({
|
package/dist/commands/auth.js
CHANGED
|
@@ -4,19 +4,24 @@ export async function runAuthCommand(args) {
|
|
|
4
4
|
const target = args[0];
|
|
5
5
|
const rest = args.slice(1);
|
|
6
6
|
if (!target || target === "--help" || target === "-h") {
|
|
7
|
-
console.log(`kweaver auth login <url> [
|
|
8
|
-
kweaver auth <url>
|
|
9
|
-
kweaver auth status [url|alias]
|
|
10
|
-
kweaver auth list
|
|
11
|
-
kweaver auth use <url|alias>
|
|
12
|
-
kweaver auth logout [url|alias]
|
|
13
|
-
kweaver auth delete <url|alias>
|
|
7
|
+
console.log(`kweaver auth login <url> [options] Login to a platform (browser OAuth2 by default)
|
|
8
|
+
kweaver auth <url> Login (shorthand; same options as login)
|
|
9
|
+
kweaver auth status [url|alias] Show current auth status
|
|
10
|
+
kweaver auth list List saved platforms
|
|
11
|
+
kweaver auth use <url|alias> Switch active platform
|
|
12
|
+
kweaver auth logout [url|alias] Logout (clear local token)
|
|
13
|
+
kweaver auth delete <url|alias> Delete saved credentials
|
|
14
14
|
|
|
15
|
-
Login options
|
|
16
|
-
--alias <name>
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
Login options:
|
|
16
|
+
--alias <name> Save platform with a short alias (use with use / status / logout)
|
|
17
|
+
--client-id <id> Use an existing OAuth2 client ID instead of registering a new one.
|
|
18
|
+
Use the platform's web app client ID to get the same permissions
|
|
19
|
+
as the browser. Find it in DevTools: /oauth2/auth?client_id=<id>
|
|
20
|
+
--client-secret <s> Client secret (omit for public/PKCE clients)
|
|
21
|
+
-u, --username Username (with -p triggers Playwright headless login)
|
|
22
|
+
-p, --password Password
|
|
23
|
+
--playwright Force Playwright browser login even without -u/-p
|
|
24
|
+
--insecure, -k Skip TLS certificate verification (self-signed / dev HTTPS only)`);
|
|
20
25
|
return 0;
|
|
21
26
|
}
|
|
22
27
|
if (target === "login") {
|
|
@@ -38,21 +43,33 @@ Login options (browser OAuth2 by default; use -u/-p for headless Playwright):
|
|
|
38
43
|
const username = readOption(args, "--username") ?? readOption(args, "-u");
|
|
39
44
|
const password = readOption(args, "--password") ?? readOption(args, "-p");
|
|
40
45
|
const usePlaywright = args.includes("--playwright");
|
|
46
|
+
const clientId = readOption(args, "--client-id");
|
|
47
|
+
const clientSecret = readOption(args, "--client-secret");
|
|
48
|
+
const tlsInsecure = args.includes("--insecure") || args.includes("-k");
|
|
41
49
|
let token;
|
|
42
50
|
if (username && password) {
|
|
43
51
|
// Headless Playwright login with credentials
|
|
44
52
|
console.log("Logging in (headless)...");
|
|
45
|
-
token = await playwrightLogin(normalizedTarget, { username, password });
|
|
53
|
+
token = await playwrightLogin(normalizedTarget, { username, password, tlsInsecure });
|
|
46
54
|
}
|
|
47
55
|
else if (usePlaywright) {
|
|
48
56
|
// Explicit Playwright fallback
|
|
49
57
|
console.log("Opening browser for login (Playwright)...");
|
|
50
|
-
token = await playwrightLogin(normalizedTarget);
|
|
58
|
+
token = await playwrightLogin(normalizedTarget, { tlsInsecure });
|
|
51
59
|
}
|
|
52
60
|
else {
|
|
53
61
|
// Default: OAuth2 authorization code flow (supports refresh_token)
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
if (clientId) {
|
|
63
|
+
console.log(`Opening browser for OAuth2 login (client: ${clientId})...`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.log("Opening browser for OAuth2 login...");
|
|
67
|
+
}
|
|
68
|
+
token = await oauth2Login(normalizedTarget, {
|
|
69
|
+
clientId: clientId ?? undefined,
|
|
70
|
+
clientSecret: clientSecret ?? undefined,
|
|
71
|
+
tlsInsecure,
|
|
72
|
+
});
|
|
56
73
|
}
|
|
57
74
|
if (alias) {
|
|
58
75
|
setPlatformAlias(normalizedTarget, alias);
|
|
@@ -106,6 +123,9 @@ Login options (browser OAuth2 by default; use -u/-p for headless Playwright):
|
|
|
106
123
|
`Token present: yes`,
|
|
107
124
|
];
|
|
108
125
|
lines.push(`Refresh token: ${token.refreshToken ? "yes (auto-refresh enabled)" : "no"}`);
|
|
126
|
+
if (token.tlsInsecure) {
|
|
127
|
+
lines.push(`TLS: certificate verification disabled (saved; dev only)`);
|
|
128
|
+
}
|
|
109
129
|
if (token.expiresAt) {
|
|
110
130
|
const expiry = new Date(token.expiresAt);
|
|
111
131
|
const remainingMs = expiry.getTime() - Date.now();
|