@xbongbong/xbbcli 2.4.3 → 2.4.4

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
@@ -91,9 +91,9 @@ xbbcli auth-login --corpid <CORPID> # 校验登录的企业,不一致时报
91
91
  xbbcli auth-login --keepOpen # 登录成功后保留浏览器窗口(默认自动关闭)
92
92
  ```
93
93
 
94
- 执行后与 `token-set` 等价:`--env 0`(默认)写入 `~/.xbbcli/config.env`、刷新表单缓存、命令映射与部门/员工缓存(不写环境变量);`--env 1` 仅写入 `XBB_*` 环境变量,不写任何本地文件。
94
+ 执行后与 `token-set` 等价:`--env 0`(默认)写入 `~/.xbbcli/config.env`、刷新表单缓存、命令映射与部门/员工缓存(不写环境变量);`--env 1` 仅写入 `XBB_*` 环境变量(供外部系统使用,本 CLI 不读取),不写任何本地文件。
95
95
 
96
- 技术实现:零依赖,用 `node:child_process` 以 `--remote-debugging-pipe` 启动浏览器(fd3 写 / fd4 读 CDP,**不开任何调试端口**),profile 固定为 `~/.xbbcli/browser-profile`(与日常浏览器隔离,登录态可复用);CLI 读取页面 `localStorage` 的 `{corpid, userId, xbbAccessToken}`,再在页面内 `fetch` 网关 `apiToken/getApiToken` 换取个人 token。只支持 Chromium 内核浏览器(Chrome / Edge / Chromium),更多细节见 [`doc/auth-login-command-design.md`](doc/auth-login-command-design.md)。
96
+ 技术实现:零依赖,用 `node:child_process` 以 `--remote-debugging-pipe` 启动浏览器(fd3 写 / fd4 读 CDP,**不开任何调试端口**),profile 固定为 `~/.xbbcli/browser-profile`(与日常浏览器隔离,登录态可复用);CLI 读取页面 `localStorage` 的 `{corpid, userId, xbbAccessToken}`,再在页面内 `fetch` 网关 `apiPersonalToken/generate` 换取个人 token。只支持 Chromium 内核浏览器(Chrome / Edge / Chromium),更多细节见 [`doc/auth-login-command-design.md`](doc/auth-login-command-design.md)。
97
97
 
98
98
  - `--debug` 输出请求体、返回体与浏览器信息(密钥只显示掩码),`--raw` 输出换取 API token 接口的原始响应
99
99
  - 失败时返回合成错误行:`NO_BROWSER`、`BROWSER_CLOSED`、`LOGIN_TIMEOUT`、`API_TOKEN_FAILED`、`CORPID_MISMATCH`、`SAVE_FAILED` 等
@@ -107,9 +107,8 @@ xbbcli auth-refresh-cache -f json
107
107
  ```
108
108
 
109
109
  - 刷新范围(仅当前启用公司):`~/.xbbcli/<corpid>.formlist.json`(表单清单)、`~/.xbbcli/<corpid>.command-map.md`(命令映射)、`~/.xbbcli/<corpid>.department-user.json`(部门/员工清单),并把 `corpName` / `userName` 回填进 `config.env`
110
- - 环境变量模式(`XBB_*`)下同样可用:只刷新缓存文件,不回填 `config.env`
111
110
  - 只支持当前启用公司:底层接口一律使用启用公司的凭证,切换公司请先用 `xbbcli token-use`
112
- - `--userId` 覆盖用于匹配 `userName` 的员工 id(默认取配置里的 `userId`);`--debug` 在 stderr 输出配置来源、corpid 与三份缓存文件路径
111
+ - `--userId` 覆盖用于匹配 `userName` 的员工 id(默认取配置里的 `userId`);`--debug` 在 stderr 输出 corpid、userId 与三份缓存文件路径
113
112
  - 失败时返回 `status=error`、`code=CACHE_REFRESH_FAILED`,`message` 以「步骤 formlist / command-map / department-user / profile」指出中断位置,已完成的部分照常在对应列输出
114
113
 
115
114
  `token-set` 会按 `corpid` 新增或覆盖一家公司,并把该公司置为 `enable=true`(其余公司自动置为 `false`),同时写入公司名称 `corpName`(取部门 `id` 为 `1` 的部门名称)和操作人姓名 `userName`(从员工列表中按 `userId` 匹配)。除 `token-set`、`token-list`、`token-use`、`token-del`、`auth-status`、`auth-logout` 外,其余命令都使用 `enable` 为 `true` 的公司配置。
@@ -173,22 +172,22 @@ userId 02415643151585
173
172
  baseurl https://proapi.xbongbong.com
174
173
  token user_6***a7da
175
174
  验证 有效
