@kweaver-ai/kweaver-sdk 0.6.5 → 0.6.7

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.
@@ -3,6 +3,21 @@ import { createPublicKey } from "node:crypto";
3
3
  import { isNoAuth } from "../config/no-auth.js";
4
4
  import { deleteClientConfig, getCurrentPlatform, loadClientConfig, loadTokenConfig, loadUserTokenConfig, resolveUserId, saveClientConfig, saveNoAuthPlatform, saveTokenConfig, setCurrentPlatform, } from "../config/store.js";
5
5
  import { HttpError, NetworkRequestError, fetchWithRetry } from "../utils/http.js";
6
+ /** Thrown when `POST /oauth2/signin` returns HTTP 401 with EACP code `401001017` (initial password must be changed). */
7
+ export class InitialPasswordChangeRequiredError extends Error {
8
+ code = 401001017;
9
+ account;
10
+ baseUrl;
11
+ httpStatus = 401;
12
+ serverMessage;
13
+ constructor(opts) {
14
+ super(opts.serverMessage);
15
+ this.name = "InitialPasswordChangeRequiredError";
16
+ this.account = opts.account;
17
+ this.baseUrl = opts.baseUrl;
18
+ this.serverMessage = opts.serverMessage;
19
+ }
20
+ }
6
21
  const TOKEN_TTL_SECONDS = 3600;
7
22
  /** Seconds before access token expiry to trigger refresh (matches Python ConfigAuth). */
8
23
  const REFRESH_THRESHOLD_SEC = 60;
