@kweaver-ai/kweaver-sdk 0.6.6 → 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.
package/README.md CHANGED
@@ -153,8 +153,11 @@ const skillMd = await client.skills.fetchContent("skill-id");
153
153
  ## CLI Reference
154
154
 
155
155
  ```
156
- kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--insecure|-k]
156
+ kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--new-password <pwd>] [--http-signin] [--insecure|-k]
157
157
  # -u/-p (with or without --http-signin): HTTP POST /oauth2/signin (yields refresh_token). Missing -u/-p are prompted from stdin (password hidden when TTY).
158
+ # If the server returns error 401001017 (initial password), TTY users get a prompt to set a new password; non-interactive scripts must pass --new-password <pwd>.
159
+ kweaver auth change-password [<url>] [-u <account>] [-o <old>] [-n <new>] [--insecure|-k]
160
+ # EACP POST /api/eacp/v1/auth1/modifypassword — no OAuth token required. Omit -o/-n on a TTY to be prompted.
158
161
  kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (headless login)
159
162
  kweaver auth export [url|alias] [--json] (export command to run on a headless host)
160
163
  kweaver auth status / whoami [url|alias] [--json] # whoami: --json; with KWEAVER_BASE_URL+KWEAVER_TOKEN when no ~/.kweaver/ platform
package/README.zh.md CHANGED
@@ -146,8 +146,10 @@ const skillMd = await client.skills.fetchContent("skill-id");
146
146
  ## 命令速查
147
147
 
148
148
  ```
149
- kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--insecure|-k]
149
+ kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--new-password <pwd>] [--http-signin] [--insecure|-k]
150
150
  # -u/-p(无论是否带 --http-signin):HTTP POST /oauth2/signin(可拿 refresh_token);缺失的用户名/密码会从 stdin 提示输入(TTY 下密码隐藏)
151
+ # 若服务端返回 401001017(初始密码),交互终端会引导修改;非交互请使用 --new-password <pwd>。
152
+ kweaver auth change-password [<url>] [-u <account>] [-o <old>] [-n <new>] [--insecure|-k]
151
153
  kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (无浏览器登录)
152
154
  kweaver auth export [url|alias] [--json] (导出在无浏览器机器上运行的命令)
153
155
  kweaver auth status / whoami [url|alias] [--json] # whoami 支持 --json;无 ~/.kweaver/ 当前平台时可配 KWEAVER_BASE_URL+KWEAVER_TOKEN
@@ -12,8 +12,8 @@ import { buildHeaders } from "./headers.js";
12
12
  // POST /tool-box/{id}/tools/status enable/disable (batch)
13
13
  //
14
14
  // Verified during Task 8 e2e against the live backend (2026-04-18):
15
- // GET /tool-box?keyword=&limit=&offset= list toolboxes
16
- // GET /tool-box/{id}/tool list tools
15
+ // GET /tool-box/list?keyword=&limit=&offset= list toolboxes
16
+ // GET /tool-box/{id}/tools/list list tools
17
17
  const PATH = "/api/agent-operator-integration/v1/tool-box";
