@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 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
@@ -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 (or manual paste for non-localhost)
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>;
@@ -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=invalid_client") ||
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 valid = await isClientStillValid(baseUrl, client.clientId, redirectUri);
203
- if (valid)
204
- return client;
205
- process.stderr.write("Cached OAuth2 client is no longer valid on the server. Re-registering…\n");
206
- deleteClientConfig(baseUrl);
207
- client = null;
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
- * Parse a redirect URI to extract host, port, and pathname.
215
- * Returns null if the URI is not a valid HTTP(S) URL.
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 parseRedirectUri(uri) {
218
- try {
219
- const parsed = new URL(uri);
220
- const host = parsed.hostname;
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
- catch {
226
- return null;
232
+ if (!process.stderr.isTTY) {
233
+ return text;
227
234
  }
235
+ return `\x1b[1;33m${text}\x1b[0m`;
228
236
  }
229
237
  /**
230
- * Manual code flow for non-localhost redirect URIs.
231
- * Prints the auth URL, then reads the full callback URL from stdin
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 waitForManualCode(authUrl, state) {
241
+ async function promptForCode(authUrl, state, port, pasteMode = "explicit") {
235
242
  const { createInterface } = await import("node:readline");
236
- process.stderr.write("\nSince the redirect URI is not localhost, you need to complete login manually.\n" +
237
- "1. Open this URL in your browser:\n\n" +
238
- ` ${authUrl}\n\n` +
239
- "2. After login, the browser will redirect to your callback URL.\n" +
240
- "3. Copy the full callback URL and paste it here:\n\n");
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 callbackUrl = await new Promise((resolve) => {
243
- rl.question("Callback URL> ", (answer) => {
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
- const parsed = new URL(callbackUrl);
249
- const receivedState = parsed.searchParams.get("state");
250
- if (receivedState !== state) {
251
- throw new Error("OAuth2 state mismatch possible CSRF attack.");
252
- }
253
- const error = parsed.searchParams.get("error");
254
- if (error) {
255
- const desc = parsed.searchParams.get("error_description") ?? "";
256
- throw new Error(desc ? `Authorization failed: ${error} — ${desc}` : `Authorization failed: ${error}`);
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
- const code = parsed.searchParams.get("code");
259
- if (!code) {
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 code;
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 (or manual paste for non-localhost)
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
- // Determine redirect URI: explicit option > port-based default
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 (isLocalRedirect) {
320
- // Step 4a: Local callback — start HTTP server to receive the authorization code
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:${listenPort}`);
331
- if (url.pathname !== callbackPathname) {
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(listenPort, "127.0.0.1", () => {
391
- import("../utils/browser.js").then(({ openBrowser }) => {
392
- openBrowser(authUrl);
393
- });
394
- process.stderr.write(`If the wrong browser opens, copy this URL to your correct browser:\n ${authUrl}\n`);
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 = options?.redirectUri ?? `http://127.0.0.1:${port}/callback`;
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:${listenPort}`);
568
- if (url.pathname !== callbackPathname) {
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(listenPort, "127.0.0.1", async () => {
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://127.0.0.1:${DEFAULT_REDIRECT_PORT}/callback`;
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]
@@ -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
- --redirect-uri <uri> Full OAuth2 redirect URI override. Localhost URIs start a local server;
32
- non-localhost URIs use a manual paste-the-callback-URL flow.
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", "--redirect-uri", "--username", "-u", "--password", "-p",
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", "--redirect-uri", "--username", "-u", "--password", "-p",
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 (clientId) {
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) {
@@ -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 isWindows = process.platform === "win32";
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: !isWindows,
37
+ detached,
15
38
  windowsHide: true,
16
39
  });
17
40
  child.once("error", () => resolve(false));
18
41
  child.once("spawn", () => resolve(true));
19
- if (!isWindows) {
42
+ if (detached) {
20
43
  child.unref();
21
44
  }
22
45
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kweaver-ai/kweaver-sdk",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "KWeaver TypeScript SDK — CLI tool and programmatic API for knowledge networks and Decision Agents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",