@@ -463,23 +478,36 @@ function stderrEmphasis(text) {
463
478
  /**
464
479
  * Headless login: read authorization code from stdin (full callback URL or raw code).
465
480
  * Used with `--no-browser` or when automatic browser launch fails.
481
+ *
482
+ * `io` is injectable for tests; defaults to `process.stdin` / `process.stderr`.
466
483
  */
467
- async function promptForCode(authUrl, state, port, pasteMode = "explicit") {
484
+ export async function promptForCode(authUrl, state, port, pasteMode = "explicit", io) {
468
485
  const { createInterface } = await import("node:readline");
486
+ const stdin = io?.input ?? process.stdin;
487
+ const stderr = io?.output ?? process.stderr;
469
488
  const intro = pasteMode === "explicit"
470
489
  ? "Open this URL on any device (use a private/incognito window if you need the full sign-in form):\n\n"
471
490
  : "Could not open a browser automatically. Open this URL on any device:\n\n";
472
491
  const pasteInstructions = "After login, the browser may show an error page (this is expected if nothing listens on localhost).\n" +
473
492
  "Copy the FULL URL from the address bar and paste it here, or paste only the authorization code.\n" +
474
493
  `The URL looks like: http://127.0.0.1:${port}/callback?code=THIS_PART&state=...\n\n`;
475
- process.stderr.write("\n" +
494
+ stderr.write("\n" +
476
495
  intro +
477
496
  ` ${authUrl}\n\n` +
478
497
  stderrEmphasis(pasteInstructions));
479
- const rl = createInterface({ input: process.stdin, output: process.stderr });
498
+ const rl = createInterface({ input: stdin, output: stderr });
499
+ // The `close` listener exists to surface Ctrl-D / EOF before the user answers.
500
+ // It MUST be a no-op once the question callback has fired, because `rl.close()`
501
+ // emits `close` synchronously and would otherwise reject the promise before
502
+ // `resolve(answer)` runs (race condition that turns valid input into "Login cancelled.").
480
503
  const input = await new Promise((resolve, reject) => {
481
- rl.on("close", () => reject(new Error("Login cancelled.")));
504
+ let answered = false;
505
+ rl.on("close", () => {
506
+ if (!answered)
507
+ reject(new Error("Login cancelled."));
508
+ });
482
509
  rl.question("Paste URL or code> ", (answer) => {
510
+ answered = true;
483
511
  rl.close();
484
512
  resolve(answer.trim());
485
513
  });
@@ -512,6 +540,128 @@ async function promptForCode(authUrl, state, port, pasteMode = "explicit") {
512
540
  }
513
541
  return input;
514
542
  }
543
+ /**
544
+ * Prompt the user for a username on stderr (input echoed).
545
+ *
546
+ * `io` is injectable for tests; defaults to `process.stdin` / `process.stderr`.
547
+ */
548
+ export async function promptForUsername(promptLabel = "Username", io) {
549
+ const { createInterface } = await import("node:readline");
550
+ const stdin = io?.input ?? process.stdin;
551
+ const stderr = io?.output ?? process.stderr;
552
+ const rl = createInterface({ input: stdin, output: stderr });
553
+ const value = await new Promise((resolve, reject) => {
554
+ let answered = false;
555
+ rl.on("close", () => {
556
+ if (!answered)
557
+ reject(new Error("Login cancelled."));
558
+ });
559
+ rl.question(`${promptLabel}: `, (answer) => {
560
+ answered = true;
561
+ rl.close();
562
+ resolve(answer.trim());
563
+ });
564
+ });
565
+ if (!value) {
566
+ throw new Error(`${promptLabel} is required.`);
567
+ }
568
+ return value;
569
+ }
570
+ /**
571
+ * Prompt the user for a password on stderr without echoing keystrokes (TTY only).
572
+ *
573
+ * Falls back to a regular readline prompt when stdin is not a TTY (e.g. piped input
574
+ * during scripted use); callers needing strict no-echo should detect this case themselves.
575
+ *
576
+ * `io` is injectable for tests.
577
+ */
578
+ export async function promptForPassword(promptLabel = "Password", io) {
579
+ const stdin = io?.input ?? process.stdin;
580
+ const stderr = io?.output ?? process.stderr;
581
+ // Non-TTY (piped, redirected, tests): use regular readline — no masking is possible
582
+ // without raw mode, so we accept echoed input rather than block forever.
583
+ if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
584
+ const { createInterface } = await import("node:readline");
585
+ const rl = createInterface({ input: stdin, output: stderr });
586
+ const value = await new Promise((resolve, reject) => {
587
+ let answered = false;
588
+ rl.on("close", () => {
589
+ if (!answered)
590
+ reject(new Error("Login cancelled."));
591
+ });
592
+ rl.question(`${promptLabel}: `, (answer) => {
593
+ answered = true;
594
+ rl.close();
595
+ resolve(answer);
596
+ });
597
+ });
598
+ if (!value)
599
+ throw new Error(`${promptLabel} is required.`);
600
+ return value;
601
+ }
602
+ // TTY: read byte-by-byte in raw mode so keystrokes are not echoed.
603
+ return new Promise((resolve, reject) => {
604
+ stderr.write(`${promptLabel}: `);
605
+ let buf = "";
606
+ const onData = (chunk) => {
607
+ const s = chunk.toString("utf8");
608
+ for (const ch of s) {
609
+ const code = ch.charCodeAt(0);
610
+ if (ch === "\n" || ch === "\r") {
611
+ cleanup();
612
+ stderr.write("\n");
613
+ if (!buf) {
614
+ reject(new Error(`${promptLabel} is required.`));
615
+ }
616
+ else {
617
+ resolve(buf);
618
+ }
619
+ return;
620
+ }
621
+ if (code === 3) {
622
+ // Ctrl-C
623
+ cleanup();
624
+ stderr.write("\n");
625
+ reject(new Error("Login cancelled."));
626
+ return;
627
+ }
628
+ if (code === 4 && buf.length === 0) {
629
+ // Ctrl-D on empty buffer -> cancel
630
+ cleanup();
631
+ stderr.write("\n");
632
+ reject(new Error("Login cancelled."));
633
+ return;
634
+ }
635
+ if (code === 8 || code === 127) {
636
+ // Backspace / DEL
637
+ buf = buf.slice(0, -1);
638
+ continue;
639
+ }
640
+ if (code < 32)
641
+ continue; // ignore other control chars
642
+ buf += ch;
643
+ }
644
+ };
645
+ const cleanup = () => {
646
+ try {
647
+ stdin.setRawMode(false);
648
+ }
649
+ catch { /* noop */ }
650
+ stdin.removeListener("data", onData);
651
+ if (typeof stdin.pause === "function") {
652
+ stdin.pause();
653
+ }
654
+ };
655
+ try {
656
+ stdin.setRawMode(true);
657
+ }
658
+ catch { /* noop */ }
659
+ if (typeof stdin.resume === "function") {
660
+ stdin.resume();
661
+ }
662
+ stdin.on("data", onData);
663
+ });
664
+ }
515
665
  /**
516
666
  * OAuth2 Authorization Code login flow.
517
667
  * 1. Register client (if not already registered), OR use a provided client ID
@@ -755,174 +905,6 @@ async function exchangeCodeForToken(baseUrl, code, clientId, clientSecret, redir
755
905
  saveTokenConfig(token);
756
906
  return token;
757
907
  }
758
- /**
759
- * Playwright-automated OAuth2 login.
760
- *
761
- * Uses the full OAuth2 authorization code flow (same as `oauth2Login`) but
762
- * automates the browser interaction with Playwright. This produces a
763
- * refresh_token so the CLI can auto-refresh without re-login.
764
- *
765
- * When `username` and `password` are provided the browser runs headless and
766
- * fills the login form automatically. Otherwise it opens a visible browser
767
- * window for manual login (same UX as the old cookie-based flow).
768
- */
769
- export async function playwrightLogin(baseUrl, options) {
770
- return runWithTlsInsecure(options?.tlsInsecure, async () => {
771
- const { createServer } = await import("node:http");
772
- const { randomBytes } = await import("node:crypto");
773
- let chromium;
774
- try {
775
- const modName = "playwright";
776
- const pw = await import(/* webpackIgnore: true */ modName);
777
- chromium = pw.chromium;
778
- }
779
- catch {
780
- throw new Error("Playwright is not installed. Run:\n npm install playwright && npx playwright install chromium");
781
- }
782
- const base = normalizeBaseUrl(baseUrl);
783
- const port = options?.port ?? DEFAULT_REDIRECT_PORT;
784
- const scope = options?.scope ?? DEFAULT_SCOPE;
785
- const redirectUri = `http://127.0.0.1:${port}/callback`;
786
- const hasCredentials = !!(options?.username && options?.password);
787
- // Step 1: Ensure registered OAuth2 client (with stale-client auto-recovery)
788
- let client;
789
- try {
790
- client = await resolveOrRegisterClient(base, redirectUri, scope);
791
- }
792
- catch (e) {
793
- if (e instanceof HttpError && e.status === 404) {
794
- process.stderr.write("OAuth2 endpoint not found (404). Saving platform in no-auth mode.\n");
795
- return saveNoAuthPlatform(base, { tlsInsecure: options?.tlsInsecure });
796
- }
797
- throw e;
798
- }
799
- // Step 2: Generate CSRF state
800
- const state = randomBytes(12).toString("hex");
801
- // Step 3: Build authorization URL
802
- const authParams = new URLSearchParams({
803
- redirect_uri: redirectUri,
804
- "x-forwarded-prefix": "",
805
- client_id: client.clientId,
806
- scope,
807
- response_type: "code",
808
- state,
809
- lang: "zh-cn",
810
- product: "adp",
811
- });
812
- const authUrl = `${base}/oauth2/auth?${authParams.toString()}`;
813
- // Step 4: Start local callback server; exchange code inside handler, then show credentials HTML
814
- let browser;
815
- const token = await new Promise((resolve, reject) => {
816
- const TIMEOUT_MS = hasCredentials ? 30_000 : 120_000;
817
- let server;
818
- const timeoutId = setTimeout(() => {
819
- server?.close();
820
- browser?.close();
821
- reject(new Error(`OAuth2 login timed out (${TIMEOUT_MS / 1000}s). No authorization code received.`));
822
- }, TIMEOUT_MS);
823
- server = createServer((req, res) => {
824
- void (async () => {
825
- try {
826
- const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
827
- if (url.pathname !== "/callback") {
828
- res.writeHead(404);
829
- res.end();
830
- return;
831
- }
832
- const receivedState = url.searchParams.get("state");
833
- const receivedCode = url.searchParams.get("code");
834
- const callbackError = url.searchParams.get("error");
835
- const callbackErrorDesc = url.searchParams.get("error_description");
836
- if (receivedState !== state) {
837
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
838
- res.end(buildCallbackExchangeErrorHtml("OAuth2 state mismatch — possible CSRF attack."));
839
- clearTimeout(timeoutId);
840
- server.close();
841
- browser?.close();
842
- reject(new Error("OAuth2 state mismatch — possible CSRF attack."));
843
- return;
844
- }
845
- if (callbackError) {
846
- const msg = callbackErrorDesc
847
- ? `Authorization failed: ${callbackError} — ${callbackErrorDesc}`
848
- : `Authorization failed: ${callbackError}`;
849
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
850
- res.end(buildCallbackExchangeErrorHtml(msg));
851
- clearTimeout(timeoutId);
852
- server.close();
853
- browser?.close();
854
- reject(new Error(msg));
855
- return;
856
- }
857
- if (!receivedCode) {
858
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
859
- res.end(buildCallbackExchangeErrorHtml("No authorization code received in callback."));
860
- clearTimeout(timeoutId);
861
- server.close();
862
- browser?.close();
863
- reject(new Error("No authorization code received in callback."));
864
- return;
865
- }
866
- const exchanged = await exchangeCodeForToken(base, receivedCode, client.clientId, client.clientSecret, redirectUri, undefined, options?.tlsInsecure);
867
- const copyCommand = buildCopyCommand(base, client.clientId, client.clientSecret, exchanged.refreshToken, options?.tlsInsecure);
868
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
869
- res.end(buildCallbackHtml(copyCommand));
870
- clearTimeout(timeoutId);
871
- server.close();
872
- browser?.close();
873
- resolve(exchanged);
874
- }
875
- catch (err) {
876
- const message = err instanceof Error ? err.message : String(err);
877
- try {
878
- res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
879
- res.end(buildCallbackExchangeErrorHtml(message));
880
- }
881
- catch {
882
- /* response may already be sent */
883
- }
884
- clearTimeout(timeoutId);
885
- server.close();
886
- browser?.close();
887
- reject(err instanceof Error ? err : new Error(message));
888
- }
889
- })();
890
- });
891
- server.listen(port, "127.0.0.1", async () => {
892
- try {
893
- browser = await chromium.launch({ headless: hasCredentials });
894
- const context = await browser.newContext({ ignoreHTTPSErrors: !!options?.tlsInsecure });
895
- const page = await context.newPage();
896
- // Navigate to OAuth2 auth URL — redirects to signin page
897
- await page.goto(authUrl, { waitUntil: "networkidle", timeout: 30_000 });
898
- if (hasCredentials) {
899
- // Auto-fill credentials
900
- await page.waitForSelector('input[name="account"]', { timeout: 10_000 });
901
- await page.fill('input[name="account"]', options.username);
902
- await page.fill('input[name="password"]', options.password);
903
- await page.click("button.ant-btn-primary");
904
- }
905
- // else: visible browser — user logs in manually
906
- // The OAuth2 callback will fire when login completes, resolving the promise above
907
- }
908
- catch (err) {
909
- clearTimeout(timeoutId);
910
- server.close();
911
- browser?.close();
912
- reject(err);
913
- }
914
- });
915
- });
916
- if (hasCredentials) {
917
- const copyCommand = buildCopyCommand(base, client.clientId, client.clientSecret, token.refreshToken, options?.tlsInsecure);
918
- process.stderr.write("\nHeadless login: copy this command and run it on a machine without a browser, or use `kweaver auth export`:\n\n" +
919
- copyCommand +
920
- "\n\n");
921
- }
922
- setCurrentPlatform(base);
923
- return token;
924
- });
925
- }
926
908
  function mergeCookieJarForSignin(existing, response) {
927
909
  const setCookies = typeof response.headers.getSetCookie === "function"
928
910
  ? response.headers.getSetCookie()
@@ -1041,7 +1023,7 @@ async function followSigninRedirectsUntilCallback(startUrl, initialJar, state, r
1041
1023
  return consentResult;
1042
1024
  }
1043
1025
  throw new Error(`Unexpected OAuth page (HTTP 200) at ${url.slice(0, 120)}… ` +
1044
- `If this is a consent or MFA screen, use browser login or Playwright.`);
1026
+ `If this is a consent or MFA screen, use browser login (kweaver auth login <url>).`);
1045
1027
  }
1046
1028
  const text = await resp.text().catch(() => "");
1047
1029
  throw new HttpError(resp.status, resp.statusText, text);
@@ -1094,17 +1076,38 @@ async function tryAcceptConsentAfterSignin(base, pageUrl, html, jar, scope, stat
1094
1076
  }
1095
1077
  return null;
1096
1078
  }
1097
- const STUDIOWEB_SHELL_UNAVAILABLE_SNIPPETS = [
1098
- "Studioweb signin endpoint not available",
1099
- "Cannot reach studioweb signin endpoint",
1100
- ];
1101
1079
  /**
1102
- * True when {@link oauth2PasswordSigninLogin} failed because the Studio web sign-in shell
1103
- * (`/interface/studioweb/login`) is missing or unreachable — callers may fall back to Playwright.
1080
+ * Build the JSON body for `POST /oauth2/signin` (matches the browser `oauth2-ui` form).
1081
+ *
1082
+ * `device.client_type` MUST be a value present in the EACP whitelist defined by
1083
+ * `kweaver/deploy/auto_cofig/auto_config.sh`. `console_web` is the canonical CLI value
1084
+ * (also used by `kweaver-admin`); other values such as `unknown` are rejected by strict
1085
+ * deployments with `管理员已禁止此类客户端登录` — surfaced upstream as a `request_forbidden`
1086
+ * `No CSRF value available in the session cookie` error after Hydra discards the rejected
1087
+ * login challenge.
1088
+ *
1089
+ * `vcode` and `dualfactorauthinfo` must be present even when empty; otherwise eachttpserver
1090
+ * returns HTTP 400 (invalid parameter).
1104
1091
  */
1105
- export function isStudiowebShellUnavailableError(err) {
1106
- const msg = err instanceof Error ? err.message : String(err);
1107
- return STUDIOWEB_SHELL_UNAVAILABLE_SNIPPETS.some((s) => msg.includes(s));
1092
+ export function buildOauth2SigninPostBody(opts) {
1093
+ return {
1094
+ _csrf: opts.csrftoken,
1095
+ challenge: opts.challenge,
1096
+ account: opts.account,
1097
+ password: opts.passwordCipher,
1098
+ vcode: { id: "", content: "" },
1099
+ dualfactorauthinfo: {
1100
+ validcode: { vcode: "" },
1101
+ OTP: { OTP: "" },
1102
+ },
1103
+ remember: opts.remember,
1104
+ device: {
1105
+ name: "",
1106
+ description: "",
1107
+ client_type: "console_web",
1108
+ udids: [],
1109
+ },
1110
+ };
1108
1111
  }
1109
1112
  /**
1110
1113
  * OAuth2 Authorization Code login using HTTP **only**: `GET /oauth2/signin` (Next.js shell) and
@@ -1127,27 +1130,10 @@ export async function oauth2PasswordSigninLogin(baseUrl, options) {
1127
1130
  (typeof process.env.KWEAVER_OAUTH_PRODUCT === "string" && process.env.KWEAVER_OAUTH_PRODUCT.trim()
1128
1131
  ? process.env.KWEAVER_OAUTH_PRODUCT.trim()
1129
1132
  : "adp");
1130
- // Pre-flight: verify studioweb signin shell exists (same entry as deploy auto_config.sh get_token).
1131
- // If the deployment lacks studioweb, abort before OAuth client registration.
1132
- const studiowebProbeUrl = `${base}/interface/studioweb/login?lang=zh-cn&state=${encodeURIComponent(state)}` +
1133
- `&x-forwarded-prefix=&integrated=false&product=${encodeURIComponent(oauthProduct)}&_t=${Date.now()}`;
1134
- let probeResp;
1135
- try {
1136
- probeResp = await fetch(studiowebProbeUrl, { method: "GET", redirect: "manual" });
1137
- }
1138
- catch (cause) {
1139
- throw new Error(`Cannot reach studioweb signin endpoint at ${base}/interface/studioweb/login. ` +
1140
- `The deployment may not include studioweb. Use \`kweaver auth login ${base}\` ` +
1141
- `(OAuth code flow) instead.\n Cause: ${cause instanceof Error ? cause.message : String(cause)}`);
1142
- }
1143
- const probeOk2xx = probeResp.status >= 200 && probeResp.status < 300;
1144
- const probeOkRedirect = [301, 302, 303, 307, 308].includes(probeResp.status);
1145
- await probeResp.text().catch(() => "");
1146
- if (!probeOk2xx && !probeOkRedirect) {
1147
- throw new Error(`Studioweb signin endpoint not available at ${base}/interface/studioweb/login ` +
1148
- `(HTTP ${probeResp.status}). The deployment may not include studioweb. ` +
1149
- `Use \`kweaver auth login ${base}\` (OAuth code flow) instead.`);
1150
- }
1133
+ // Note: previously we pre-flighted `/interface/studioweb/login` to detect deployments
1134
+ // missing the Studio web shell. The probe added an extra round-trip and was unreliable
1135
+ // (see kweaver-admin which works fine without it). HTTP sign-in only needs `/oauth2/auth`
1136
+ // and `/oauth2/signin`; if either is missing the request below will surface a precise error.
1151
1137
  let client;
1152
1138
  try {
1153
1139
  client = await resolveOrRegisterClient(base, redirectUri, scope, {
@@ -1231,26 +1217,13 @@ export async function oauth2PasswordSigninLogin(baseUrl, options) {
1231
1217
  : options.signinPasswordBase64Plain === false
1232
1218
  ? false
1233
1219
  : process.env.KWEAVER_SIGNIN_PASSWORD_B64_RSA_MIN !== "1";
1234
- // Body shape matches browser `POST /oauth2/signin` (EACP / oauth2-ui); omitting vcode/dualfactorauthinfo
1235
- // causes 400 from eachttpserver (invalid parameter).
1236
- const postBody = {
1237
- _csrf: csrftoken,
1220
+ const postBody = buildOauth2SigninPostBody({
1221
+ csrftoken,
1238
1222
  challenge: loginChallenge,
1239
1223
  account: options.username,
1240
- password: "",
1241
- vcode: { id: "", content: "" },
1242
- dualfactorauthinfo: {
1243
- validcode: { vcode: "" },
1244
- OTP: { OTP: "" },
1245
- },
1224
+ passwordCipher: "",
1246
1225
  remember,
1247
- device: {
1248
- name: "",
1249
- description: "",
1250
- client_type: "unknown",
1251
- udids: [],
1252
- },
1253
- };
1226
+ });
1254
1227
  const origin = new URL(base).origin;
1255
1228
  /** Some gateways (e.g. DIP) return HTTP 200 + `{"redirect":"..."}` instead of 3xx Location. */
1256
1229
  let signinRedirectFromJson;
@@ -1310,6 +1283,26 @@ export async function oauth2PasswordSigninLogin(baseUrl, options) {
1310
1283
  }
1311
1284
  }
1312
1285
  else {
1286
+ if (postResp.status === 401) {
1287
+ try {
1288
+ const j = JSON.parse(bodyText);
1289
+ const c = j.code;
1290
+ if (c === 401001017 || c === "401001017") {
1291
+ const msg = typeof j.message === "string" && j.message.trim() !== ""
1292
+ ? j.message.trim()
1293
+ : "Initial password must be changed before login.";
1294
+ throw new InitialPasswordChangeRequiredError({
1295
+ account: options.username,
1296
+ baseUrl: base,
1297
+ serverMessage: msg,
1298
+ });
1299
+ }
1300
+ }
1301
+ catch (e) {
1302
+ if (e instanceof InitialPasswordChangeRequiredError)
1303
+ throw e;
1304
+ }
1305
+ }
1313
1306
  throw new HttpError(postResp.status, postResp.statusText, bodyText);
1314
1307
  }
1315
1308
  }
@@ -1652,6 +1645,9 @@ function isTlsVerificationDisabledForProcess() {
1652
1645
  process.env.KWEAVER_TLS_INSECURE === "true");
1653
1646
  }
1654
1647
  export function formatHttpError(error) {
1648
+ if (error instanceof InitialPasswordChangeRequiredError) {
1649
+ return `${error.serverMessage} (code ${error.code})`;
1650
+ }
1655
1651
  if (error instanceof HttpError) {
1656
1652
  const oauthMessage = formatOAuthErrorBody(error.body);
1657
1653
  if (oauthMessage) {
package/dist/cli.js CHANGED
@@ -23,9 +23,10 @@ Usage:
23
23
  kweaver --version | -V
24
24
  kweaver --help | -h
25
25
 
26
- kweaver auth <platform-url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--playwright] [--insecure|-k]
26
+ kweaver auth <platform-url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--new-password <pwd>] [--http-signin] [--insecure|-k]
27
27
  kweaver auth login <platform-url> (alias for auth <url>)
28
28
  kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (run on host without browser)
29
+ kweaver auth change-password [<platform-url>] [-u <account>] [-o <old>] [-n <new>] [--insecure|-k]
29
30
  kweaver auth whoami [platform-url|alias] [--json]
30
31
  kweaver auth export [platform-url|alias] [--json]
31
32
  kweaver auth status [platform-url|alias]