18
18
  function url(base, suffix = "") {
19
19
  return `${base.replace(/\/+$/, "")}${PATH}${suffix}`;
@@ -74,7 +74,7 @@ export async function listToolboxes(opts) {
74
74
  qp.set("limit", String(opts.limit));
75
75
  if (opts.offset !== undefined)
76
76
  qp.set("offset", String(opts.offset));
77
- const suffix = qp.toString() ? `?${qp}` : "";
77
+ const suffix = `/list${qp.toString() ? `?${qp}` : ""}`;
78
78
  const { body } = await fetchTextOrThrow(url(opts.baseUrl, suffix), {
79
79
  method: "GET",
80
80
  headers: buildHeaders(opts.accessToken, opts.businessDomain ?? "bd_public"),
@@ -82,7 +82,7 @@ export async function listToolboxes(opts) {
82
82
  return body;
83
83
  }
84
84
  export async function listTools(opts) {
85
- const { body } = await fetchTextOrThrow(url(opts.baseUrl, `/${encodeURIComponent(opts.boxId)}/tool`), {
85
+ const { body } = await fetchTextOrThrow(url(opts.baseUrl, `/${encodeURIComponent(opts.boxId)}/tools/list`), {
86
86
  method: "GET",
87
87
  headers: buildHeaders(opts.accessToken, opts.businessDomain ?? "bd_public"),
88
88
  });
@@ -0,0 +1,25 @@
1
+ /** Encrypt a password with EACP modifypassword's RSA public key, base64-encoded. */
2
+ export declare function encryptModifyPwd(plain: string, publicKeyPem?: string): string;
3
+ /** @internal For unit tests: decrypt ciphertext produced by encryptModifyPwd with the embedded key. */
4
+ export declare function decryptModifyPwdForTest(cipherB64: string): string;
5
+ export interface EacpModifyPasswordOptions {
6
+ account: string;
7
+ oldPassword: string;
8
+ newPassword: string;
9
+ /** Override the embedded RSA public key (PEM). */
10
+ publicKeyPem?: string;
11
+ tlsInsecure?: boolean;
12
+ }
13
+ export interface EacpModifyPasswordResult {
14
+ status: number;
15
+ ok: boolean;
16
+ body: string;
17
+ json?: unknown;
18
+ }
19
+ /**
20
+ * Call EACP `POST /api/eacp/v1/auth1/modifypassword` to change a user's password
21
+ * when the old password is known (`isforgetpwd: false`).
22
+ *
23
+ * No bearer token / cookie is required — the endpoint authenticates by old password.
24
+ */
25
+ export declare function eacpModifyPassword(baseUrl: string, options: EacpModifyPasswordOptions): Promise<EacpModifyPasswordResult>;
@@ -0,0 +1,84 @@
1
+ import { constants as cryptoConstants, createPrivateKey, createPublicKey, privateDecrypt, publicEncrypt, } from "node:crypto";
2
+ import { normalizeBaseUrl, runWithTlsInsecure } from "./oauth.js";
3
+ /**
4
+ * 1024-bit RSA private key embedded in ShareServer
5
+ * (`isf/ShareServer/src/eachttpserver/ncEACHttpServerUtil.cpp`, function
6
+ * `ncEACHttpServerUtil::RSADecrypt`). It is the keypair used by the EACP
7
+ * `auth1/modifypassword` endpoint to decrypt `oldpwd` / `newpwd`.
8
+ *
9
+ * Note: this key is intentionally hard-coded in the C++ binary and shipped to
10
+ * every customer; it is not a secret. We embed it here so the CLI can perform
11
+ * the matching `RSA_PKCS1` encryption without contacting the server.
12
+ */
13
+ const EACP_MODIFYPWD_PRIVATE_KEY_PEM = `-----BEGIN RSA PRIVATE KEY-----
14
+ MIICXgIBAAKBgQDB2fhLla9rMx+6LWTXajnK11Kdp520s1Q+TfPfIXI/7G9+L2YC
15
+ 4RA3M5rgRi32s5+UFQ/CVqUFqMqVuzaZ4lw/uEdk1qHcP0g6LB3E9wkl2FclFR0M
16
+ +/HrWmxPoON+0y/tFQxxfNgsUodFzbdh0XY1rIVUIbPLvufUBbLKXHDPpwIDAQAB
17
+ AoGBALCM/H6ajXFs1nCR903aCVicUzoS9qckzI0SIhIOPCfMBp8+PAJTSJl9/ohU
18
+ YnhVj/kmVXwBvboxyJAmOcxdRPWL7iTk5nA1oiVXMer3Wby+tRg/ls91xQbJLVv3
19
+ oGSt7q0CXxJpRH2oYkVVlMMlZUwKz3ovHiLKAnhw+jEsdL2BAkEA9hA97yyeA2eq
20
+ f9dMu/ici99R3WJRRtk4NEI4WShtWPyziDg48d3SOzYmhEJjPuOo3g1ze01os70P
21
+ ApE7d0qcyQJBAMmt+FR8h5MwxPQPAzjh/fTuTttvUfBeMiUDrIycK1I/L96lH+fU
22
+ i4Nu+7TPOzExnPeGO5UJbZxrpIEUB7Zs8O8CQQCLzTCTGiNwxc5eMgH77kVrRudp
23
+ Q7nv6ex/7Hu9VDXEUFbkdyULbj9KuvppPJrMmWZROw04qgNp02mayM8jeLXZAkEA
24
+ o+PM/pMn9TPXiWE9xBbaMhUKXgXLd2KEq1GeAbHS/oY8l1hmYhV1vjwNLbSNrH9d
25
+ yEP73TQJL+jFiONHFTbYXwJAU03Xgum5mLIkX/02LpOrz2QCdfX1IMJk2iKi9osV
26
+ KqfbvHsF0+GvFGg18/FXStG9Kr4TjqLsygQJT76/MnMluw==
27
+ -----END RSA PRIVATE KEY-----`;
28
+ let cachedPubKey;
29
+ function getModifyPwdPublicKey() {
30
+ if (!cachedPubKey) {
31
+ cachedPubKey = createPublicKey(createPrivateKey(EACP_MODIFYPWD_PRIVATE_KEY_PEM));
32
+ }
33
+ return cachedPubKey;
34
+ }
35
+ /** Encrypt a password with EACP modifypassword's RSA public key, base64-encoded. */
36
+ export function encryptModifyPwd(plain, publicKeyPem) {
37
+ const key = publicKeyPem ? createPublicKey(publicKeyPem) : getModifyPwdPublicKey();
38
+ const buf = publicEncrypt({ key, padding: cryptoConstants.RSA_PKCS1_PADDING }, Buffer.from(plain, "utf8"));
39
+ return buf.toString("base64");
40
+ }
41
+ /** @internal For unit tests: decrypt ciphertext produced by encryptModifyPwd with the embedded key. */
42
+ export function decryptModifyPwdForTest(cipherB64) {
43
+ const key = createPrivateKey(EACP_MODIFYPWD_PRIVATE_KEY_PEM);
44
+ const buf = privateDecrypt({ key, padding: cryptoConstants.RSA_PKCS1_PADDING }, Buffer.from(cipherB64, "base64"));
45
+ return buf.toString("utf8");
46
+ }
47
+ /**
48
+ * Call EACP `POST /api/eacp/v1/auth1/modifypassword` to change a user's password
49
+ * when the old password is known (`isforgetpwd: false`).
50
+ *
51
+ * No bearer token / cookie is required — the endpoint authenticates by old password.
52
+ */
53
+ export async function eacpModifyPassword(baseUrl, options) {
54
+ return runWithTlsInsecure(options.tlsInsecure, async () => {
55
+ const body = {
56
+ account: options.account,
57
+ oldpwd: encryptModifyPwd(options.oldPassword, options.publicKeyPem),
58
+ newpwd: encryptModifyPwd(options.newPassword, options.publicKeyPem),
59
+ vcodeinfo: {
60
+ uuid: "",
61
+ vcode: "",
62
+ },
63
+ isforgetpwd: false,
64
+ };
65
+ const url = `${normalizeBaseUrl(baseUrl)}/api/eacp/v1/auth1/modifypassword`;
66
+ const resp = await fetch(url, {
67
+ method: "POST",
68
+ headers: {
69
+ "Content-Type": "application/json",
70
+ Accept: "application/json, text/plain, */*",
71
+ },
72
+ body: JSON.stringify(body),
73
+ });
74
+ const text = await resp.text();
75
+ let json;
76
+ try {
77
+ json = text ? JSON.parse(text) : undefined;
78
+ }
79
+ catch {
80
+ /* not JSON */
81
+ }
82
+ return { status: resp.status, ok: resp.ok, body: text, json };
83
+ });
84
+ }
@@ -1,4 +1,17 @@
1
1
  import { type TokenConfig } from "../config/store.js";
2
+ /** Thrown when `POST /oauth2/signin` returns HTTP 401 with EACP code `401001017` (initial password must be changed). */
3
+ export declare class InitialPasswordChangeRequiredError extends Error {
4
+ readonly code = 401001017;
5
+ readonly account: string;
6
+ readonly baseUrl: string;
7
+ readonly httpStatus = 401;
8
+ readonly serverMessage: string;
9
+ constructor(opts: {
10
+ account: string;
11
+ baseUrl: string;
12
+ serverMessage: string;
13
+ });
14
+ }
2
15
  /**
3
16
  * Studioweb hardcoded LOGIN public key (PEM) — the single key used for HTTP `/oauth2/signin`.
4
17
  * Source: kweaver-ai/kweaver `deploy/auto_cofig/auto_config.sh` `LOGIN_PUBLIC_KEY`.
@@ -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;
@@ -1268,6 +1283,26 @@ export async function oauth2PasswordSigninLogin(baseUrl, options) {
1268
1283
  }
1269
1284
  }
1270
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
+ }
1271
1306
  throw new HttpError(postResp.status, postResp.statusText, bodyText);
1272
1307
  }
1273
1308
  }
@@ -1610,6 +1645,9 @@ function isTlsVerificationDisabledForProcess() {
1610
1645
  process.env.KWEAVER_TLS_INSECURE === "true");
1611
1646
  }
1612
1647
  export function formatHttpError(error) {
1648
+ if (error instanceof InitialPasswordChangeRequiredError) {
1649
+ return `${error.serverMessage} (code ${error.code})`;
1650
+ }
1613
1651
  if (error instanceof HttpError) {
1614
1652
  const oauthMessage = formatOAuthErrorBody(error.body);
1615
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] [--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]
@@ -1,7 +1,8 @@
1
1
  import { isNoAuth } from "../config/no-auth.js";
2
- import { autoSelectBusinessDomain, clearPlatformSession, deletePlatform, deleteUser, getActiveUser, getConfigDir, getCurrentPlatform, getPlatformAlias, hasPlatform, listPlatforms, listUserProfiles, loadClientConfig, loadTokenConfig, resolveBusinessDomain, resolvePlatformIdentifier, resolveUserId, saveNoAuthPlatform, setActiveUser, setCurrentPlatform, setPlatformAlias, } from "../config/store.js";
2
+ import { autoSelectBusinessDomain, clearPlatformSession, deletePlatform, deleteUser, getActiveUser, getConfigDir, getCurrentPlatform, getPlatformAlias, hasPlatform, listPlatforms, listUserProfiles, loadClientConfig, loadTokenConfig, loadUserTokenConfig, resolveBusinessDomain, resolvePlatformIdentifier, resolveUserId, saveNoAuthPlatform, setActiveUser, setCurrentPlatform, setPlatformAlias, } from "../config/store.js";
3
3
  import { decodeJwtPayload } from "../config/jwt.js";
4
- import { buildCopyCommand, formatHttpError, normalizeBaseUrl, oauth2Login, oauth2PasswordSigninLogin, promptForUsername, promptForPassword, refreshTokenLogin, } from "../auth/oauth.js";
4
+ import { eacpModifyPassword } from "../auth/eacp-modify-password.js";
5
+ import { buildCopyCommand, formatHttpError, InitialPasswordChangeRequiredError, normalizeBaseUrl, oauth2Login, oauth2PasswordSigninLogin, promptForUsername, promptForPassword, refreshTokenLogin, } from "../auth/oauth.js";
5
6
  export async function runAuthCommand(args) {
6
7
  const target = args[0];
7
8
  const rest = args.slice(1);
@@ -17,6 +18,7 @@ kweaver auth users [url|alias] List all user profiles (with usernames) for
17
18
  kweaver auth switch [url|alias] --user <id|username> Switch active user for a platform
18
19
  kweaver auth logout [url|alias] [--user <id>] Logout (clear local token)
19
20
  kweaver auth delete <url|alias> [--user <id>] Delete saved credentials
21
+ kweaver auth change-password [<url>] [-u <account>] [-o <old>] [-n <new>] Change password
20
22
 
21
23
  Login options:
22
24
  --alias <name> Save platform with a short alias (use with use / status / logout)
@@ -34,13 +36,14 @@ Login options:
34
36
  -u, --username Username for HTTP /oauth2/signin (POST). If -p is omitted, password is prompted.
35
37
  -p, --password Password for HTTP /oauth2/signin (POST). If -u is omitted, username is prompted.
36
38
  --http-signin Force HTTP /oauth2/signin (no browser). Missing -u/-p are prompted from stdin.
39
+ --new-password <pwd> After HTTP sign-in error 401001017 (initial password), set the new password non-interactively, then retry login.
37
40
  --insecure, -k Skip TLS certificate verification (self-signed / dev HTTPS only)
38
41
  --no-auth Save platform without OAuth (servers with no authentication). Same as detecting OAuth 404 during login.`);
39
42
  return 0;
40
43
  }
41
44
  if (target === "login") {
42
45
  if (rest[0] === "--help" || rest[0] === "-h") {
43
- console.log(`kweaver auth login <platform-url> [--alias <name>] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--refresh-token T --client-id ID --client-secret S]`);
46
+ console.log(`kweaver auth login <platform-url> [--alias <name>] [--no-auth] [--no-browser] [-u user] [-p pass] [--new-password <pwd>] [--http-signin] [--refresh-token T --client-id ID --client-secret S]`);
44
47
  return 0;
45
48
  }
46
49
  const url = rest[0];
@@ -62,6 +65,9 @@ Login options:
62
65
  if (target === "switch") {
63
66
  return runAuthSwitchCommand(rest);
64
67
  }
68
+ if (target === "change-password") {
69
+ return runAuthChangePasswordCommand(rest);
70
+ }
65
71
  const LOGIN_SUBCOMMANDS = new Set(["status", "list", "use", "delete", "logout", "export", "whoami", "users", "switch"]);
66
72
  if (target && !LOGIN_SUBCOMMANDS.has(target)) {
67
73
  try {
@@ -80,6 +86,7 @@ Login options:
80
86
  const tlsInsecure = args.includes("--insecure") || args.includes("-k");
81
87
  const noAuth = args.includes("--no-auth");
82
88
  const noBrowser = args.includes("--no-browser");
89
+ const newPasswordFlag = readOption(args, "--new-password");
83
90
  if (args.includes("--redirect-uri")) {
84
91
  console.error("Warning: --redirect-uri is deprecated and ignored. The redirect URI is always http://127.0.0.1:<port>/callback.");
85
92
  }
@@ -87,6 +94,7 @@ Login options:
87
94
  "--alias", "--client-id", "--client-secret", "--refresh-token",
88
95
  "--port", "--no-browser", "--username", "-u", "--password", "-p",
89
96
  "--http-signin",
97
+ "--new-password",
90
98
  "--oauth-product",
91
99
  "--signin-public-key-file",
92
100
  "--insecure", "-k", "--no-auth", "--redirect-uri",
@@ -94,6 +102,7 @@ Login options:
94
102
  const KNOWN_VALUE_FLAGS = new Set([
95
103
  "--alias", "--client-id", "--client-secret", "--refresh-token",
96
104
  "--port", "--username", "-u", "--password", "-p", "--redirect-uri",
105
+ "--new-password",
97
106
  "--oauth-product",
98
107
  "--signin-public-key-file",
99
108
  ]);
@@ -122,6 +131,10 @@ Login options:
122
131
  console.error("--no-auth cannot be used with HTTP sign-in or -u/-p.");
123
132
  return 1;
124
133
  }
134
+ if (newPasswordFlag !== undefined && (!username || !password)) {
135
+ console.error("--new-password requires -u/--username and -p/--password (HTTP sign-in).");
136
+ return 1;
137
+ }
125
138
  if (noBrowser && httpSignin) {
126
139
  // HTTP sign-in already runs without a browser; --no-browser is a no-op signal here.
127
140
  console.error("--http-signin already runs without a browser; --no-browser is redundant and ignored.");
@@ -161,22 +174,9 @@ Login options:
161
174
  clientId, clientSecret, refreshToken, tlsInsecure,
162
175
  });
163
176
  }
164
- else if (username && password && httpSignin) {
165
- console.log("Logging in (HTTP /oauth2/signin)...");
166
- token = await oauth2PasswordSigninLogin(normalizedTarget, {
167
- username,
168
- password,
169
- tlsInsecure,
170
- port: customPort,
171
- clientId: clientId ?? undefined,
172
- clientSecret: clientSecret ?? undefined,
173
- oauthProduct: oauthProduct ?? undefined,
174
- signinPublicKeyPemPath: signinPublicKeyFile ?? undefined,
175
- });
176
- }
177
177
  else if (username && password) {
178
178
  console.log("Logging in (HTTP /oauth2/signin)...");
179
- token = await oauth2PasswordSigninLogin(normalizedTarget, {
179
+ token = await loginWithInitialPasswordRecovery(normalizedTarget, {
180
180
  username,
181
181
  password,
182
182
  tlsInsecure,
@@ -185,7 +185,7 @@ Login options:
185
185
  clientSecret: clientSecret ?? undefined,
186
186
  oauthProduct: oauthProduct ?? undefined,
187
187
  signinPublicKeyPemPath: signinPublicKeyFile ?? undefined,
188
- });
188
+ }, { newPasswordFlag, tlsInsecure });
189
189
  }
190
190
  else {
191
191
  if (noBrowser) {
@@ -635,3 +635,217 @@ function readOption(args, name) {
635
635
  }
636
636
  return args[index + 1];
637
637
  }
638
+ const EACP_NEW_PWD_MIN = 6;
639
+ const EACP_NEW_PWD_MAX = 100;
640
+ function validateNewPasswordLengthForEacp(pwd) {
641
+ if (pwd.length < EACP_NEW_PWD_MIN || pwd.length > EACP_NEW_PWD_MAX) {
642
+ throw new Error(`New password must be between ${EACP_NEW_PWD_MIN} and ${EACP_NEW_PWD_MAX} characters.`);
643
+ }
644
+ }
645
+ function formatEacpModifyFailure(status, json, body) {
646
+ if (json && typeof json === "object" && json !== null) {
647
+ const o = json;
648
+ const msg = typeof o.message === "string" && o.message.trim() !== ""
649
+ ? o.message
650
+ : typeof o.cause === "string"
651
+ ? o.cause
652
+ : "";
653
+ if (msg)
654
+ return `Password change failed (HTTP ${status}): ${msg}`;
655
+ }
656
+ return `Password change failed (HTTP ${status}): ${body.slice(0, 500)}`;
657
+ }
658
+ async function promptYesNo(message) {
659
+ const { createInterface } = await import("node:readline");
660
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
661
+ return await new Promise((resolve, reject) => {
662
+ let answered = false;
663
+ rl.on("close", () => {
664
+ if (!answered)
665
+ reject(new Error("Login cancelled."));
666
+ });
667
+ rl.question(`${message} [Y/n] `, (answer) => {
668
+ answered = true;
669
+ rl.close();
670
+ const a = answer.trim().toLowerCase();
671
+ resolve(a === "" || a === "y" || a === "yes");
672
+ });
673
+ });
674
+ }
675
+ async function loginWithInitialPasswordRecovery(normalizedTarget, signinOpts, recovery) {
676
+ try {
677
+ return await oauth2PasswordSigninLogin(normalizedTarget, signinOpts);
678
+ }
679
+ catch (e) {
680
+ if (!(e instanceof InitialPasswordChangeRequiredError))
681
+ throw e;
682
+ const err = e;
683
+ const account = signinOpts.username;
684
+ const oldPwd = signinOpts.password;
685
+ let newPwd = recovery.newPasswordFlag;
686
+ if (newPwd !== undefined) {
687
+ validateNewPasswordLengthForEacp(newPwd);
688
+ }
689
+ else if (process.stderr.isTTY) {
690
+ process.stderr.write(`${err.serverMessage}\n`);
691
+ const ok = await promptYesNo(`Account "${account}" must change its initial password. Proceed with password change now?`);
692
+ if (!ok) {
693
+ throw new Error("Initial password change declined. Run again when ready.");
694
+ }
695
+ const np1 = await promptForPassword("New password (6-100 characters)");
696
+ const np2 = await promptForPassword("Confirm new password");
697
+ if (np1 !== np2) {
698
+ throw new Error("New passwords do not match.");
699
+ }
700
+ validateNewPasswordLengthForEacp(np1);
701
+ newPwd = np1;
702
+ }
703
+ else {
704
+ throw new Error("This account must change its initial password (error 401001017). Re-run with --new-password <password> (non-interactive).");
705
+ }
706
+ const mod = await eacpModifyPassword(normalizedTarget, {
707
+ account,
708
+ oldPassword: oldPwd,
709
+ newPassword: newPwd,
710
+ tlsInsecure: recovery.tlsInsecure,
711
+ });
712
+ if (!mod.ok) {
713
+ throw new Error(formatEacpModifyFailure(mod.status, mod.json, mod.body));
714
+ }
715
+ return oauth2PasswordSigninLogin(normalizedTarget, {
716
+ ...signinOpts,
717
+ password: newPwd,
718
+ });
719
+ }
720
+ }
721
+ async function runAuthChangePasswordCommand(args) {
722
+ if (args[0] === "--help" || args[0] === "-h") {
723
+ console.log(`kweaver auth change-password [<platform-url>] [options]
724
+
725
+ Change the EACP account password via POST /api/eacp/v1/auth1/modifypassword.
726
+ No saved OAuth token is required.
727
+
728
+ Options:
729
+ -u, --account <name> Account / login name. On TTY, defaults to the current active user
730
+ after a confirmation prompt. Required in non-interactive mode.
731
+ -o, --old-password <pwd> Current password (omit on TTY to be prompted)
732
+ -n, --new-password <pwd> New password, 6-100 characters (omit on TTY to be prompted)
733
+ --insecure, -k Skip TLS certificate verification (defaults to the platform's saved
734
+ preference set at login with -k; pass to override per-call)
735
+
736
+ Platform URL is optional; defaults to the current active platform (kweaver auth use).`);
737
+ return 0;
738
+ }
739
+ const KNOWN_CP_FLAGS = new Set([
740
+ "-u",
741
+ "--account",
742
+ "-o",
743
+ "--old-password",
744
+ "-n",
745
+ "--new-password",
746
+ "--insecure",
747
+ "-k",
748
+ "--help",
749
+ "-h",
750
+ ]);
751
+ const KNOWN_CP_VALUE = new Set([
752
+ "-u",
753
+ "--account",
754
+ "-o",
755
+ "--old-password",
756
+ "-n",
757
+ "--new-password",
758
+ ]);
759
+ // First positional (if present and not a flag) is the platform URL or alias.
760
+ const positional = args[0] && !args[0].startsWith("-") ? args[0] : undefined;
761
+ const flagArgs = positional ? args.slice(1) : args;
762
+ for (let i = 0; i < flagArgs.length; i++) {
763
+ const a = flagArgs[i];
764
+ if (a.startsWith("-") && !KNOWN_CP_FLAGS.has(a)) {
765
+ console.error(`Unknown option: ${a}`);
766
+ console.error("Run 'kweaver auth change-password --help' for usage.");
767
+ return 1;
768
+ }
769
+ if (KNOWN_CP_VALUE.has(a))
770
+ i++;
771
+ }
772
+ const normalizedTarget = resolvePlatformArg(positional ? [positional] : []);
773
+ if (!normalizedTarget) {
774
+ console.error("No platform resolved. Pass <platform-url|alias> or run `kweaver auth use <url|alias>` first.");
775
+ return 1;
776
+ }
777
+ let account = readOption(flagArgs, "--account") ?? readOption(flagArgs, "-u");
778
+ let oldPassword = readOption(flagArgs, "--old-password") ?? readOption(flagArgs, "-o");
779
+ let newPassword = readOption(flagArgs, "--new-password") ?? readOption(flagArgs, "-n");
780
+ const explicitTlsInsecure = flagArgs.includes("--insecure") || flagArgs.includes("-k");
781
+ // Resolve the active user's saved token; we use it both to default the account
782
+ // and to inherit the platform's saved tlsInsecure preference (set at login with -k).
783
+ const activeUser = getActiveUser(normalizedTarget);
784
+ const activeToken = activeUser ? loadUserTokenConfig(normalizedTarget, activeUser) : null;
785
+ const tlsInsecure = explicitTlsInsecure || activeToken?.tlsInsecure === true;
786
+ const interactive = process.stdin.isTTY === true && process.stderr.isTTY === true;
787
+ const accountWasExplicit = !!account?.trim();
788
+ // Account resolution (with safety guards):
789
+ // - Explicit -u always wins.
790
+ // - Non-TTY + no -u: REFUSE. Silently using the active account in CI / pipes
791
+ // would let scripts modify the wrong account's password without warning.
792
+ // - TTY + no -u: default to the active user's displayName, but require an
793
+ // interactive yes/no confirmation before proceeding.
794
+ if (!accountWasExplicit) {
795
+ const defaultAccount = activeToken?.displayName?.trim();
796
+ if (!defaultAccount) {
797
+ console.error("Cannot determine current account on the platform. Pass -u/--account, or log in first (kweaver auth login ...).");
798
+ return 1;
799
+ }
800
+ if (!interactive) {
801
+ console.error(`Refusing to default account in non-interactive mode. Pass -u/--account explicitly (would have used "${defaultAccount}").`);
802
+ return 1;
803
+ }
804
+ const ok = await promptYesNo(`Change password for account "${defaultAccount}" on ${normalizedTarget}?`);
805
+ if (!ok) {
806
+ console.error("Aborted by user.");
807
+ return 1;
808
+ }
809
+ account = defaultAccount;
810
+ }
811
+ const trimmedAccount = account.trim();
812
+ try {
813
+ if (!interactive) {
814
+ if (!oldPassword || !newPassword) {
815
+ console.error("In non-interactive mode, --old-password and --new-password are required.");
816
+ return 1;
817
+ }
818
+ }
819
+ else {
820
+ if (!oldPassword) {
821
+ oldPassword = await promptForPassword("Old password");
822
+ }
823
+ if (!newPassword) {
824
+ const n1 = await promptForPassword("New password (6-100 characters)");
825
+ const n2 = await promptForPassword("Confirm new password");
826
+ if (n1 !== n2) {
827
+ console.error("New passwords do not match.");
828
+ return 1;
829
+ }
830
+ newPassword = n1;
831
+ }
832
+ }
833
+ validateNewPasswordLengthForEacp(newPassword);
834
+ const result = await eacpModifyPassword(normalizedTarget, {
835
+ account: trimmedAccount,
836
+ oldPassword: oldPassword,
837
+ newPassword: newPassword,
838
+ tlsInsecure,
839
+ });
840
+ if (!result.ok) {
841
+ console.error(`${formatEacpModifyFailure(result.status, result.json, result.body)} (account="${trimmedAccount}")`);
842
+ return 1;
843
+ }
844
+ console.log(`Password changed for ${trimmedAccount} on ${normalizedTarget}`);
845
+ return 0;
846
+ }
847
+ catch (e) {
848
+ console.error(`${formatHttpError(e)}\n(account="${trimmedAccount}")`);
849
+ return 1;
850
+ }
851
+ }
package/dist/index.d.ts CHANGED
@@ -62,4 +62,5 @@ export type { TokenConfig, ContextLoaderEntry, ContextLoaderConfig, } from "./co
62
62
  export type { UserProfile } from "./config/store.js";
63
63
  export { NO_AUTH_TOKEN, isNoAuth, saveNoAuthPlatform, autoSelectBusinessDomain, getConfigDir, getCurrentPlatform, getActiveUser, setActiveUser, listUsers, listUserProfiles, resolveUserId, extractUserId, } from "./config/store.js";
64
64
  export { decodeJwtPayload, extractUserIdFromJwt } from "./config/jwt.js";
65
- export { DEFAULT_SIGNIN_RSA_MODULUS_HEX, oauth2PasswordSigninLogin, parseSigninPageHtmlProps, rsaModulusHexToSpkiPem, STUDIOWEB_LOGIN_PUBLIC_KEY_PEM, } from "./auth/oauth.js";
65
+ export { DEFAULT_SIGNIN_RSA_MODULUS_HEX, InitialPasswordChangeRequiredError, oauth2PasswordSigninLogin, parseSigninPageHtmlProps, rsaModulusHexToSpkiPem, STUDIOWEB_LOGIN_PUBLIC_KEY_PEM, } from "./auth/oauth.js";
66
+ export { eacpModifyPassword, encryptModifyPwd } from "./auth/eacp-modify-password.js";
package/dist/index.js CHANGED
@@ -50,4 +50,5 @@ export { NO_AUTH_TOKEN, isNoAuth, saveNoAuthPlatform, autoSelectBusinessDomain,
50
50
  // ── JWT utilities ─────────────────────────────────────────────────────────────
51
51
  export { decodeJwtPayload, extractUserIdFromJwt } from "./config/jwt.js";
52
52
  // ── OAuth (advanced — CLI uses these internally; optional for custom login tools) ─
53
- export { DEFAULT_SIGNIN_RSA_MODULUS_HEX, oauth2PasswordSigninLogin, parseSigninPageHtmlProps, rsaModulusHexToSpkiPem, STUDIOWEB_LOGIN_PUBLIC_KEY_PEM, } from "./auth/oauth.js";
53
+ export { DEFAULT_SIGNIN_RSA_MODULUS_HEX, InitialPasswordChangeRequiredError, oauth2PasswordSigninLogin, parseSigninPageHtmlProps, rsaModulusHexToSpkiPem, STUDIOWEB_LOGIN_PUBLIC_KEY_PEM, } from "./auth/oauth.js";
54
+ export { eacpModifyPassword, encryptModifyPwd } from "./auth/eacp-modify-password.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kweaver-ai/kweaver-sdk",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
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",