@kweaver-ai/kweaver-sdk 0.6.0 → 0.6.1
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 +1 -1
- package/README.zh.md +1 -1
- package/dist/auth/oauth.d.ts +7 -6
- package/dist/auth/oauth.js +106 -73
- package/dist/cli.js +1 -1
- package/dist/commands/auth.js +28 -15
- package/dist/utils/browser.js +33 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -142,7 +142,7 @@ const skillMd = await client.skills.fetchContent("skill-id");
|
|
|
142
142
|
## CLI Reference
|
|
143
143
|
|
|
144
144
|
```
|
|
145
|
-
kweaver auth login <url> [--alias name] [--no-auth] [-u user] [-p pass] [--playwright] [--insecure|-k]
|
|
145
|
+
kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--insecure|-k]
|
|
146
146
|
kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (headless login)
|
|
147
147
|
kweaver auth export [url|alias] [--json] (export command to run on a headless host)
|
|
148
148
|
kweaver auth status/list/use/delete/logout
|
package/README.zh.md
CHANGED
|
@@ -131,7 +131,7 @@ const skillMd = await client.skills.fetchContent("skill-id");
|
|
|
131
131
|
## 命令速查
|
|
132
132
|
|
|
133
133
|
```
|
|
134
|
-
kweaver auth login <url> [--alias name] [--no-auth] [-u user] [-p pass] [--playwright] [--insecure|-k]
|
|
134
|
+
kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--insecure|-k]
|
|
135
135
|
kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (无浏览器登录)
|
|
136
136
|
kweaver auth export [url|alias] [--json] (导出在无浏览器机器上运行的命令)
|
|
137
137
|
kweaver auth status/list/use/delete/logout
|
package/dist/auth/oauth.d.ts
CHANGED
|
@@ -14,20 +14,23 @@ export declare function normalizeBaseUrl(value: string): string;
|
|
|
14
14
|
/**
|
|
15
15
|
* OAuth2 Authorization Code login flow.
|
|
16
16
|
* 1. Register client (if not already registered), OR use a provided client ID
|
|
17
|
-
* 2. Open browser to /oauth2/auth
|
|
18
|
-
* 3. Receive authorization code via local HTTP callback
|
|
17
|
+
* 2. Open browser to /oauth2/auth (unless `noBrowser` or browser launch fails)
|
|
18
|
+
* 3. Receive authorization code via local HTTP callback, or stdin paste (`noBrowser` / fallback)
|
|
19
19
|
* 4. Exchange code for access_token + refresh_token
|
|
20
20
|
* 5. Save token.json + client.json to ~/.kweaver/
|
|
21
21
|
*/
|
|
22
22
|
export declare function oauth2Login(baseUrl: string, options?: {
|
|
23
23
|
port?: number;
|
|
24
|
-
/** Full redirect URI override (e.g. "http://127.0.0.1:9010/callback" or a remote URL). */
|
|
25
|
-
redirectUri?: string;
|
|
26
24
|
scope?: string;
|
|
27
25
|
clientId?: string;
|
|
28
26
|
clientSecret?: string;
|
|
29
27
|
/** Skip TLS certificate verification (self-signed / dev servers only). */
|
|
30
28
|
tlsInsecure?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Do not open a browser; print the auth URL and prompt for the callback URL or code (stdin).
|
|
31
|
+
* For headless servers or when automatic browser launch is unavailable.
|
|
32
|
+
*/
|
|
33
|
+
noBrowser?: boolean;
|
|
31
34
|
}): Promise<TokenConfig>;
|
|
32
35
|
/**
|
|
33
36
|
* Playwright-automated OAuth2 login.
|
|
@@ -44,8 +47,6 @@ export declare function playwrightLogin(baseUrl: string, options?: {
|
|
|
44
47
|
username?: string;
|
|
45
48
|
password?: string;
|
|
46
49
|
port?: number;
|
|
47
|
-
/** Full redirect URI override. */
|
|
48
|
-
redirectUri?: string;
|
|
49
50
|
scope?: string;
|
|
50
51
|
tlsInsecure?: boolean;
|
|
51
52
|
}): Promise<TokenConfig>;
|
package/dist/auth/oauth.js
CHANGED
|
@@ -160,8 +160,7 @@ async function isClientStillValid(baseUrl, clientId, redirectUri) {
|
|
|
160
160
|
});
|
|
161
161
|
if (resp.status === 302) {
|
|
162
162
|
const location = resp.headers.get("location") ?? "";
|
|
163
|
-
if (location.includes("error=
|
|
164
|
-
location.includes("error=error")) {
|
|
163
|
+
if (location.includes("error=")) {
|
|
165
164
|
return false;
|
|
166
165
|
}
|
|
167
166
|
return true;
|
|
@@ -199,73 +198,99 @@ async function resolveOrRegisterClient(baseUrl, redirectUri, scope, options) {
|
|
|
199
198
|
}
|
|
200
199
|
let client = loadClientConfig(baseUrl);
|
|
201
200
|
if (client?.clientId) {
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
201
|
+
const storedUri = client.redirectUri ?? redirectUri;
|
|
202
|
+
const valid = await isClientStillValid(baseUrl, client.clientId, storedUri);
|
|
203
|
+
if (valid) {
|
|
204
|
+
if (storedUri !== redirectUri) {
|
|
205
|
+
process.stderr.write("Redirect URI changed. Re-registering OAuth2 client…\n");
|
|
206
|
+
deleteClientConfig(baseUrl);
|
|
207
|
+
client = null;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
return client;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
process.stderr.write("Cached OAuth2 client is no longer valid on the server. Re-registering…\n");
|
|
215
|
+
deleteClientConfig(baseUrl);
|
|
216
|
+
client = null;
|
|
217
|
+
}
|
|
208
218
|
}
|
|
209
219
|
const registered = await registerOAuth2Client(baseUrl, redirectUri, scope);
|
|
210
220
|
saveClientConfig(baseUrl, registered);
|
|
211
221
|
return registered;
|
|
212
222
|
}
|
|
213
223
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
224
|
+
* Emphasize text on stderr (bold + bright yellow) when stderr is a TTY and `NO_COLOR` is unset.
|
|
225
|
+
* See https://no-color.org/
|
|
216
226
|
*/
|
|
217
|
-
function
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const port = parsed.port ? Number(parsed.port) : (parsed.protocol === "https:" ? 443 : 80);
|
|
222
|
-
const isLocalhost = host === "127.0.0.1" || host === "localhost" || host === "::1";
|
|
223
|
-
return { host, port, pathname: parsed.pathname, isLocalhost };
|
|
227
|
+
function stderrEmphasis(text) {
|
|
228
|
+
const noColor = process.env.NO_COLOR;
|
|
229
|
+
if (noColor != null && noColor !== "") {
|
|
230
|
+
return text;
|
|
224
231
|
}
|
|
225
|
-
|
|
226
|
-
return
|
|
232
|
+
if (!process.stderr.isTTY) {
|
|
233
|
+
return text;
|
|
227
234
|
}
|
|
235
|
+
return `\x1b[1;33m${text}\x1b[0m`;
|
|
228
236
|
}
|
|
229
237
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* to extract the authorization code.
|
|
238
|
+
* Headless login: read authorization code from stdin (full callback URL or raw code).
|
|
239
|
+
* Used with `--no-browser` or when automatic browser launch fails.
|
|
233
240
|
*/
|
|
234
|
-
async function
|
|
241
|
+
async function promptForCode(authUrl, state, port, pasteMode = "explicit") {
|
|
235
242
|
const { createInterface } = await import("node:readline");
|
|
236
|
-
|
|
237
|
-
"
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
"
|
|
243
|
+
const intro = pasteMode === "explicit"
|
|
244
|
+
? "Open this URL on any device (use a private/incognito window if you need the full sign-in form):\n\n"
|
|
245
|
+
: "Could not open a browser automatically. Open this URL on any device:\n\n";
|
|
246
|
+
const pasteInstructions = "After login, the browser may show an error page (this is expected if nothing listens on localhost).\n" +
|
|
247
|
+
"Copy the FULL URL from the address bar and paste it here, or paste only the authorization code.\n" +
|
|
248
|
+
`The URL looks like: http://127.0.0.1:${port}/callback?code=THIS_PART&state=...\n\n`;
|
|
249
|
+
process.stderr.write("\n" +
|
|
250
|
+
intro +
|
|
251
|
+
` ${authUrl}\n\n` +
|
|
252
|
+
stderrEmphasis(pasteInstructions));
|
|
241
253
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
242
|
-
const
|
|
243
|
-
rl.
|
|
254
|
+
const input = await new Promise((resolve, reject) => {
|
|
255
|
+
rl.on("close", () => reject(new Error("Login cancelled.")));
|
|
256
|
+
rl.question("Paste URL or code> ", (answer) => {
|
|
244
257
|
rl.close();
|
|
245
258
|
resolve(answer.trim());
|
|
246
259
|
});
|
|
247
260
|
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
261
|
+
if (input.includes("code=")) {
|
|
262
|
+
let url;
|
|
263
|
+
try {
|
|
264
|
+
url = new URL(input.startsWith("http") ? input : `http://x/?${input}`);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
throw new Error("Could not parse the pasted URL. Paste the full callback URL or the code value.");
|
|
268
|
+
}
|
|
269
|
+
const receivedState = url.searchParams.get("state");
|
|
270
|
+
if (receivedState && receivedState !== state) {
|
|
271
|
+
throw new Error("OAuth2 state mismatch — possible CSRF attack.");
|
|
272
|
+
}
|
|
273
|
+
const err = url.searchParams.get("error");
|
|
274
|
+
if (err) {
|
|
275
|
+
const desc = url.searchParams.get("error_description") ?? "";
|
|
276
|
+
throw new Error(desc ? `Authorization failed: ${err} — ${desc}` : `Authorization failed: ${err}`);
|
|
277
|
+
}
|
|
278
|
+
const code = url.searchParams.get("code");
|
|
279
|
+
if (!code) {
|
|
280
|
+
throw new Error("No authorization code found in the pasted URL.");
|
|
281
|
+
}
|
|
282
|
+
return code;
|
|
257
283
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
throw new Error("No authorization code found in the callback URL.");
|
|
284
|
+
if (!input) {
|
|
285
|
+
throw new Error("No authorization code entered.");
|
|
261
286
|
}
|
|
262
|
-
return
|
|
287
|
+
return input;
|
|
263
288
|
}
|
|
264
289
|
/**
|
|
265
290
|
* OAuth2 Authorization Code login flow.
|
|
266
291
|
* 1. Register client (if not already registered), OR use a provided client ID
|
|
267
|
-
* 2. Open browser to /oauth2/auth
|
|
268
|
-
* 3. Receive authorization code via local HTTP callback
|
|
292
|
+
* 2. Open browser to /oauth2/auth (unless `noBrowser` or browser launch fails)
|
|
293
|
+
* 3. Receive authorization code via local HTTP callback, or stdin paste (`noBrowser` / fallback)
|
|
269
294
|
* 4. Exchange code for access_token + refresh_token
|
|
270
295
|
* 5. Save token.json + client.json to ~/.kweaver/
|
|
271
296
|
*/
|
|
@@ -276,12 +301,7 @@ export async function oauth2Login(baseUrl, options) {
|
|
|
276
301
|
const base = normalizeBaseUrl(baseUrl);
|
|
277
302
|
const port = options?.port ?? DEFAULT_REDIRECT_PORT;
|
|
278
303
|
const scope = options?.scope ?? DEFAULT_SCOPE;
|
|
279
|
-
|
|
280
|
-
const redirectUri = options?.redirectUri ?? `http://127.0.0.1:${port}/callback`;
|
|
281
|
-
const parsedRedirect = parseRedirectUri(redirectUri);
|
|
282
|
-
const isLocalRedirect = parsedRedirect?.isLocalhost ?? true;
|
|
283
|
-
const listenPort = parsedRedirect?.port ?? port;
|
|
284
|
-
const callbackPathname = parsedRedirect?.pathname ?? "/callback";
|
|
304
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
285
305
|
// Step 1: Determine client — use provided client ID or fall back to dynamic registration
|
|
286
306
|
let client;
|
|
287
307
|
try {
|
|
@@ -315,9 +335,19 @@ export async function oauth2Login(baseUrl, options) {
|
|
|
315
335
|
authParams.set("code_challenge_method", "S256");
|
|
316
336
|
}
|
|
317
337
|
const authUrl = `${base}/oauth2/auth?${authParams.toString()}`;
|
|
338
|
+
const runPasteCodeFlow = async (pasteMode) => {
|
|
339
|
+
const code = await promptForCode(authUrl, state, port, pasteMode);
|
|
340
|
+
const exchanged = await exchangeCodeForToken(base, code, client.clientId, client.clientSecret, redirectUri, pkce?.verifier, options?.tlsInsecure);
|
|
341
|
+
const copyCommand = buildCopyCommand(base, client.clientId, client.clientSecret, exchanged.refreshToken, options?.tlsInsecure);
|
|
342
|
+
process.stderr.write("\nOn a machine without a browser, run:\n\n " + copyCommand + "\n\n");
|
|
343
|
+
return exchanged;
|
|
344
|
+
};
|
|
318
345
|
let token;
|
|
319
|
-
if (
|
|
320
|
-
|
|
346
|
+
if (options?.noBrowser) {
|
|
347
|
+
token = await runPasteCodeFlow("explicit");
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
// Step 4: Local HTTP callback, or paste-code if browser cannot be opened
|
|
321
351
|
token = await new Promise((resolve, reject) => {
|
|
322
352
|
let server;
|
|
323
353
|
const timeoutId = setTimeout(() => {
|
|
@@ -327,8 +357,8 @@ export async function oauth2Login(baseUrl, options) {
|
|
|
327
357
|
server = createServer((req, res) => {
|
|
328
358
|
void (async () => {
|
|
329
359
|
try {
|
|
330
|
-
const url = new URL(req.url ?? "/", `http://127.0.0.1:${
|
|
331
|
-
if (url.pathname !==
|
|
360
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
361
|
+
if (url.pathname !== "/callback") {
|
|
332
362
|
res.writeHead(404);
|
|
333
363
|
res.end();
|
|
334
364
|
return;
|
|
@@ -387,19 +417,25 @@ export async function oauth2Login(baseUrl, options) {
|
|
|
387
417
|
}
|
|
388
418
|
})();
|
|
389
419
|
});
|
|
390
|
-
server.listen(
|
|
391
|
-
|
|
392
|
-
openBrowser(
|
|
393
|
-
|
|
394
|
-
|
|
420
|
+
server.listen(port, "127.0.0.1", () => {
|
|
421
|
+
void (async () => {
|
|
422
|
+
const { openBrowser } = await import("../utils/browser.js");
|
|
423
|
+
const opened = await openBrowser(authUrl);
|
|
424
|
+
process.stderr.write(`If the wrong browser opens, copy this URL to your correct browser:\n ${authUrl}\n`);
|
|
425
|
+
if (!opened) {
|
|
426
|
+
clearTimeout(timeoutId);
|
|
427
|
+
server.close();
|
|
428
|
+
try {
|
|
429
|
+
resolve(await runPasteCodeFlow("fallback"));
|
|
430
|
+
}
|
|
431
|
+
catch (err) {
|
|
432
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
})();
|
|
395
436
|
});
|
|
396
437
|
});
|
|
397
438
|
}
|
|
398
|
-
else {
|
|
399
|
-
// Step 4b: Non-localhost redirect — manual code entry flow
|
|
400
|
-
const code = await waitForManualCode(authUrl, state);
|
|
401
|
-
token = await exchangeCodeForToken(base, code, client.clientId, client.clientSecret, redirectUri, pkce?.verifier, options?.tlsInsecure);
|
|
402
|
-
}
|
|
403
439
|
setCurrentPlatform(base);
|
|
404
440
|
return token;
|
|
405
441
|
});
|
|
@@ -520,10 +556,7 @@ export async function playwrightLogin(baseUrl, options) {
|
|
|
520
556
|
const base = normalizeBaseUrl(baseUrl);
|
|
521
557
|
const port = options?.port ?? DEFAULT_REDIRECT_PORT;
|
|
522
558
|
const scope = options?.scope ?? DEFAULT_SCOPE;
|
|
523
|
-
const redirectUri =
|
|
524
|
-
const parsedRedirect = parseRedirectUri(redirectUri);
|
|
525
|
-
const listenPort = parsedRedirect?.port ?? port;
|
|
526
|
-
const callbackPathname = parsedRedirect?.pathname ?? "/callback";
|
|
559
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
527
560
|
const hasCredentials = !!(options?.username && options?.password);
|
|
528
561
|
// Step 1: Ensure registered OAuth2 client (with stale-client auto-recovery)
|
|
529
562
|
let client;
|
|
@@ -564,8 +597,8 @@ export async function playwrightLogin(baseUrl, options) {
|
|
|
564
597
|
server = createServer((req, res) => {
|
|
565
598
|
void (async () => {
|
|
566
599
|
try {
|
|
567
|
-
const url = new URL(req.url ?? "/", `http://127.0.0.1:${
|
|
568
|
-
if (url.pathname !==
|
|
600
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
601
|
+
if (url.pathname !== "/callback") {
|
|
569
602
|
res.writeHead(404);
|
|
570
603
|
res.end();
|
|
571
604
|
return;
|
|
@@ -629,7 +662,7 @@ export async function playwrightLogin(baseUrl, options) {
|
|
|
629
662
|
}
|
|
630
663
|
})();
|
|
631
664
|
});
|
|
632
|
-
server.listen(
|
|
665
|
+
server.listen(port, "127.0.0.1", async () => {
|
|
633
666
|
try {
|
|
634
667
|
browser = await chromium.launch({ headless: hasCredentials });
|
|
635
668
|
const context = await browser.newContext({ ignoreHTTPSErrors: !!options?.tlsInsecure });
|
|
@@ -670,7 +703,7 @@ export async function playwrightLogin(baseUrl, options) {
|
|
|
670
703
|
*/
|
|
671
704
|
export async function refreshTokenLogin(baseUrl, options) {
|
|
672
705
|
const base = normalizeBaseUrl(baseUrl);
|
|
673
|
-
const redirectUri = `http://
|
|
706
|
+
const redirectUri = `http://localhost:${DEFAULT_REDIRECT_PORT}/callback`;
|
|
674
707
|
const client = {
|
|
675
708
|
baseUrl: base,
|
|
676
709
|
clientId: options.clientId,
|
package/dist/cli.js
CHANGED
|
@@ -21,7 +21,7 @@ Usage:
|
|
|
21
21
|
kweaver --version | -V
|
|
22
22
|
kweaver --help | -h
|
|
23
23
|
|
|
24
|
-
kweaver auth <platform-url> [--alias name] [--no-auth] [-u user] [-p pass] [--playwright] [--insecure|-k]
|
|
24
|
+
kweaver auth <platform-url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--insecure|-k]
|
|
25
25
|
kweaver auth login <platform-url> (alias for auth <url>)
|
|
26
26
|
kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (run on host without browser)
|
|
27
27
|
kweaver auth whoami [platform-url|alias] [--json]
|
package/dist/commands/auth.js
CHANGED
|
@@ -28,9 +28,8 @@ Login options:
|
|
|
28
28
|
Requires --client-id and --client-secret.
|
|
29
29
|
Get these from the callback page after browser login or \`auth export\`.
|
|
30
30
|
--port <n> Local callback port (default: 9010). Use when 9010 is occupied.
|
|
31
|
-
--
|
|
32
|
-
|
|
33
|
-
When set, --port is ignored. Example: --redirect-uri http://127.0.0.1:9010/callback
|
|
31
|
+
--no-browser Do not open a browser; print the auth URL and prompt for the callback URL or code (stdin).
|
|
32
|
+
Use on headless servers or when automatic browser launch fails.
|
|
34
33
|
-u, --username Username (with -p triggers Playwright headless login)
|
|
35
34
|
-p, --password Password
|
|
36
35
|
--playwright Force Playwright browser login even without -u/-p
|
|
@@ -40,7 +39,7 @@ Login options:
|
|
|
40
39
|
}
|
|
41
40
|
if (target === "login") {
|
|
42
41
|
if (rest[0] === "--help" || rest[0] === "-h") {
|
|
43
|
-
console.log(`kweaver auth login <platform-url> [--alias <name>] [--no-auth] [-u user] [-p pass] [--playwright] [--refresh-token T --client-id ID --client-secret S]`);
|
|
42
|
+
console.log(`kweaver auth login <platform-url> [--alias <name>] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--refresh-token T --client-id ID --client-secret S]`);
|
|
44
43
|
return 0;
|
|
45
44
|
}
|
|
46
45
|
const url = rest[0];
|
|
@@ -73,19 +72,22 @@ Login options:
|
|
|
73
72
|
const clientId = readOption(args, "--client-id");
|
|
74
73
|
const clientSecret = readOption(args, "--client-secret");
|
|
75
74
|
const refreshToken = readOption(args, "--refresh-token");
|
|
76
|
-
const customRedirectUri = readOption(args, "--redirect-uri");
|
|
77
75
|
const customPortStr = readOption(args, "--port");
|
|
78
76
|
const customPort = customPortStr ? parseInt(customPortStr, 10) : undefined;
|
|
79
77
|
const tlsInsecure = args.includes("--insecure") || args.includes("-k");
|
|
80
78
|
const noAuth = args.includes("--no-auth");
|
|
79
|
+
const noBrowser = args.includes("--no-browser");
|
|
80
|
+
if (args.includes("--redirect-uri")) {
|
|
81
|
+
console.error("Warning: --redirect-uri is deprecated and ignored. The redirect URI is always http://127.0.0.1:<port>/callback.");
|
|
82
|
+
}
|
|
81
83
|
const KNOWN_LOGIN_FLAGS = new Set([
|
|
82
84
|
"--alias", "--client-id", "--client-secret", "--refresh-token",
|
|
83
|
-
"--port", "--
|
|
84
|
-
"--playwright", "--insecure", "-k", "--no-auth",
|
|
85
|
+
"--port", "--no-browser", "--username", "-u", "--password", "-p",
|
|
86
|
+
"--playwright", "--insecure", "-k", "--no-auth", "--redirect-uri",
|
|
85
87
|
]);
|
|
86
88
|
const KNOWN_VALUE_FLAGS = new Set([
|
|
87
89
|
"--alias", "--client-id", "--client-secret", "--refresh-token",
|
|
88
|
-
"--port", "--
|
|
90
|
+
"--port", "--username", "-u", "--password", "-p", "--redirect-uri",
|
|
89
91
|
]);
|
|
90
92
|
for (let i = 0; i < args.length; i++) {
|
|
91
93
|
const a = args[i];
|
|
@@ -105,10 +107,21 @@ Login options:
|
|
|
105
107
|
console.error("--no-auth cannot be used with --refresh-token.");
|
|
106
108
|
return 1;
|
|
107
109
|
}
|
|
110
|
+
if (noAuth && noBrowser) {
|
|
111
|
+
console.error("--no-auth does not require a browser; --no-browser is ignored.");
|
|
112
|
+
}
|
|
108
113
|
if (noAuth && (username || password || usePlaywright)) {
|
|
109
114
|
console.error("--no-auth cannot be used with Playwright login or -u/-p.");
|
|
110
115
|
return 1;
|
|
111
116
|
}
|
|
117
|
+
if (noBrowser && (username || password || usePlaywright)) {
|
|
118
|
+
console.error("--no-browser cannot be used with Playwright login or -u/-p.");
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
if (noBrowser && refreshToken) {
|
|
122
|
+
console.error("--no-browser cannot be used with --refresh-token.");
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
112
125
|
let token;
|
|
113
126
|
if (noAuth) {
|
|
114
127
|
token = saveNoAuthPlatform(normalizedTarget, { tlsInsecure });
|
|
@@ -127,19 +140,20 @@ Login options:
|
|
|
127
140
|
else if (username && password) {
|
|
128
141
|
console.log("Logging in (headless)...");
|
|
129
142
|
token = await playwrightLogin(normalizedTarget, {
|
|
130
|
-
username, password, tlsInsecure,
|
|
131
|
-
port: customPort, redirectUri: customRedirectUri ?? undefined,
|
|
143
|
+
username, password, tlsInsecure, port: customPort,
|
|
132
144
|
});
|
|
133
145
|
}
|
|
134
146
|
else if (usePlaywright) {
|
|
135
147
|
console.log("Opening browser for login (Playwright)...");
|
|
136
148
|
token = await playwrightLogin(normalizedTarget, {
|
|
137
|
-
tlsInsecure,
|
|
138
|
-
port: customPort, redirectUri: customRedirectUri ?? undefined,
|
|
149
|
+
tlsInsecure, port: customPort,
|
|
139
150
|
});
|
|
140
151
|
}
|
|
141
152
|
else {
|
|
142
|
-
if (
|
|
153
|
+
if (noBrowser) {
|
|
154
|
+
console.log("OAuth2 login (no browser — open the URL on any device, then paste the callback URL or code)...");
|
|
155
|
+
}
|
|
156
|
+
else if (clientId) {
|
|
143
157
|
console.log(`Opening browser for OAuth2 login (client: ${clientId})...`);
|
|
144
158
|
}
|
|
145
159
|
else {
|
|
@@ -148,8 +162,7 @@ Login options:
|
|
|
148
162
|
token = await oauth2Login(normalizedTarget, {
|
|
149
163
|
clientId: clientId ?? undefined,
|
|
150
164
|
clientSecret: clientSecret ?? undefined,
|
|
151
|
-
tlsInsecure,
|
|
152
|
-
port: customPort, redirectUri: customRedirectUri ?? undefined,
|
|
165
|
+
tlsInsecure, port: customPort, noBrowser,
|
|
153
166
|
});
|
|
154
167
|
}
|
|
155
168
|
if (alias) {
|
package/dist/utils/browser.js
CHANGED
|
@@ -1,22 +1,45 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import { release } from "node:os";
|
|
4
|
+
let _isWSL;
|
|
5
|
+
function isWSL() {
|
|
6
|
+
if (_isWSL !== undefined)
|
|
7
|
+
return _isWSL;
|
|
8
|
+
if (process.platform !== "linux")
|
|
9
|
+
return (_isWSL = false);
|
|
10
|
+
if (/microsoft/i.test(release()))
|
|
11
|
+
return (_isWSL = true);
|
|
12
|
+
try {
|
|
13
|
+
const procVersion = readFileSync("/proc/version", "utf8");
|
|
14
|
+
return (_isWSL = /microsoft/i.test(procVersion));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return (_isWSL = false);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function resolveCommand(url) {
|
|
21
|
+
if (process.platform === "darwin") {
|
|
22
|
+
return { command: "open", args: [url], detached: true };
|
|
23
|
+
}
|
|
24
|
+
if (process.platform === "win32") {
|
|
25
|
+
return { command: "rundll32.exe", args: ["url.dll,FileProtocolHandler", url], detached: false };
|
|
26
|
+
}
|
|
27
|
+
if (isWSL()) {
|
|
28
|
+
return { command: "cmd.exe", args: ["/c", "start", "", url.replace(/&/g, "^&")], detached: true };
|
|
29
|
+
}
|
|
30
|
+
return { command: "xdg-open", args: [url], detached: true };
|
|
31
|
+
}
|
|
2
32
|
export function openBrowser(url) {
|
|
3
|
-
const
|
|
4
|
-
const command = process.platform === "darwin" ? "open" : isWindows ? "rundll32.exe" : "xdg-open";
|
|
5
|
-
const args = isWindows
|
|
6
|
-
? [
|
|
7
|
-
"url.dll,FileProtocolHandler",
|
|
8
|
-
url,
|
|
9
|
-
]
|
|
10
|
-
: [url];
|
|
33
|
+
const { command, args, detached } = resolveCommand(url);
|
|
11
34
|
return new Promise((resolve) => {
|
|
12
35
|
const child = spawn(command, args, {
|
|
13
36
|
stdio: "ignore",
|
|
14
|
-
detached
|
|
37
|
+
detached,
|
|
15
38
|
windowsHide: true,
|
|
16
39
|
});
|
|
17
40
|
child.once("error", () => resolve(false));
|
|
18
41
|
child.once("spawn", () => resolve(true));
|
|
19
|
-
if (
|
|
42
|
+
if (detached) {
|
|
20
43
|
child.unref();
|
|
21
44
|
}
|
|
22
45
|
});
|
package/package.json
CHANGED