176
- note 以上取值来自环境变量 XBB_*(config.env 中没有启用公司,回落到环境变量);环境变量模式不支持多公司切换
175
+ note config.env 中存在 2 个 enable=true,仅第一条生效,请用 xbbcli token-use 修正
177
176
  ```
178
177
 
179
- - 来源(`config.env` 启用公司 还是 `XBB_*`)与冲突提醒只在需要时以 `note` 行追加:环境变量与 `config.env` 同时存在时提示「环境变量优先,`config.env` 中启用(`enable=true`)的公司被覆盖」并提示清除环境变量可切回配置文件;`config.env` 里出现多个 `enable=true` 时提示用 `token-use` 修正。
178
+ - 冲突提醒只在需要时以 `note` 行追加:`config.env` 里出现多个 `enable=true` 时提示用 `token-use` 修正;没有任何生效配置时提示下一步命令,若同时检测到 `XBB_*` 环境变量,会追加一条「检测到 XBB_* 环境变量,但本版本不再读取环境变量」。
180
179
  - `auth-status` 的 `验证` 行会实打实发起一次 `user-list --pageSize 1` 请求(等价于手工执行 `xbbcli user-list --pageSize 1`):接口成功(含 `NO_DATA` 空清单)→ `有效`;接口返回业务错误码或请求异常 → `无效-请重新配置token`。没有生效配置或缺少 token 时不再发起请求,直接给出 `无效-请重新配置token`;`--debug` 会输出判定结果与原因。
181
- - 没有任何生效配置时,6 个配置值一律留空(`验证` 行为 `无效-请重新配置token`)并给出 `note` 提示下一步命令(即使 `XBB_BASEURL` / `XBB_USERID` 单独残留也不会当成生效值回显)。配置文件路径、公司数量、生效的环境变量名改用 `--debug` 输出到 stderr。
180
+ - 没有任何生效配置时,6 个配置值一律留空(`验证` 行为 `无效-请重新配置token`)并给出 `note` 提示下一步命令(即使 `XBB_BASEURL` / `XBB_USERID` 单独残留也不会当成生效值回显);检测到 `XBB_*` 环境变量时追加提示「本版本不再读取环境变量」。配置文件路径、公司数量与检测到的 `XBB_*` 环境变量名改用 `--debug` 输出到 stderr。
182
181
  - `auth-status` 默认把 token 掩码为「前 6 + `***` + 后 4」(长度不足 11 时只保留前 2 位),只有 `--showToken` 才输出明文。
183
182
  - `auth-logout` 只删除 `enable=true` 的那一条,**不会**把其他公司自动提升为启用:登出后本地可能一个启用公司都没有,需要 `xbbcli token-use --corpid <CORPID>` 或重新 `auth-login` / `token-set`。想保留其他公司并可切换时,用 `xbbcli token-del --corpid <CORPID>`(它会保证剩余配置仍有且仅有一个启用;默认还会删除 `XBB_*` 环境变量,传 `--env 0` 可保留)。
184
183
  - 删除前 `config.env` 会整份备份到 `~/.xbbcli/config.env.bak`(每次登出覆盖),`backupFile` 列给出路径;误删把 `.bak` 复制回 `config.env` 即可恢复。
185
- - 默认**同时**清除 `XBB_*` 环境变量:Windows 用 `reg delete HKCU\Environment` 删除用户级变量(已打开的终端要重开才生效,`envStored` 为 `user`,原本就不存在时为 `none`);macOS/Linux 清理 `~/.xbbcli/env.sh` 里的 `export XBB_*` 行(文件清空则删除,`envStored` 为 `cleared` / `removed` / `none`)。传 `--env 0` 时只删 `config.env`,此时若环境变量仍在,`message` 会提示「其优先级高于 config.env,业务命令仍会使用它,本次不算彻底登出」(`XBB_ENV_ONLY=1` 时提示为「已强制只用环境变量」)。
184
+ - 默认**同时**清除 `XBB_*` 环境变量:Windows 用 `reg delete HKCU\Environment` 删除用户级变量(已打开的终端要重开才生效,`envStored` 为 `user`,原本就不存在时为 `none`);macOS/Linux 清理 `~/.xbbcli/env.sh` 里的 `export XBB_*` 行(文件清空则删除,`envStored` 为 `cleared` / `removed` / `none`)。传 `--env 0` 时只删 `config.env`,此时若环境变量仍在,`message` 会提示环境变量仍存在(供外部系统使用;业务命令不再读取),并给出改用 `--env 1` 一并清除的建议。
186
185
  - `config.env` 没有启用公司时:只有在环境变量确实存在时才只清环境变量(`config.env` 保持不动);两者都不存在则返回 `NO_ACTIVE_CONFIG`,不写任何文件。
187
186
  - 任何情况都不改动 `<corpid>.formlist.json` / `<corpid>.command-map.md` / `<corpid>.department-user.json` 三份缓存,重新启用该公司时无需再次拉取。
188
187
 
189
- ## 环境变量模式(单公司)
188
+ ## 环境变量输出(供外部集成)
190
189
 
191
- 除 `config.env` 外,命令也支持从环境变量读取一家公司的凭证,便于 CI、容器与其他工具集成:
190
+ 自本版本起,业务命令不再读取 `XBB_*` 环境变量,凭证来源唯一化为 `config.env` 中 `enable=true` 的公司;`token-set` / `auth-login` 的 `--env 1` 仍会把凭证写入环境变量,供 CI、容器与其他外部工具集成消费:
192
191
 
193
192
  | 环境变量 | 对应字段 |
194
193
  | --- | --- |
@@ -199,34 +198,26 @@ note 以上取值来自环境变量 XBB_*(config.env 中没有启用公
199
198
  | `XBB_CORPNAME` | `corpName` |
200
199
  | `XBB_USERNAME` | `userName` |
201
200
 
202
- 读取优先级:
203
-
204
- 1. 环境变量 `XBB_*` 中 `XBB_CORPID` / `XBB_TOKEN` 有值时,优先使用环境变量(`config.env` 中 `enable=true` 的公司被覆盖,命令会以环境变量为准)
205
- 2. 环境变量不存在时,使用 `config.env` 中存在 `enable=true` 的公司(多公司切换行为不变)
206
- 3. 设置 `XBB_ENV_ONLY=1` 可强制只使用环境变量(完全忽略 `config.env`)
207
-
208
- > 注意:由于环境变量优先,早先 `--env 1` 写入的 `XBB_*` 会覆盖之后 `token-set --env 0` 写入的 `config.env`(`token-set` 的 `message` 会给出提示)。要切回 `config.env` 请先清除 `XBB_*` 环境变量,或执行 `xbbcli auth-logout`(默认会一并清除)。
209
-
210
201
  `token-set` / `auth-login` 通过 `--env` 控制存储方式:`--env 0`(默认)仅写入本地文件(`config.env` 与各类缓存),`--env 1` 仅写入环境变量(不写任何本地文件)。选 `--env 1` 时会把 `corpid`/`token`/`baseurl`/`userId` 写入环境变量:
211
202
 
212
203
  - Windows:用 `setx` 写入用户级环境变量,**只对新开的终端生效**
213
204
  - macOS/Linux:写入 `~/.xbbcli/env.sh`(`export` 形式,权限 600),需要 `source ~/.xbbcli/env.sh` 才在当前 shell 生效
214
205
 
215
- 环境变量模式只支持一家公司:此时 `token-list` 会把环境变量行(`source=env`)与 `config.env` 中的公司行(`source=config`)一起列出,并在 `msg` 列标注各自当前是否生效;`token-use` 会在切换 `config.env` 启用公司的同时,把该公司凭证同步写回 `XBB_*` 环境变量(目标公司缺 `token` 时拒绝切换,避免残留旧 `XBB_TOKEN`);`token-del` 默认在删除公司的同时一并删除 `XBB_*` 环境变量(`--env 0` 可保留),并在 `message` 说明删除后的实际生效来源。
206
+ `token-list` 会把环境变量行(`source=env`)与 `config.env` 中的公司行(`source=config`)一起列出:环境变量行在 `msg` 列标注「XBB_* 环境变量存在(仅供外部系统,业务命令不再读取)」,`config.env` 的启用行固定标注「来源为 config.env,当前生效」;`token-use` 在切换 `config.env` 启用公司的同时,若检测到 `XBB_*` 有值,会把该公司凭证同步写回环境变量(目标公司缺 `token` 时拒绝切换,避免残留旧 `XBB_TOKEN`),便于外部系统跟随切换;`token-del` 默认在删除公司的同时一并删除 `XBB_*` 环境变量(`--env 0` 可保留),并在 `message` 说明删除后的实际生效来源。
216
207
 
217
- > 注意:`token-del --env 0` 保留环境变量时,若 `XBB_*` 仍有效,其优先级高于 `config.env`,业务命令仍会使用它(`message` 会给出提示);要连环境变量一起清理,用 `token-del` 默认行为(不传 `--env`)或执行 `xbbcli auth-logout`(默认就会清除 `XBB_*`);也可以手工执行 `setx XBB_TOKEN ""`(其余 `XBB_*` 同理,Windows 需重开终端)或删除 `~/.xbbcli/env.sh` 并 `unset` 当前 shell 中已导出的变量。
208
+ > 注意:`token-del --env 0` 保留环境变量时,`XBB_*` 仍会存在(仅供外部系统使用;业务命令不再读取),`message` 会给出提示;要连环境变量一起清理,用 `token-del` 默认行为(不传 `--env`)或执行 `xbbcli auth-logout`(默认就会清除 `XBB_*`);也可以手工执行 `setx XBB_TOKEN ""`(其余 `XBB_*` 同理,Windows 需重开终端)或删除 `~/.xbbcli/env.sh` 并 `unset` 当前 shell 中已导出的变量。
218
209
 
219
210
  ## 当前支持的命令
220
211
 
221
212
  ### 配置
222
213
 
223
214
  - `auth-login`:拉起本地浏览器完成交互式登录(账号密码 / 短信 / 扫码均可),自动读取 Web 会话、换取 API token 并复用 `token-set` 落盘;支持 `--browser`、`--timeout`、`--corpid`、`--keepOpen`、`--env`、`--debug`、`--raw`
224
- - `auth-status`:以 `key` / `value` 两列回显当前激活配置的 `corpid`、`corpName`、`userName`、`userId`、`baseurl`、`token`(token 默认中间掩码,`--showToken` 出明文),并追加 `验证` 行(调用一次 `user-list --pageSize 1` 实测 token,`有效` / `无效-请重新配置token`);来源与冲突提醒以 `note` 行追加,无生效配置时六个值留空并给出 `note`;`--debug` 在 stderr 输出配置文件路径、公司数量、生效的环境变量名与验证结果
215
+ - `auth-status`:以 `key` / `value` 两列回显当前激活配置的 `corpid`、`corpName`、`userName`、`userId`、`baseurl`、`token`(token 默认中间掩码,`--showToken` 出明文),并追加 `验证` 行(调用一次 `user-list --pageSize 1` 实测 token,`有效` / `无效-请重新配置token`);来源与冲突提醒以 `note` 行追加(多个 `enable=true`;无生效配置时提示下一步命令,检测到 `XBB_*` 时说明本版本不再读取);`--debug` 在 stderr 输出配置文件路径、公司数量、检测到的 `XBB_*` 环境变量名与验证结果
225
216
  - `auth-logout`:删除 `config.env` 中 `enable=true` 的那一条配置,删除前整份备份为 `~/.xbbcli/config.env.bak`;不自动把其他公司提升为启用;默认同时清除 `XBB_*` 环境变量(`--env 0` 跳过,返回 `envStored` / `envFile`);既无启用公司又无环境变量时返回 `NO_ACTIVE_CONFIG` 且不改动文件
226
217
  - `auth-refresh-cache`:对当前启用公司重新拉取表单缓存、命令映射与部门/员工缓存,并回填 `corpName` / `userName`;失败时返回 `code=CACHE_REFRESH_FAILED`,`message` 注明中断步骤(`formlist` / `command-map` / `department-user` / `profile`),已完成的部分照常输出;支持 `--userId`、`--debug`
227
- - `token-set`:保存个人 token、`corpid`、`userId`、`baseurl`,并刷新本地表单缓存、命令映射文件与部门/员工缓存;传入的 token 不以 `user_` 开头时,会先为该 `userId` 刷新并保存个人 token;`--env` 控制存储方式:`0`(默认)仅写入本地文件,`1` 仅写入 `XBB_*` 环境变量(Windows 用 `setx`,其他平台写 `~/.xbbcli/env.sh`)且不写任何本地文件;返回 `envStored` / `envFile` 两列
228
- - `token-list`:同时列出环境变量行(`source=env`,仅 `XBB_CORPID` / `XBB_TOKEN` 有值时出现)与 `config.env` 中保存的全部公司(`source=config`,含 `corpName` 公司名称与 `userName` 操作人姓名),`msg` 列标注各行当前状态(当前生效 / 被环境变量覆盖 / 被 `XBB_ENV_ONLY=1` 忽略);`--showToken` 显示完整 token(默认脱敏)
229
- - `token-use`:切换当前启用的公司(`xbbcli token-use --corpid <CORPID>`),保证仅且只有一个公司被启用;若环境变量 `XBB_*` 有值,同时把该公司凭证(`corpid`/`token`/`baseurl`/`userId`/`corpName`/`userName`,缺 `baseurl` 时按 `corpid` 推导)同步写回环境变量——Windows 用 `setx`(需重开终端)、macOS/Linux 写 `~/.xbbcli/env.sh`(需重新 `source`);同步失败时返回 `status=partial`,目标公司缺 `token` 时返回错误且不改动任何配置
218
+ - `token-set`:保存个人 token、`corpid`、`userId`、`baseurl`,并刷新本地表单缓存、命令映射文件与部门/员工缓存;传入的 token 不以 `user_` 开头时,会先为该 `userId` 刷新并保存个人 token;`--env` 控制存储方式:`0`(默认)仅写入本地文件,`1` 仅写入 `XBB_*` 环境变量(供外部系统使用,本 CLI 不读取;Windows 用 `setx`,其他平台写 `~/.xbbcli/env.sh`)且不写任何本地文件;返回 `envStored` / `envFile` 两列
219
+ - `token-list`:同时列出环境变量行(`source=env`,仅 `XBB_CORPID` / `XBB_TOKEN` 有值时出现,`msg` 标注「仅供外部系统,业务命令不再读取」)与 `config.env` 中保存的全部公司(`source=config`,含 `corpName` 公司名称与 `userName` 操作人姓名),config 启用行在 `msg` 列标注「来源为 config.env,当前生效」;`--showToken` 显示完整 token(默认脱敏)
220
+ - `token-use`:切换当前启用的公司(`xbbcli token-use --corpid <CORPID>`),保证仅且只有一个公司被启用;若环境变量 `XBB_*` 有值,同时把该公司凭证(`corpid`/`token`/`baseurl`/`userId`/`corpName`/`userName`,缺 `baseurl` 时按 `corpid` 推导)同步写回环境变量(供外部系统使用,本 CLI 不读取)——Windows 用 `setx`(需重开终端)、macOS/Linux 写 `~/.xbbcli/env.sh`(需重新 `source`);同步失败时返回 `status=partial`,目标公司缺 `token` 时返回错误且不改动任何配置
230
221
  - `token-del`:删除指定公司的本地配置(`xbbcli token-del --corpid <CORPID>`),并保证剩余配置中仅且只有一个公司被启用;默认同时删除 `XBB_*` 环境变量(`--env 0` 保留,返回 `envStored` / `envFile` 两列)
231
222
  - `token-generate`:生成/获取个人 token,`--resetToken 0` 获取(默认)、`1` 刷新;`--checkUserId` 未传则用配置中的 `userId`
232
223
 
@@ -471,10 +462,10 @@ note 以上取值来自环境变量 XBB_*(config.env 中没有启用公
471
462
  ## 通用行为
472
463
 
473
464
  - 先执行一次 `token-set`(或 `auth-login`)保存有效的 `corpid`、`token`、`userId`
474
- - 用 `xbbcli auth-status` 查看当前生效的配置来自 `config.env` 还是环境变量;`xbbcli auth-logout` 删除激活配置并默认清除 `XBB_*` 环境变量(`--env 0` 保留)
465
+ - 用 `xbbcli auth-status` 查看当前生效的配置(`config.env` 中 `enable=true` 的公司);`xbbcli auth-logout` 删除激活配置并默认清除 `XBB_*` 环境变量(`--env 0` 保留)
475
466
  - 除 `token-set` 外,其余命令都会从 `~/.xbbcli/config.env` 读取 `corpid`
476
467
  - 大部分命令会从 `~/.xbbcli/config.env` 读取 `token`
477
- - 环境变量 `XBB_*` 的优先级高于 `config.env`:`XBB_CORPID` / `XBB_TOKEN` 有值时,所有命令都以环境变量为准(`config.env` 中的启用公司被覆盖);两者都不存在时才没有任何生效配置。`XBB_ENV_ONLY=1` 可强制只用环境变量
468
+ - 业务命令不再读取 `XBB_*` 环境变量:凭证来源唯一化为 `config.env` 中 `enable=true` 的公司;`--env 1` 写入的 `XBB_*` 仅供外部系统集成使用
478
469
  - 所有命令会从配置中读取 `userId` 并附加到请求 header 中
479
470
  - 大部分命令需要 formId 参数,可以根据业务名称或 businessType 从 `~/.xbbcli/<corpid>.formlist.json` 中获取 formId
480
471
  - 需要查询部门 id/名称,或员工 userId/所属部门时,可以先查 `~/.xbbcli/<corpid>.department-user.json`(由 `token-set` 分页抓取并缓存全部部门与员工)
package/auth-login.js CHANGED
@@ -3,7 +3,7 @@ import { cli, Strategy } from './xbb-registry.js';
3
3
  import { openBrowser, findBrowserExecutable, LOGIN_PAGE_URL } from './xbb-browser.js';
4
4
  import { saveCompanyCredentials } from './xbb-token-store.js';
5
5
 
6
- const API_TOKEN_PATH = '/pro/v1/apiToken/getApiToken';
6
+ const API_TOKEN_PATH = '/pro/v1/apiPersonalToken/generate';
7
7
  const PRO_GATEWAY_ORIGIN = 'https://progateway.xbongbong.com';
8
8
  const APP_GATEWAY_ORIGIN = 'https://appgateway.xbongbong.com';
9
9
  const PLATFORM = 'web';
@@ -39,6 +39,11 @@ function maskSecret(value) {
39
39
  return `${text.slice(0, 6)}***(${text.length})`;
40
40
  }
41
41
 
42
+ // debug 回显换取响应前对 token 字段做掩码(凭证明文只允许走 --raw 出口)
43
+ function maskResponseTokens(text) {
44
+ return String(text || '').replace(/"(?:personalToken|token)"\s*:\s*"([^"]*)"/gi, (match, value) => match.replace(value, maskSecret(value)));
45
+ }
46
+
42
47
  // 统一输出:成功时与 token-set 的列保持一致,失败时回落到合成错误行(不抛异常)
43
48
  function makeRow(fields) {
44
49
  return [{
@@ -91,10 +96,11 @@ async function readSession(session) {
91
96
  };
92
97
  }
93
98
 
94
- // 在页面内调用网关换取 API token:CORS 全开,sign = SHA256(JSON body + xbbAccessToken)
95
- async function exchangeApiToken(session, loginSession, debug) {
99
+ // 在页面内调用网关换取个人 token:CORS 全开,sign = SHA256(JSON body + xbbAccessToken)
100
+ // resetToken=0 复用已有 token、1 重新生成;checkUserId 为目标人员,自助登录时与登录用户一致
101
+ async function exchangeApiToken(session, loginSession, resetToken, debug) {
96
102
  const url = resolveGatewayUrl(loginSession.corpid);
97
- const body = JSON.stringify({ corpid: loginSession.corpid, userId: loginSession.userId, platform: PLATFORM });
103
+ const body = JSON.stringify({ corpid: loginSession.corpid, userId: loginSession.userId, checkUserId: loginSession.userId, platform: PLATFORM, resetToken });
98
104
  const sign = buildSign(body, loginSession.accessToken);
99
105
  const expression = `(async () => {
100
106
  const response = await fetch(${JSON.stringify(url)}, {
@@ -111,7 +117,7 @@ async function exchangeApiToken(session, loginSession, debug) {
111
117
 
112
118
  const text = await session.evaluate(expression, { attempts: 3, delayMs: 200 });
113
119
  const responseText = typeof text === 'string' ? text : JSON.stringify(text);
114
- if (debug) process.stderr.write(`[debug] ResponseBody: ${responseText}\n`);
120
+ if (debug) process.stderr.write(`[debug] ResponseBody: ${maskResponseTokens(responseText)}\n`);
115
121
 
116
122
  let data;
117
123
  try {
@@ -127,10 +133,7 @@ async function exchangeApiToken(session, loginSession, debug) {
127
133
  }
128
134
 
129
135
  const result = data.result;
130
- const token = normalizeArg(typeof result === 'string' ? result : (result && result.token));
131
- if (!token) {
132
- return { error: '接口未返回 API token;可到销帮帮「个人中心 - API 设置」复制 token 后执行 xbbcli token-set' };
133
- }
136
+ const token = normalizeArg(typeof result === 'string' ? result : (result && (result.personalToken || result.token)));
134
137
  return {
135
138
  token,
136
139
  corpid: normalizeArg(result && result.corpid) || loginSession.corpid,
@@ -139,6 +142,18 @@ async function exchangeApiToken(session, loginSession, debug) {
139
142
  };
140
143
  }
141
144
 
145
+ // resetToken=0 复用已有 token;接口返回空 token 时改用 resetToken=1 重新生成,仍未返回则给出合成错误
146
+ async function exchangeApiTokenWithFallback(session, loginSession, debug) {
147
+ const existing = await exchangeApiToken(session, loginSession, 0, debug);
148
+ if (existing.error || existing.sessionExpired || existing.token) return existing;
149
+
150
+ const regenerated = await exchangeApiToken(session, loginSession, 1, debug);
151
+ if (!regenerated.error && !regenerated.sessionExpired && !regenerated.token) {
152
+ return { ...regenerated, error: '接口未返回 API token;可到销帮帮「个人中心 - API 设置」复制 token 后执行 xbbcli token-set' };
153
+ }
154
+ return regenerated;
155
+ }
156
+
142
157
  async function clearAccessToken(session) {
143
158
  try {
144
159
  await session.evaluate(`(() => { localStorage.removeItem('xbbAccessToken'); return 'ok'; })()`, { attempts: 1 });
@@ -224,7 +239,19 @@ async function authLogin(kwargs) {
224
239
  continue;
225
240
  }
226
241
 
227
- const exchange = await exchangeApiToken(session, current, debug);
242
+ let exchange;
243
+ try {
244
+ exchange = await exchangeApiTokenWithFallback(session, current, debug);
245
+ } catch (error) {
246
+ // 页面导航、执行上下文重建等瞬时失败:记录原因后继续轮询;浏览器已关闭则直接返回
247
+ if (session.isClosed()) {
248
+ await finish(session, keepOpen);
249
+ return makeErrorRow('BROWSER_CLOSED', '浏览器已被关闭,登录未完成');
250
+ }
251
+ lastMessage = String(error.message || error);
252
+ await delay(POLL_INTERVAL_MS);
253
+ continue;
254
+ }
228
255
  if (exchange.sessionExpired) {
229
256
  // 旧会话已过期:清掉后继续等待用户重新登录
230
257
  await clearAccessToken(session);
@@ -297,7 +324,7 @@ cli({
297
324
  { name: 'timeout', type: 'str', default: '', help: `等待登录完成的秒数(默认 ${DEFAULT_TIMEOUT_SECONDS})` },
298
325
  { name: 'corpid', type: 'str', default: '', help: '期望登录的企业 ID;与实际登录企业不一致时报错(可选)' },
299
326
  { name: 'keepOpen', type: 'bool', default: false, help: '登录成功后保留浏览器窗口(默认自动关闭)' },
300
- { name: 'env', type: 'str', default: '0', help: '存储方式:0 仅写入 config.env 文件(默认),1 仅写入环境变量(不写任何本地文件)' },
327
+ { name: 'env', type: 'str', default: '0', help: '存储方式:0 仅写入 config.env 文件(默认);1 仅写入 XBB_* 环境变量(不写任何本地文件,供外部系统使用,本 CLI 不读取)' },
301
328
  { name: 'debug', type: 'bool', default: false, help: '输出请求体、返回体与浏览器信息(密钥只显示掩码)' },
302
329
  { name: 'raw', type: 'bool', default: false, help: '输出换取 API token 接口返回的原文' },
303
330
  ],
package/auth-logout.js CHANGED
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { cli, Strategy } from './xbb-registry.js';
5
- import { clearEnvVars, getEnvVarNames, hasEnvConfig, isEnvOnly, readEnvConfig } from './xbb-config.js';
5
+ import { clearEnvVars, getEnvVarNames, hasEnvConfig, readEnvConfig } from './xbb-config.js';
6
6
 
7
7
  const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
8
8
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
@@ -76,7 +76,7 @@ async function authLogout(kwargs) {
76
76
  ? `本地共 ${companies.length} 家公司(${previewCorpids(companies)})且均未启用;要登出某家请先用 xbbcli token-use --corpid <CORPID> 启用,或改用 xbbcli token-del --corpid <CORPID> 直接删除`
77
77
  : '本地没有任何公司配置';
78
78
  const envNote = envPresent
79
- ? '环境变量 XBB_* 仍存在,本次未清除(用 --env 1 或不指定该参数即可一并清除)'
79
+ ? '环境变量 XBB_* 仍存在(供外部系统使用;业务命令不再读取),本次未清除(用 --env 1 或不指定该参数即可一并清除)'
80
80
  : '环境变量 XBB_* 也不存在,本次无可清除内容';
81
81
  const hint = `${scope};${envNote}`;
82
82
  return createRow({
@@ -86,7 +86,7 @@ async function authLogout(kwargs) {
86
86
  hasActive: 'false',
87
87
  envStored: 'skipped',
88
88
  code: 'NO_ACTIVE_CONFIG',
89
- msg: isEnvOnly() ? `${hint}(当前 XBB_ENV_ONLY=1,config.env 被忽略)` : hint,
89
+ msg: hint,
90
90
  });
91
91
  }
92
92
 
@@ -138,8 +138,7 @@ async function authLogout(kwargs) {
138
138
  parts.push(`清除环境变量失败:${envFile}`);
139
139
  }
140
140
  } else if (envBefore) {
141
- const forced = isEnvOnly() ? '且 XBB_ENV_ONLY=1 已强制只用环境变量' : '且其优先级高于 config.env,业务命令仍会使用它';
142
- parts.push(`注意:环境变量 XBB_*(corpid=${envBefore})仍然有效,${forced},本次不算彻底登出;如需一并清除请改用 --env 1(或不指定该参数,auth-logout 默认会清除环境变量)`);
141
+ parts.push(`注意:环境变量 XBB_*(corpid=${envBefore})仍然存在(供外部系统使用;业务命令不再读取),本次不算彻底登出;如需一并清除请改用 --env 1(或不指定该参数,auth-logout 默认会清除环境变量)`);
143
142
  }
144
143
 
145
144
  const remainingCompanies = target ? remaining : readCompanies();
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from './xbb-registry.js';
2
- import { readActiveConfig, isEnvActive } from './xbb-config.js';
2
+ import { readActiveConfig } from './xbb-config.js';
3
3
  import {
4
4
  refreshLocalCaches,
5
5
  getFormlistFile,
@@ -43,12 +43,11 @@ async function authRefreshCache(kwargs) {
43
43
  const userId = trim(kwargs.userId) || trim(active.userId);
44
44
 
45
45
  if (kwargs.debug) {
46
- const source = isEnvActive() ? 'env' : 'config';
47
- process.stderr.write(`[debug] ConfigSource: ${source}\n[debug] Corpid: ${corpid || '(none)'}\n[debug] UserId: ${userId || '(none)'}\n[debug] FormlistFile: ${corpid ? getFormlistFile(corpid) : ''}\n[debug] CommandMapFile: ${corpid ? getCommandMapFile(corpid) : ''}\n[debug] DepartmentUserFile: ${corpid ? getDepartmentUserFile(corpid) : ''}\n`);
46
+ process.stderr.write(`[debug] Corpid: ${corpid || '(none)'}\n[debug] UserId: ${userId || '(none)'}\n[debug] FormlistFile: ${corpid ? getFormlistFile(corpid) : ''}\n[debug] CommandMapFile: ${corpid ? getCommandMapFile(corpid) : ''}\n[debug] DepartmentUserFile: ${corpid ? getDepartmentUserFile(corpid) : ''}\n`);
48
47
  }
49
48
 
50
49
  if (!corpid) {
51
- return makeErrorRow('NO_ACTIVE_CONFIG', '没有生效的配置:config.env 中不存在 enable=true 的公司,环境变量 XBB_* 也为空;请先执行 xbbcli auth-login 或 xbbcli token-set');
50
+ return makeErrorRow('NO_ACTIVE_CONFIG', '没有生效的配置:config.env 中不存在 enable=true 的公司;请先执行 xbbcli auth-login 或 xbbcli token-set');
52
51
  }
53
52
 
54
53
  let cache;
@@ -79,9 +78,7 @@ async function authRefreshCache(kwargs) {
79
78
 
80
79
  const message = cache.profileUpdated
81
80
  ? '已刷新表单清单缓存、命令映射与部门/员工缓存,并回填 corpName/userName 到 config.env'
82
- : isEnvActive()
83
- ? '已刷新表单清单缓存、命令映射与部门/员工缓存;当前为环境变量模式,corpName/userName 未回填 config.env'
84
- : '已刷新表单清单缓存、命令映射与部门/员工缓存;该公司不在 config.env 中,corpName/userName 未回填';
81
+ : '已刷新表单清单缓存、命令映射与部门/员工缓存;该公司不在 config.env 中,corpName/userName 未回填';
85
82
 
86
83
  return makeRow({
87
84
  status: 'ok',
package/auth-status.js CHANGED
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { cli, Strategy, getRegistry } from './xbb-registry.js';
5
- import { readActiveConfig, isEnvActive, isEnvOnly, getEnvVarNames } from './xbb-config.js';
5
+ import { readActiveConfig, getEnvVarNames } from './xbb-config.js';
6
6
 
7
7
  const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
8
8
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
@@ -79,15 +79,14 @@ function withVerify(rows, verify) {
79
79
 
80
80
  async function authStatus(kwargs) {
81
81
  const showToken = Boolean(kwargs.showToken);
82
- const envUsed = isEnvActive();
83
82
  const active = readActiveConfig();
84
83
  const companies = readCompanies();
85
84
  const hasActive = Boolean(trim(active.corpid) || trim(active.token));
86
- const source = hasActive ? (envUsed ? 'env' : 'config') : 'none';
85
+ const source = hasActive ? 'config' : 'none';
86
+ const envNames = getEnvVarNames().filter((name) => trim(process.env[name]));
87
87
 
88
88
  if (kwargs.debug) {
89
- const envNames = getEnvVarNames().filter((name) => trim(process.env[name])).join(', ');
90
- process.stderr.write(`[debug] ConfigFile: ${CONFIG_FILE}\n[debug] Companies: ${companies.length}\n[debug] Source: ${source}\n[debug] XBB_ENV_ONLY: ${trim(process.env.XBB_ENV_ONLY) || 'unset'}\n[debug] EnvVarsSet: ${envNames || 'none'}\n`);
89
+ process.stderr.write(`[debug] ConfigFile: ${CONFIG_FILE}\n[debug] Companies: ${companies.length}\n[debug] EnvVarsSet: ${envNames.join(', ') || 'none'}\n`);
91
90
  }
92
91
 
93
92
  const token = trim(active.token);
@@ -106,22 +105,10 @@ async function authStatus(kwargs) {
106
105
  const verifiedRows = withVerify(rows, verify);
107
106
 
108
107
  if (source === 'none') {
109
- // 没有生效配置时不把残留的环境变量值当真值回显(XBB_BASEURL 等可能单独存在)
110
- return withNotes(verifiedRows, [
111
- '没有处于激活状态的配置:config.env 中不存在 enable=true 的公司,环境变量 XBB_* 也为空;请先执行 xbbcli auth-login 或 xbbcli token-set',
112
- ]);
113
- }
114
-
115
- if (source === 'env') {
116
- const enabledCount = companies.filter((item) => item.enable === true).length;
117
- const reason = isEnvOnly()
118
- ? 'XBB_ENV_ONLY=1 已强制使用环境变量'
119
- : enabledCount > 0
120
- ? '环境变量优先于 config.env,其中 enable=true 的公司被覆盖'
121
- : 'config.env 中没有启用公司,回落到环境变量';
122
- const notes = [`以上取值来自环境变量 XBB_*(${reason});环境变量模式不支持多公司切换,配置文件路径与公司数量见 --debug`];
123
- if (!isEnvOnly() && enabledCount > 0) {
124
- notes.push('如需改用 config.env 中的启用公司,请先清除 XBB_* 环境变量(或执行 xbbcli auth-logout)');
108
+ // 没有生效配置时不把残留的环境变量值当真值回显;检测到 XBB_* 时提示本版本不再读取
109
+ const notes = ['没有处于激活状态的配置:config.env 中不存在 enable=true 的公司;请先执行 xbbcli auth-login 或 xbbcli token-set'];
110
+ if (envNames.length) {
111
+ notes.push('检测到 XBB_* 环境变量,但本版本不再读取环境变量');
125
112
  }
126
113
  return withNotes(verifiedRows, notes);
127
114
  }
@@ -31,6 +31,15 @@ function buildApiUrl(baseUrl, defaultUrl) {
31
31
  }
32
32
 
33
33
  function buildConditions(kwargs) {
34
+ const conditions = kwargs.conditions;
35
+ if (typeof conditions === 'string' && conditions.trim()) {
36
+ const parsed = JSON.parse(conditions);
37
+ if (!Array.isArray(parsed)) {
38
+ throw new Error('INVALID_CONDITIONS:conditions 必须是 JSON 数组');
39
+ }
40
+ return parsed;
41
+ }
42
+
34
43
  if (!(kwargs.attr && kwargs.value)) {
35
44
  return [];
36
45
  }
@@ -126,6 +135,7 @@ cli({
126
135
  args: [
127
136
  { name: 'userId', type: 'str', default: '', help: '操作人id(可选)' },
128
137
  { name: 'viewApproval', type: 'str', default: '', help: '是否查询审批中数据,1是,0否' },
138
+ { name: 'conditions', type: 'str', default: '', help: '条件集合 JSON 字符串,例如 [{"attr":"text_1","value":["025554"],"symbol":"equal"}]' },
129
139
  { name: 'attr', type: 'str', default: '', help: '筛选字段 attr,例如 text_1' },
130
140
  { name: 'value', type: 'str', default: '', help: '筛选值,和 --attr 配合使用' },
131
141
  { name: 'symbol', type: 'str', default: 'equal', help: '筛选操作符,默认 equal' },
@@ -139,7 +149,16 @@ cli({
139
149
  func: async function (kwargs) {
140
150
  const debug = Boolean(kwargs.debug);
141
151
  const { corpid, token, baseUrl, userId } = getRuntimeConfig();
142
- const payload = buildPayload(kwargs, corpid);
152
+ let payload;
153
+ try {
154
+ payload = buildPayload(kwargs, corpid);
155
+ } catch (error) {
156
+ const message = String(error?.message || error);
157
+ const separatorIndex = message.indexOf(':');
158
+ const code = separatorIndex > 0 ? message.slice(0, separatorIndex) : 'INVALID_PAYLOAD';
159
+ const detail = separatorIndex > 0 ? message.slice(separatorIndex + 1) : message;
160
+ return makeErrorRow(code, detail);
161
+ }
143
162
  const body = JSON.stringify(payload);
144
163
 
145
164
  const validationError = getValidationError(payload, token);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xbongbong/xbbcli",
3
- "version": "2.4.3",
3
+ "version": "2.4.4",
4
4
  "description": "销帮帮 CRM 的独立命令行工具",
5
5
  "type": "module",
6
6
  "bin": {
package/token-del.js CHANGED
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { cli, Strategy } from './xbb-registry.js';
5
- import { clearEnvVars, getEnvVarNames, hasEnvConfig, isEnvOnly, readEnvConfig } from './xbb-config.js';
5
+ import { clearEnvVars, getEnvVarNames, hasEnvConfig, readEnvConfig } from './xbb-config.js';
6
6
 
7
7
  const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
8
8
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
@@ -106,9 +106,7 @@ async function delCompany(kwargs) {
106
106
  }
107
107
  // 环境变量已清空,说明删除后的实际生效来源
108
108
  if (envStored !== 'none' || envBefore) {
109
- if (isEnvOnly()) {
110
- parts.push('注意:XBB_ENV_ONLY=1 仍然开启,环境变量已清空,业务命令将报 NO_ACTIVE_CONFIG;如需恢复请先写入凭证或去掉该变量');
111
- } else if (next.length) {
109
+ if (next.length) {
112
110
  parts.push(`此后业务命令使用 config.env 中启用的 corpid=${enabledCorpid}`);
113
111
  } else {
114
112
  parts.push('此后没有可用凭证,请重新执行 xbbcli token-set 或 xbbcli auth-login');
@@ -120,10 +118,7 @@ async function delCompany(kwargs) {
120
118
  return createResult('partial', `${parts.join(';')};删除环境变量失败:${envFile}`, corpid, next.length ? 'true' : '', next.length, envStored, envFile);
121
119
  }
122
120
  } else if (envPresent) {
123
- const forced = isEnvOnly()
124
- ? '且 XBB_ENV_ONLY=1 已强制只用环境变量(config.env 被忽略)'
125
- : '且其优先级高于 config.env';
126
- parts.push(`注意:环境变量 XBB_*(corpid=${envBefore || '未知'})仍然有效,${forced},业务命令仍会使用它;如需一并删除请改用 --env 1(或不指定该参数)`);
121
+ parts.push(`注意:环境变量 XBB_*(corpid=${envBefore || '未知'})仍然存在(供外部系统使用;业务命令不再读取);如需一并删除请改用 --env 1(或不指定该参数)`);
127
122
  }
128
123
 
129
124
  return createResult('ok', parts.join(';'), corpid, next.length ? 'true' : '', next.length, envStored, envFile);
package/token-list.js CHANGED
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { cli, Strategy } from './xbb-registry.js';
5
- import { readEnvConfig, hasEnvConfig, isEnvOnly } from './xbb-config.js';
5
+ import { readEnvConfig, hasEnvConfig } from './xbb-config.js';
6
6
 
7
7
  const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
8
8
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
@@ -71,13 +71,12 @@ cli({
71
71
  const companies = readCompanies();
72
72
  const envConfig = readEnvConfig();
73
73
  const envPresent = hasEnvConfig(envConfig);
74
- const envOnly = isEnvOnly();
75
74
  const rows = [];
76
75
 
77
- // 环境变量行:XBB_CORPID / XBB_TOKEN 有值就回显,source=env(优先级高于 config.env)
76
+ // 环境变量行:XBB_CORPID / XBB_TOKEN 有值就回显,source=env;仅供外部系统消费,业务命令不再读取
78
77
  if (envPresent) {
79
78
  rows.push({
80
- enable: 'true',
79
+ enable: '',
81
80
  corpid: String(envConfig.corpid || ''),
82
81
  userId: String(envConfig.userId || ''),
83
82
  baseurl: String(envConfig.baseurl || ''),
@@ -85,9 +84,7 @@ cli({
85
84
  corpName: String(envConfig.corpName || ''),
86
85
  userName: String(envConfig.userName || ''),
87
86
  code: '',
88
- msg: envOnly
89
- ? '来源为环境变量 XBB_*(XBB_ENV_ONLY=1 已强制只用环境变量,config.env 被忽略)'
90
- : '来源为环境变量 XBB_*(优先级高于 config.env,当前生效)',
87
+ msg: 'XBB_* 环境变量存在(仅供外部系统,业务命令不再读取)',
91
88
  source: 'env',
92
89
  });
93
90
  }
@@ -104,13 +101,7 @@ cli({
104
101
  corpName: String(item.corpName || ''),
105
102
  userName: String(item.userName || ''),
106
103
  code: '',
107
- msg: isEnabled
108
- ? envOnly
109
- ? '来源为 config.env,但 XBB_ENV_ONLY=1 已将其忽略,当前不生效'
110
- : envPresent
111
- ? '来源为 config.env,但已被环境变量 XBB_* 覆盖,当前不生效'
112
- : '来源为 config.env,当前生效'
113
- : '',
104
+ msg: isEnabled ? '来源为 config.env,当前生效' : '',
114
105
  source: 'config',
115
106
  });
116
107
  }
package/token-set.js CHANGED
@@ -12,7 +12,7 @@ cli({
12
12
  { name: 'corpid', type: 'str', help: '公司id(必填)' },
13
13
  { name: 'token', type: 'str', help: '要保存的 API token' },
14
14
  { name: 'userId', type: 'str', help: '操作人id(必填)' },
15
- { name: 'env', type: 'str', default: '0', help: '存储方式:0 仅写入 config.env 文件(默认),1 仅写入环境变量(不写任何本地文件)' },
15
+ { name: 'env', type: 'str', default: '0', help: '存储方式:0 仅写入 config.env 文件(默认);1 仅写入 XBB_* 环境变量(不写任何本地文件,供外部系统使用,本 CLI 不读取)' },
16
16
  ],
17
17
  columns: ['status', 'message', 'configFile', 'corpid', 'baseurl', 'userId', 'enable', 'companyCount', 'formlistFile', 'commandMapFile', 'departmentUserFile', 'corpName', 'userName', 'envStored', 'envFile'],
18
18
  func: saveCompanyCredentials,
package/token-use.js CHANGED
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { cli, Strategy } from './xbb-registry.js';
5
- import { hasEnvConfig, isEnvOnly, getEnvVarNames, persistEnvVars } from './xbb-config.js';
5
+ import { hasEnvConfig, persistEnvVars } from './xbb-config.js';
6
6
  import { resolveBaseUrl } from './xbb-token-store.js';
7
7
 
8
8
  const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
@@ -69,10 +69,6 @@ async function useCompany(kwargs) {
69
69
  }
70
70
 
71
71
  const envPresent = hasEnvConfig();
72
- // XBB_ENV_ONLY=1 且 XBB_* 全空:config.env 被强制忽略,写入也无法生效
73
- if (isEnvOnly() && !envPresent) {
74
- return createResult('error', `当前 XBB_ENV_ONLY=1 已强制只用环境变量,但 ${getEnvVarNames().join(', ')} 均为空;请先执行 xbbcli token-set --env 1 写入环境变量`, corpid, '', '');
75
- }
76
72
 
77
73
  const companies = readCompanies();
78
74
  if (!companies.length) {
@@ -85,7 +81,7 @@ async function useCompany(kwargs) {
85
81
  }
86
82
 
87
83
  const target = companies[index];
88
- // 同步 XBB_* 需要完整凭证:缺 token 时直接拒绝,避免残留上一家公司的 XBB_TOKEN 被继续使用
84
+ // XBB_* 有值时会同步覆盖环境变量,需要完整凭证:缺 token 时直接拒绝,避免残留上一家公司的 XBB_TOKEN
89
85
  if (envPresent && !String(target.token || '').trim()) {
90
86
  return createResult('error', `corpid=${corpid} 的本地配置缺少 token,无法同步到 XBB_* 环境变量;请先执行 xbbcli token-set --corpid ${corpid} --token <TOKEN> --userId <USERID>`, corpid, '', companies.length);
91
87
  }
@@ -95,7 +91,7 @@ async function useCompany(kwargs) {
95
91
 
96
92
  const parts = [`已启用 corpid=${corpid},其余公司已标记为 enable=false`];
97
93
 
98
- // 环境变量中有值时把生效公司同步写回 XBB_*,否则切换会被优先级更高的环境变量覆盖
94
+ // 环境变量中有值时把启用公司凭证同步写回 XBB_*(供外部系统消费,本 CLI 不再读取)
99
95
  if (envPresent) {
100
96
  try {
101
97
  persistEnvVars({
@@ -110,7 +106,7 @@ async function useCompany(kwargs) {
110
106
  const suffix = process.platform === 'win32'
111
107
  ? '(Windows 需重开终端后才生效)'
112
108
  : '(重新 source ~/.xbbcli/env.sh 后才生效)';
113
- parts.push(`检测到环境变量 XBB_* 有值,已同步更新为该公司凭证${suffix}`);
109
+ parts.push(`检测到环境变量 XBB_* 有值,已同步更新为该公司凭证(供外部系统使用,本 CLI 不读取)${suffix}`);
114
110
  } catch (error) {
115
111
  return createResult('partial', `已启用 corpid=${corpid},但同步更新环境变量失败:${String(error.message || error)}`, corpid, 'true', next.length);
116
112
  }
@@ -122,7 +118,7 @@ async function useCompany(kwargs) {
122
118
  cli({
123
119
  site: 'xbb',
124
120
  name: 'token-use',
125
- description: '切换当前启用的公司(enable),保证仅且只有一个公司被启用;XBB_* 环境变量有值时同步更新为该公司凭证',
121
+ description: '切换当前启用的公司(enable),保证仅且只有一个公司被启用;XBB_* 环境变量有值时同步更新为该公司凭证(供外部系统使用,本 CLI 不读取)',
126
122
  strategy: Strategy.PUBLIC,
127
123
  access: 'write',
128
124
  browser: false,
package/xbb-config.js CHANGED
@@ -7,7 +7,7 @@ const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
7
7
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.env');
8
8
  const ENV_FILE = path.join(CONFIG_DIR, 'env.sh');
9
9
 
10
- // config.env 字段名 -> 对应环境变量名(环境变量模式只支持单公司)
10
+ // config.env 字段名 -> 对应环境变量名;仅供写入、同步、清除与展示类命令检测使用,业务命令不再读取
11
11
  export const ENV_VAR_NAMES = Object.freeze({
12
12
  corpid: 'XBB_CORPID',
13
13
  token: 'XBB_TOKEN',
@@ -21,10 +21,6 @@ function trim(value) {
21
21
  return String(value ?? '').trim();
22
22
  }
23
23
 
24
- function isTruthy(value) {
25
- return ['1', 'true', 'yes', 'on'].includes(trim(value).toLowerCase());
26
- }
27
-
28
24
  // 读取环境中非空的 XBB_* 变量,返回与 config.env 单条配置同构的对象
29
25
  export function readEnvConfig() {
30
26
  const config = {};
@@ -39,12 +35,7 @@ export function hasEnvConfig(config = readEnvConfig()) {
39
35
  return Boolean(trim(config.corpid) || trim(config.token));
40
36
  }
41
37
 
42
- // XBB_ENV_ONLY=1 时强制只使用环境变量,忽略 config.env
43
- export function isEnvOnly() {
44
- return isTruthy(process.env.XBB_ENV_ONLY);
45
- }
46
-
47
- // 与旧版每个命令内联的 readConfig 行为保持一致:优先返回 enable=true 的公司
38
+ // 与旧版每个命令内联的 readConfig 行为保持一致:返回 enable=true 的公司
48
39
  function readFileConfig() {
49
40
  try {
50
41
  const parsed = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
@@ -62,23 +53,11 @@ function readFileConfig() {
62
53
  }
63
54
  }
64
55
 
65
- // 环境变量优先:XBB_* 中 corpid/token 非空时只用环境变量,否则用 config.env 里 enable=true 的公司
56
+ // 业务命令统一从 config.env 读取 enable=true 的公司;XBB_* 环境变量不再参与读取
66
57
  export function readActiveConfig() {
67
- const envConfig = readEnvConfig();
68
- if (isEnvOnly()) {
69
- return envConfig;
70
- }
71
- if (hasEnvConfig(envConfig)) {
72
- return envConfig;
73
- }
74
58
  return readFileConfig();
75
59
  }
76
60
 
77
- // 当前生效来源是否为环境变量
78
- export function isEnvActive() {
79
- return hasEnvConfig();
80
- }
81
-
82
61
  export function getEnvVarNames() {
83
62
  return Object.values(ENV_VAR_NAMES);
84
63
  }
@@ -2,7 +2,7 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { getRegistry } from './xbb-registry.js';
5
- import { persistEnvVars, hasEnvConfig } from './xbb-config.js';
5
+ import { persistEnvVars } from './xbb-config.js';
6
6
 
7
7
  // token-set 与 auth-login 共用的凭证落盘逻辑:写 config.env、表单缓存、命令映射、部门/员工缓存、环境变量
8
8
  export const CONFIG_DIR = path.join(os.homedir(), '.xbbcli');
@@ -445,19 +445,14 @@ export async function saveCompanyCredentials(kwargs) {
445
445
  fs.mkdirSync(CONFIG_DIR, { recursive: true });
446
446
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(companies, null, 2) + '\n', 'utf8');
447
447
 
448
- // 环境变量优先级高于 config.env:此时只写文件不会改变业务命令实际使用的凭证,需在结果里提示
449
- const envShadow = hasEnvConfig()
450
- ? ';注意:检测到 XBB_* 环境变量,其优先级高于 config.env,业务命令仍会使用环境变量;如需改用本次保存的配置,请清除 XBB_* 环境变量或执行 xbbcli auth-logout'
451
- : '';
452
-
453
448
  try {
454
449
  const cache = await refreshLocalCaches(corpid, userId);
455
450
  const { formlistFile, commandMapFile, departmentUserFile, corpName, userName } = cache;
456
- return createResult('ok', `已保存 corpid、token、baseurl、userId,并同步表单模板缓存、命令映射文件与部门/员工缓存${envShadow}`, corpid, baseurl, userId, formlistFile, commandMapFile, true, companies.length, departmentUserFile, corpName, userName, 'skipped', '');
451
+ return createResult('ok', '已保存 corpid、token、baseurl、userId,并同步表单模板缓存、命令映射文件与部门/员工缓存', corpid, baseurl, userId, formlistFile, commandMapFile, true, companies.length, departmentUserFile, corpName, userName, 'skipped', '');
457
452
  } catch (error) {
458
453
  return createResult(
459
454
  'partial',
460
- `已保存 corpid、token、baseurl、userId,但同步缓存失败${error.step ? `(步骤 ${error.step})` : ''}:${error.message}${envShadow}`,
455
+ `已保存 corpid、token、baseurl、userId,但同步缓存失败${error.step ? `(步骤 ${error.step})` : ''}:${error.message}`,
461
456
  corpid,
462
457
  baseurl,
463
458
  userId,