@vibeapi/cli 0.0.149 → 0.0.150
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 +22 -17
- package/dist/commands/config.js +1 -1
- package/dist/commands/setup.js +1 -1
- package/dist/lib/claude-code-manager.d.ts +4 -0
- package/dist/lib/claude-code-manager.js +1 -1
- package/dist/lib/command.d.ts +0 -5
- package/dist/lib/command.js +1 -1
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.js +1 -1
- package/dist/lib/env-cleanup.d.ts +32 -28
- package/dist/lib/env-cleanup.js +1 -1
- package/dist/lib/gateway-addresses.d.ts +4 -0
- package/dist/lib/gateway-addresses.js +1 -1
- package/dist/lib/model-registry.d.ts +2 -0
- package/dist/lib/model-registry.js +1 -1
- package/dist/lib/tool-manager.d.ts +3 -0
- package/dist/lib/tool-manager.js +1 -1
- package/dist/lib/tools/claude-code.js +1 -1
- package/dist/lib/tools/claude-desktop.d.ts +1 -1
- package/dist/lib/tools/claude-desktop.js +1 -1
- package/dist/lib/tools/codex.js +1 -1
- package/dist/lib/tools/types.d.ts +13 -0
- package/dist/lib/wizard.js +1 -1
- package/dist/lib/write-plan.d.ts +2 -0
- package/dist/lib/write-plan.js +1 -1
- package/dist/lib/written-values.d.ts +21 -0
- package/dist/lib/written-values.js +1 -0
- package/dist/locales/en_US.json +16 -0
- package/dist/locales/zh_CN.json +16 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# VibeCLI(@vibeapi/api-helper)
|
|
2
2
|
|
|
3
|
-
> 一条命令,为 Claude Code、Claude Desktop、Codex、Grok Build、Antigravity CLI 配置 VibeAPI
|
|
3
|
+
> 一条命令,为 Claude Code、Claude Desktop、Codex、Grok Build、Antigravity CLI 配置 VibeAPI 的令牌和模型;也能从国内镜像安装和升级这些工具,以及 Node.js、Git、uv、Claude Desktop、ChatGPT 桌面版(`vibecli list` / `install` / `upgrade`)。
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@vibeapi/api-helper)
|
|
6
6
|
[](https://nodejs.org)
|
|
@@ -44,6 +44,8 @@ npx --yes --registry https://npm.cnb.cool/vibeapi/npm/-/packages/ @vibeapi/cli@l
|
|
|
44
44
|
| Node.js | `curl -fsSL https://cnb.cool/vibeapi/install/-/git/raw/main/node.sh \| sh` | `irm https://cnb.cool/vibeapi/install/-/git/raw/main/node.ps1 \| iex` |
|
|
45
45
|
| Git | `curl -fsSL https://cnb.cool/vibeapi/install/-/git/raw/main/git.sh \| sh` | `irm https://cnb.cool/vibeapi/install/-/git/raw/main/git.ps1 \| iex` |
|
|
46
46
|
|
|
47
|
+
已经装好 VibeCLI(有 Node.js)时,上面这些工具以及 uv、Claude Desktop、ChatGPT 桌面版都可以用 `vibecli install <组件>` 装、`vibecli upgrade <组件>` 升级,`vibecli list` 查看本机状态,不用逐条记脚本地址。组件清单见 [`docs/components.md`](docs/components.md)。
|
|
48
|
+
|
|
47
49
|
## 快速开始
|
|
48
50
|
|
|
49
51
|
下面三种方式**任选一种**,完成任意一种就已经配置好了,不要依次全部执行。
|
|
@@ -137,31 +139,34 @@ vibecli config claude-code clear-model
|
|
|
137
139
|
|
|
138
140
|
- **一次配置多个工具**:把当前的令牌和模型写入 Claude Code、Claude Desktop(第三方推理模式,写入应用的本地配置库,不需要管理员权限或描述文件)、Codex、Grok Build、Antigravity CLI。OpenCode 暂时下架,`vibecli config opencode` 仍可使用。
|
|
139
141
|
- **每个工具单独选模型**:可以用默认模型、从 API 实时获取、选内置预设或手动输入,每个工具可以用不同的模型。
|
|
140
|
-
- Claude Code 使用默认模型时不写 `ANTHROPIC_*_MODEL` 环境变量,顶层 `model` 取 `settings-template.json` 的值(当前为 `opus[1m]
|
|
142
|
+
- Claude Code 使用默认模型时不写 `ANTHROPIC_*_MODEL` 环境变量,顶层 `model` 取 `settings-template.json` 的值(当前为 `opus[1m]`);显式选择其他模型时覆盖这个值。你在 Claude Code / Codex 里用 `/model` 换过的模型和思考强度,之后再运行 setup 也保留(VibeCLI 只更新它自己写过、你没改过的值)。Codex 默认 `gpt-6-astra`,Grok Build 默认 `grok-4.6`,其余工具默认 `gpt-5.5`。
|
|
141
143
|
- Codex(命令行版与桌面版)的模型菜单除了自带的 GPT 模型,还列出网关的 Claude、Gemini、Grok 和国产模型;Claude Code 的 `/model` 菜单在内置档位之后列出 GPT、Gemini、Grok 和国产模型。两边都只列令牌能用的模型。每个模型按自己的上下文窗口设置,用到窗口的 80% 时自动压缩。Codex 默认开启 1M 上下文,交互配置时可以关闭(关闭后窗口不超过 272K)。需要 Codex 0.122 及以上;Codex 或 Claude Code 版本过旧时,配置完成后会给出升级命令。
|
|
142
144
|
- **保留你的其他配置**:只改本工具的服务商(provider)字段,你的 hooks、MCP、插件和权限设置原样保留。
|
|
143
145
|
- **配置损坏时重置**:`vibecli config <tool> --force`(或 `vibecli setup … --force`)先备份,再用推荐配置整份重写,已经不是合法 JSON / TOML 的文件也能恢复。不加 `--force` 时只做合并,遇到损坏的文件不改动。
|
|
144
146
|
- **写入前自动备份,可以还原**:覆盖配置文件前先备份(每个文件保留 10 份),`vibecli config <tool> restore` 随时还原,还原之后还可以再撤销。
|
|
145
147
|
- **多个 Profile,凭据可随机器迁移**:work、personal 等 Profile 互相独立。令牌保存在权限为 `0600` 的 `~/.vibecli/config.yaml`,与 Claude Code、Codex 自己保存凭据的方式一样是明文,换机器后不会因为加密绑定原机器而无法读取。
|
|
148
|
+
- **安装和升级常用工具**:`vibecli list` 查看本机装了哪些、什么版本、能不能升级;`vibecli install <组件>` 从国内镜像安装 Node.js、Git、uv、Claude Code、Codex、Grok Build、Antigravity CLI、Claude Desktop、ChatGPT 桌面版(校验哈希与签名,已装只提示升级,`--force` 重装);`vibecli upgrade <组件>` 升级。可以一次填多个组件,别的应用的安装脚本也能调用它。各组件来源、校验和适用系统见 [`docs/components.md`](docs/components.md)。
|
|
146
149
|
- **适合 CI**:全局选项 `--json`、`--non-interactive`、`--yes`、`--no-color` 适用于各个命令;`setup`、`config` 和只读命令支持 `--dry-run` 预演,其他会写入的命令遇到 `--dry-run` 直接拒绝执行。
|
|
147
150
|
|
|
148
151
|
## 常用命令
|
|
149
152
|
|
|
150
|
-
| 命令 | 用途
|
|
151
|
-
| ------------------------------------------------- |
|
|
152
|
-
| `vibecli config <tool>` | 打开某个工具的菜单:选择模型后应用、移除或还原
|
|
153
|
-
| `vibecli config <tool> restore` | 把该工具的配置还原到上次修改前
|
|
154
|
-
| `vibecli config <tool> clear-model` | 去掉固定的模型设置,恢复工具自带的默认模型(保留令牌)
|
|
155
|
-
| `vibecli config <tool> remove` | 移除 VibeAPI 写入的配置(Codex 会退出令牌登录,可以改用 ChatGPT 订阅登录)
|
|
156
|
-
| `vibecli config tool-model set codex gpt-6-astra` | 单独给某个工具指定模型
|
|
157
|
-
| `vibecli config profile use work` | 切换 Profile
|
|
158
|
-
| `vibecli doctor` | 检查配置、网络和工具版本;加 `--deep` 时实际发一条请求
|
|
159
|
-
| `vibecli doctor --report` | 检查后上传诊断信息,得到一个诊断码,发给客服
|
|
160
|
-
| `vibecli
|
|
161
|
-
| `vibecli
|
|
162
|
-
| `vibecli
|
|
163
|
-
| `vibecli
|
|
164
|
-
| `vibecli
|
|
153
|
+
| 命令 | 用途 |
|
|
154
|
+
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
155
|
+
| `vibecli config <tool>` | 打开某个工具的菜单:选择模型后应用、移除或还原 |
|
|
156
|
+
| `vibecli config <tool> restore` | 把该工具的配置还原到上次修改前 |
|
|
157
|
+
| `vibecli config <tool> clear-model` | 去掉固定的模型设置,恢复工具自带的默认模型(保留令牌) |
|
|
158
|
+
| `vibecli config <tool> remove` | 移除 VibeAPI 写入的配置(Codex 会退出令牌登录,可以改用 ChatGPT 订阅登录) |
|
|
159
|
+
| `vibecli config tool-model set codex gpt-6-astra` | 单独给某个工具指定模型 |
|
|
160
|
+
| `vibecli config profile use work` | 切换 Profile |
|
|
161
|
+
| `vibecli doctor` | 检查配置、网络和工具版本;加 `--deep` 时实际发一条请求 |
|
|
162
|
+
| `vibecli doctor --report` | 检查后上传诊断信息,得到一个诊断码,发给客服 |
|
|
163
|
+
| `vibecli list` | 列出本机各组件(Node.js、Git、uv、各编码工具、桌面应用)是否已装、版本、能否升级 |
|
|
164
|
+
| `vibecli install <组件>` | 从国内镜像安装一个或多个组件(node、git、uv、claude-code、codex、grok、agy、claude-desktop、chatgpt) |
|
|
165
|
+
| `vibecli upgrade` | 不填名字时升级已装的编码工具和桌面应用(Claude Code、Codex、Grok Build、agy、Claude Desktop、ChatGPT),并提示可升级的运行环境;填名字可单独升级,含 node、git、uv。官方下载地址无法访问时改用国内镜像 |
|
|
166
|
+
| `vibecli codex sessions` | 换过登录方式后 Codex 的旧会话不显示时,让它们重新出现(先退出 Codex) |
|
|
167
|
+
| `vibecli setup --desktop-registry on` | Windows 上 Claude Desktop 不读取本地配置时,改为写入注册表(会请求一次管理员确认) |
|
|
168
|
+
| `vibecli base` | 列出并测试全部接入地址 |
|
|
169
|
+
| `vibecli base auto` | 换到第一个能用的接入地址,已配置的工具一起换 |
|
|
165
170
|
|
|
166
171
|
### 主域名无法访问或证书报错
|
|
167
172
|
|
package/dist/commands/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import o from"inquirer";import e from"chalk";import{wizard as t}from"../lib/wizard.js";import{toolManager as n,SUPPORTED_TOOLS as l}from"../lib/tool-manager.js";import{i18n as i}from"../lib/i18n.js";import{configManager as r,DEFAULT_PROFILE_NAME as a}from"../lib/config.js";import{confirmAction as s,applyOrDryRun as c,requireTerminal as d}from"../lib/confirm.js";import{canPrompt as f,isDryRun as g,isNonInteractive as p,shouldAutoConfirm as m}from"../lib/runtime-options.js";const _=/^[a-zA-Z0-9._-]+$/;function u(o,e){const t=(o||"").trim();if(!t||!_.test(t))throw new Error(i.t(`config.${e}_invalid_name`,{name:t||"<empty>"}));return t}function y(o,t){console.log("\n"+e.bold(o)),0!==t.length?t.forEach(o=>console.log(" "+o)):console.log(e.gray(" ("+i.t("config.empty_list")+")"))}function v(o,t){if("backups"===t){const t=n.listToolBackups(o).flatMap(o=>{const t=o.file.replace(/^.*[/\\]/,"");return o.backups.length?o.backups.map(o=>`${e.cyan(t)} ${e.gray(o.name.split(".bak-")[1]||o.name)}`):[`${e.cyan(t)} ${e.gray("("+i.t("config.empty_list")+")")}`]});return void y(i.t("config.tool_backups_title",{tool:l[o].displayName}),t)}const r=n.restorePrivateConfig(o).filter(o=>o.result.restored);0!==r.length?(console.log(e.green(i.t("config.config_restored",{tool:l[o].displayName}))),r.forEach(o=>console.log(e.gray(` ${o.file} ← ${o.result.from}`))),console.log(e.yellow(" ! "+i.t("config.reopen_hint",{tool:l[o].displayName})))):console.log(e.yellow(i.t("config.restore_no_backup",{tool:l[o].displayName})))}async function w(a,p,m){if(!l[a])return void console.log(e.red(i.t("config.unknown_tool",{tool:a})));const _=(p[0]||"").toLowerCase();if("backups"!==_&&"list-backups"!==_)if("restore"!==_){if("remove"===_||"unload"===_){const o=l[a].displayName;return void await c({description:i.t("config.remove_dry_run",{tool:o}),json:{action:"remove",tool:a},apply:async()=>{if(!await s({message:i.t("config.confirm_remove",{tool:o,name:a}),defaultValue:!0}))return;const t=n.holdsOurConfig(a),l=n.unloadPrivateConfig(a);t?(console.log(e.green(i.t("config.remove_done",{tool:o}))),l.forEach(o=>console.log(e.yellow(" ! "+o.text)))):console.log(e.gray(i.t("config.remove_nothing",{tool:o})))}})}if("clear-model"===_||"clear-models"===_){const o=l[a].displayName;return n.supportsClearPinnedModels(a)?void await c({description:i.t("config.clear_model_dry_run",{tool:o}),json:{action:"clear_model",tool:a},apply:async()=>{const t=await n.clearPinnedModels(a);0!==t.length?(console.log(e.green(i.t("config.clear_model_done",{tool:o}))),t.forEach(o=>console.log(e.gray(` - ${o}`))),console.log(e.gray(i.t("config.clear_model_restart_hint",{tool:o})))):console.log(e.yellow(i.t("config.clear_model_none",{tool:o})))}}):void console.log(e.yellow(i.t("config.clear_model_unsupported",{tool:o})))}if(g())await c({description:i.t("config.tool_apply_dry_run",{tool:l[a].displayName}),json:{action:"apply",tool:a,force:Boolean(m.force)},apply:()=>{}});else{if(!n.isToolInstalled(a)){if(m.nonInteractive)return void console.log(e.yellow(i.t("wizard.tool_not_installed",{tool:l[a].displayName})));if(console.log(e.yellow(i.t("wizard.tool_not_installed",{tool:l[a].displayName}))),!f())return;const{shouldInstall:t}=await o.prompt([{type:"confirm",name:"shouldInstall",message:i.t("wizard.install_tool_confirm"),default:!0}]);if(!t)return void console.log(e.yellow(i.t("wizard.install_skipped")));try{await n.installTool(a)}catch(o){return console.error(e.red(i.t("install.install_failed_detail"))),void(o instanceof Error&&o.message&&console.error(e.gray(o.message)))}}if(m.nonInteractive||m.force){const o=r.getApiKeyForTool(a);if(!o)return void console.log(e.red(i.t("auth.reload_missing_config")));if(m.force&&!m.nonInteractive&&!await s({message:i.t("config.confirm_force",{tool:l[a].displayName}),defaultValue:!1}))return;return await n.applyPrivateConfig(a,o,r.getModelsForTool(a),{force:m.force}),m.force&&console.log(e.yellow(i.t("config.force_done",{tool:l[a].displayName,name:a}))),void n.printApplied(a)}d(),await t.showToolMenu(a)}}else await c({description:i.t("config.tool_restore_dry_run",{tool:l[a].displayName}),json:{action:"tool_restore",tool:a},apply:async()=>{await s({message:i.t("config.confirm_restore",{tool:l[a].displayName}),defaultValue:!1})&&v(a,"restore")}});else v(a,"backups")}export async function configCommand(t){const{positional:n,options:d}=function(o){const e=[],t={};(p()||m())&&(t.nonInteractive=!0);for(const n of o)"--non-interactive"===n||"-y"===n?t.nonInteractive=!0:"--force"===n||"-f"===n?t.force=!0:e.push(n);return{positional:e,options:t}}(t),g=n[0],v=n.slice(1);if(g){switch(g){case"profile":return void await async function(o){const t=o[0],n=o[1];if(!t||"list"===t){const o=r.getActiveProfileName(),t=r.listProfiles().map(t=>{const n=t===o;return`${n?e.green("●"):e.gray("○")} ${t}${n?e.gray(" ("+i.t("config.profile_active_label")+")"):""}`});return void y(i.t("config.profile_list_title"),t)}if("create"===t){const o=u(n,"profile");return r.listProfiles().includes(o)?void console.log(e.yellow(i.t("config.profile_exists",{name:o}))):void await c({description:i.t("config.profile_create_dry_run",{name:o}),json:{action:"profile_create",name:o},apply:()=>{r.createProfile(o),console.log(e.green(i.t("config.profile_created",{name:o})))}})}if("use"===t){const o=u(n,"profile");return void await c({description:i.t("config.profile_use_dry_run",{name:o}),json:{action:"profile_use",name:o},apply:()=>{r.setActiveProfile(o),console.log(e.green(i.t("config.profile_activated",{name:o})))}})}if("delete"===t||"remove"===t){const o=u(n,"profile");return o===a&&1===r.listProfiles().length?void console.log(e.yellow(i.t("config.profile_cannot_delete_last"))):void await c({description:i.t("config.profile_delete_dry_run",{name:o}),json:{action:"profile_delete",name:o},apply:async()=>{await s({message:i.t("config.profile_delete_confirm",{name:o}),defaultValue:!1})?(r.deleteProfile(o),console.log(e.green(i.t("config.profile_deleted",{name:o})))):console.log(e.yellow(i.t("config.profile_delete_aborted")))}})}console.log(e.red(i.t("config.unknown_action",{action:t})))}(v);case"preset":return void await async function(o){const t=o[0],n=o[1];if(!t||"list"===t){const o=Object.entries(r.getCustomPresets()).map(([o,t])=>`${e.cyan(o)} ${e.gray(`H:${t.models.haiku} S:${t.models.sonnet} O:${t.models.opus}`)}`);return void y(i.t("config.preset_list_title"),o)}if("save"===t){const o=u(n,"preset");return void await c({description:i.t("config.preset_save_dry_run",{name:o}),json:{action:"preset_save",name:o},apply:()=>{r.saveCustomPreset(o,r.getModels()),console.log(e.green(i.t("config.preset_saved",{name:o})))}})}if("use"===t||"apply"===t){const o=u(n,"preset"),t=r.getCustomPresets()[o];return t?void await c({description:i.t("config.preset_use_dry_run",{name:o}),json:{action:"preset_use",name:o},apply:()=>{r.setModels(t.models),console.log(e.green(i.t("config.preset_applied",{name:o})))}}):void console.log(e.red(i.t("config.preset_not_found",{name:o})))}if("delete"===t||"remove"===t){const o=u(n,"preset");return void await c({description:i.t("config.preset_delete_dry_run",{name:o}),json:{action:"preset_delete",name:o},apply:()=>{r.deleteCustomPreset(o),console.log(e.green(i.t("config.preset_deleted",{name:o})))}})}console.log(e.red(i.t("config.unknown_action",{action:t})))}(v);case"tool-model":return void await async function(o){const t=o[0];if(!t||"list"===t){const o=r.getProfile().tool_models||{},t=Object.entries(o).map(([o,t])=>{const n=Object.entries(t).map(([o,e])=>`${o}=${e}`).join(" ");return`${e.cyan(o)} ${e.gray(n)}`});return void y(i.t("config.tool_model_list_title"),t)}if("set"===t){const t=(o[1]||"").trim(),n=o.length>=4?(o[2]||"").trim():"opus",a=(o.length>=4?o[3]:o[2]||"").trim();return l[t]?a&&_.test(n)?void await c({description:i.t("config.tool_model_set_dry_run",{tool:t,slot:n,model:a}),json:{action:"tool_model_set",tool:t,slot:n,model:a},apply:()=>{r.setToolModel(t,{[n]:a}),console.log(e.green(i.t("config.tool_model_set",{tool:t,slot:n,model:a})))}}):void console.log(e.red(i.t("config.tool_model_usage"))):void console.log(e.red(t?i.t("config.unknown_tool",{tool:t}):i.t("config.tool_model_usage")))}if("clear"===t||"remove"===t){const t=(o[1]||"").trim();return l[t]?void await c({description:i.t("config.tool_model_clear_dry_run",{tool:t}),json:{action:"tool_model_clear",tool:t},apply:()=>{r.setToolModel(t,null),console.log(e.green(i.t("config.tool_model_cleared",{tool:t})))}}):void console.log(e.red(t?i.t("config.unknown_tool",{tool:t}):i.t("config.tool_model_usage")))}console.log(e.red(i.t("config.unknown_action",{action:t})))}(v);case"backup":return void await function(o){return async function(o){const t=o[0],n=o[1];if(!t||"list"===t){const o=r.listBackups().map(o=>{const t=new Date(o.mtimeMs).toISOString();return`${e.cyan(o.name)} ${e.gray(t)}`});return void y(i.t("config.backup_list_title"),o)}if("create"!==t&&"snapshot"!==t)return"restore"===t?n?void await c({description:i.t("config.backup_restore_dry_run",{name:n}),json:{action:"backup_restore",name:n},apply:async()=>{await s({message:i.t("config.backup_restore_confirm",{name:n}),defaultValue:!1})?(r.restoreBackup(n),console.log(e.green(i.t("config.backup_restored",{name:n})))):console.log(e.yellow(i.t("config.backup_restore_aborted")))}}):void console.log(e.red(i.t("config.backup_restore_missing_name"))):void console.log(e.red(i.t("config.unknown_action",{action:t})));await c({description:i.t("config.backup_create_dry_run"),json:{action:"backup_create",name:n??null},apply:()=>{const o=r.createBackup(n);console.log(e.green(i.t("config.backup_created",{path:o})))}})}(o)}(v)}"claude-code"!==g&&"claude-code"!==v[0]?l[g]?await w(g,v,d):(console.error(e.red(i.t("config.unknown_command",{command:g}))),console.log(e.gray(i.t("config.usage_hint"))),Object.values(l).filter(o=>!o.hidden).forEach(o=>{console.log(e.gray(` - ${o.name}: ${o.displayName}`))})):await w("claude-code",v,d)}else await async function(t){if(t.nonInteractive||!f())return void console.log(e.red(i.t("config.requires_argument")));const{selectedTool:n}=await o.prompt([{type:"list",name:"selectedTool",message:i.t("wizard.select_tool"),choices:Object.values(l).filter(o=>!o.hidden).map(o=>({name:`${o.displayName} (${o.name})`,value:o.name}))}]);await w(n,[],t)}(d)}
|
|
1
|
+
import o from"inquirer";import e from"chalk";import{wizard as t}from"../lib/wizard.js";import{toolManager as n,SUPPORTED_TOOLS as l}from"../lib/tool-manager.js";import{i18n as i}from"../lib/i18n.js";import{configManager as r,DEFAULT_PROFILE_NAME as a}from"../lib/config.js";import{confirmAction as s,applyOrDryRun as c,requireTerminal as d}from"../lib/confirm.js";import{canPrompt as f,isDryRun as g,isNonInteractive as p,shouldAutoConfirm as m}from"../lib/runtime-options.js";const _=/^[a-zA-Z0-9._-]+$/;function u(o,e){const t=(o||"").trim();if(!t||!_.test(t))throw new Error(i.t(`config.${e}_invalid_name`,{name:t||"<empty>"}));return t}function y(o,t){console.log("\n"+e.bold(o)),0!==t.length?t.forEach(o=>console.log(" "+o)):console.log(e.gray(" ("+i.t("config.empty_list")+")"))}function v(o,t){if("backups"===t){const t=n.listToolBackups(o).flatMap(o=>{const t=o.file.replace(/^.*[/\\]/,"");return o.backups.length?o.backups.map(o=>`${e.cyan(t)} ${e.gray(o.name.split(".bak-")[1]||o.name)}`):[`${e.cyan(t)} ${e.gray("("+i.t("config.empty_list")+")")}`]});return void y(i.t("config.tool_backups_title",{tool:l[o].displayName}),t)}const r=n.restorePrivateConfig(o).filter(o=>o.result.restored);0!==r.length?(console.log(e.green(i.t("config.config_restored",{tool:l[o].displayName}))),r.forEach(o=>console.log(e.gray(` ${o.file} ← ${o.result.from}`))),console.log(e.yellow(" ! "+i.t("config.reopen_hint",{tool:l[o].displayName})))):console.log(e.yellow(i.t("config.restore_no_backup",{tool:l[o].displayName})))}async function w(a,p,m){if(!l[a])return void console.log(e.red(i.t("config.unknown_tool",{tool:a})));const _=(p[0]||"").toLowerCase();if("backups"!==_&&"list-backups"!==_)if("restore"!==_){if("remove"===_||"unload"===_){const o=l[a].displayName;return void await c({description:i.t("config.remove_dry_run",{tool:o}),json:{action:"remove",tool:a},apply:async()=>{if(!await s({message:i.t("config.confirm_remove",{tool:o,name:a}),defaultValue:!0}))return;const t=n.holdsOurConfig(a),l=n.unloadPrivateConfig(a);t?(console.log(e.green(i.t("config.remove_done",{tool:o}))),l.forEach(o=>console.log(e.yellow(" ! "+o.text)))):console.log(e.gray(i.t("config.remove_nothing",{tool:o})))}})}if("clear-model"===_||"clear-models"===_){const o=l[a].displayName;return n.supportsClearPinnedModels(a)?void await c({description:i.t("config.clear_model_dry_run",{tool:o}),json:{action:"clear_model",tool:a},apply:async()=>{const t=await n.clearPinnedModels(a);0!==t.length?(console.log(e.green(i.t("config.clear_model_done",{tool:o}))),t.forEach(o=>console.log(e.gray(` - ${o}`))),console.log(e.gray(i.t("config.clear_model_restart_hint",{tool:o})))):console.log(e.yellow(i.t("config.clear_model_none",{tool:o})))}}):void console.log(e.yellow(i.t("config.clear_model_unsupported",{tool:o})))}if(g())await c({description:i.t("config.tool_apply_dry_run",{tool:l[a].displayName}),json:{action:"apply",tool:a,force:Boolean(m.force)},apply:()=>{}});else{if(!n.isToolInstalled(a)){if(m.nonInteractive)return void console.log(e.yellow(i.t("wizard.tool_not_installed",{tool:l[a].displayName})));if(console.log(e.yellow(i.t("wizard.tool_not_installed",{tool:l[a].displayName}))),!f())return;const{shouldInstall:t}=await o.prompt([{type:"confirm",name:"shouldInstall",message:i.t("wizard.install_tool_confirm"),default:!0}]);if(!t)return void console.log(e.yellow(i.t("wizard.install_skipped")));try{await n.installTool(a)}catch(o){return console.error(e.red(i.t("install.install_failed_detail"))),void(o instanceof Error&&o.message&&console.error(e.gray(o.message)))}}if(m.nonInteractive||m.force){const o=r.getApiKeyForTool(a);if(!o)return void console.log(e.red(i.t("auth.reload_missing_config")));if(m.force&&!m.nonInteractive&&!await s({message:i.t("config.confirm_force",{tool:l[a].displayName}),defaultValue:!1}))return;return await n.applyPrivateConfig(a,o,r.getModelsForTool(a),{force:m.force,takeOver:!0}),m.force&&console.log(e.yellow(i.t("config.force_done",{tool:l[a].displayName,name:a}))),void n.printApplied(a)}d(),await t.showToolMenu(a)}}else await c({description:i.t("config.tool_restore_dry_run",{tool:l[a].displayName}),json:{action:"tool_restore",tool:a},apply:async()=>{await s({message:i.t("config.confirm_restore",{tool:l[a].displayName}),defaultValue:!1})&&v(a,"restore")}});else v(a,"backups")}export async function configCommand(t){const{positional:n,options:d}=function(o){const e=[],t={};(p()||m())&&(t.nonInteractive=!0);for(const n of o)"--non-interactive"===n||"-y"===n?t.nonInteractive=!0:"--force"===n||"-f"===n?t.force=!0:e.push(n);return{positional:e,options:t}}(t),g=n[0],v=n.slice(1);if(g){switch(g){case"profile":return void await async function(o){const t=o[0],n=o[1];if(!t||"list"===t){const o=r.getActiveProfileName(),t=r.listProfiles().map(t=>{const n=t===o;return`${n?e.green("●"):e.gray("○")} ${t}${n?e.gray(" ("+i.t("config.profile_active_label")+")"):""}`});return void y(i.t("config.profile_list_title"),t)}if("create"===t){const o=u(n,"profile");return r.listProfiles().includes(o)?void console.log(e.yellow(i.t("config.profile_exists",{name:o}))):void await c({description:i.t("config.profile_create_dry_run",{name:o}),json:{action:"profile_create",name:o},apply:()=>{r.createProfile(o),console.log(e.green(i.t("config.profile_created",{name:o})))}})}if("use"===t){const o=u(n,"profile");return void await c({description:i.t("config.profile_use_dry_run",{name:o}),json:{action:"profile_use",name:o},apply:()=>{r.setActiveProfile(o),console.log(e.green(i.t("config.profile_activated",{name:o})))}})}if("delete"===t||"remove"===t){const o=u(n,"profile");return o===a&&1===r.listProfiles().length?void console.log(e.yellow(i.t("config.profile_cannot_delete_last"))):void await c({description:i.t("config.profile_delete_dry_run",{name:o}),json:{action:"profile_delete",name:o},apply:async()=>{await s({message:i.t("config.profile_delete_confirm",{name:o}),defaultValue:!1})?(r.deleteProfile(o),console.log(e.green(i.t("config.profile_deleted",{name:o})))):console.log(e.yellow(i.t("config.profile_delete_aborted")))}})}console.log(e.red(i.t("config.unknown_action",{action:t})))}(v);case"preset":return void await async function(o){const t=o[0],n=o[1];if(!t||"list"===t){const o=Object.entries(r.getCustomPresets()).map(([o,t])=>`${e.cyan(o)} ${e.gray(`H:${t.models.haiku} S:${t.models.sonnet} O:${t.models.opus}`)}`);return void y(i.t("config.preset_list_title"),o)}if("save"===t){const o=u(n,"preset");return void await c({description:i.t("config.preset_save_dry_run",{name:o}),json:{action:"preset_save",name:o},apply:()=>{r.saveCustomPreset(o,r.getModels()),console.log(e.green(i.t("config.preset_saved",{name:o})))}})}if("use"===t||"apply"===t){const o=u(n,"preset"),t=r.getCustomPresets()[o];return t?void await c({description:i.t("config.preset_use_dry_run",{name:o}),json:{action:"preset_use",name:o},apply:()=>{r.setModels(t.models),console.log(e.green(i.t("config.preset_applied",{name:o})))}}):void console.log(e.red(i.t("config.preset_not_found",{name:o})))}if("delete"===t||"remove"===t){const o=u(n,"preset");return void await c({description:i.t("config.preset_delete_dry_run",{name:o}),json:{action:"preset_delete",name:o},apply:()=>{r.deleteCustomPreset(o),console.log(e.green(i.t("config.preset_deleted",{name:o})))}})}console.log(e.red(i.t("config.unknown_action",{action:t})))}(v);case"tool-model":return void await async function(o){const t=o[0];if(!t||"list"===t){const o=r.getProfile().tool_models||{},t=Object.entries(o).map(([o,t])=>{const n=Object.entries(t).map(([o,e])=>`${o}=${e}`).join(" ");return`${e.cyan(o)} ${e.gray(n)}`});return void y(i.t("config.tool_model_list_title"),t)}if("set"===t){const t=(o[1]||"").trim(),n=o.length>=4?(o[2]||"").trim():"opus",a=(o.length>=4?o[3]:o[2]||"").trim();return l[t]?a&&_.test(n)?void await c({description:i.t("config.tool_model_set_dry_run",{tool:t,slot:n,model:a}),json:{action:"tool_model_set",tool:t,slot:n,model:a},apply:()=>{r.setToolModel(t,{[n]:a}),console.log(e.green(i.t("config.tool_model_set",{tool:t,slot:n,model:a})))}}):void console.log(e.red(i.t("config.tool_model_usage"))):void console.log(e.red(t?i.t("config.unknown_tool",{tool:t}):i.t("config.tool_model_usage")))}if("clear"===t||"remove"===t){const t=(o[1]||"").trim();return l[t]?void await c({description:i.t("config.tool_model_clear_dry_run",{tool:t}),json:{action:"tool_model_clear",tool:t},apply:()=>{r.setToolModel(t,null),console.log(e.green(i.t("config.tool_model_cleared",{tool:t})))}}):void console.log(e.red(t?i.t("config.unknown_tool",{tool:t}):i.t("config.tool_model_usage")))}console.log(e.red(i.t("config.unknown_action",{action:t})))}(v);case"backup":return void await function(o){return async function(o){const t=o[0],n=o[1];if(!t||"list"===t){const o=r.listBackups().map(o=>{const t=new Date(o.mtimeMs).toISOString();return`${e.cyan(o.name)} ${e.gray(t)}`});return void y(i.t("config.backup_list_title"),o)}if("create"!==t&&"snapshot"!==t)return"restore"===t?n?void await c({description:i.t("config.backup_restore_dry_run",{name:n}),json:{action:"backup_restore",name:n},apply:async()=>{await s({message:i.t("config.backup_restore_confirm",{name:n}),defaultValue:!1})?(r.restoreBackup(n),console.log(e.green(i.t("config.backup_restored",{name:n})))):console.log(e.yellow(i.t("config.backup_restore_aborted")))}}):void console.log(e.red(i.t("config.backup_restore_missing_name"))):void console.log(e.red(i.t("config.unknown_action",{action:t})));await c({description:i.t("config.backup_create_dry_run"),json:{action:"backup_create",name:n??null},apply:()=>{const o=r.createBackup(n);console.log(e.green(i.t("config.backup_created",{path:o})))}})}(o)}(v)}"claude-code"!==g&&"claude-code"!==v[0]?l[g]?await w(g,v,d):(console.error(e.red(i.t("config.unknown_command",{command:g}))),console.log(e.gray(i.t("config.usage_hint"))),Object.values(l).filter(o=>!o.hidden).forEach(o=>{console.log(e.gray(` - ${o.name}: ${o.displayName}`))})):await w("claude-code",v,d)}else await async function(t){if(t.nonInteractive||!f())return void console.log(e.red(i.t("config.requires_argument")));const{selectedTool:n}=await o.prompt([{type:"list",name:"selectedTool",message:i.t("wizard.select_tool"),choices:Object.values(l).filter(o=>!o.hidden).map(o=>({name:`${o.displayName} (${o.name})`,value:o.name}))}]);await w(n,[],t)}(d)}
|
package/dist/commands/setup.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import o from"chalk";import{getAdapter as e}from"../lib/tools/registry.js";import{postApplyPrompts as t}from"../lib/post-apply.js";import{ensureReachableGatewayAddress as i}from"./base.js";import{i18n as l}from"../lib/i18n.js";import{configManager as s}from"../lib/config.js";import{toolManager as a,SUPPORTED_TOOLS as n}from"../lib/tool-manager.js";import{validateApiKey as r,fetchModels as p}from"../lib/api-validator.js";import{CODEX_ONE_MILLION_CONTEXT_SETTING as d,setCodexOneMillionContext as c}from"../lib/tools/codex.js";import{resolveModelCatalog as u}from"../lib/model-catalog.js";import{CLAUDE_DESKTOP_FORCE_REGISTRY_SETTING as f,CLAUDE_DESKTOP_HIDE_OFFICIAL_LOGIN_SETTING as m}from"../lib/tools/claude-desktop.js";import{operationError as g,authenticationError as y,networkError as _,EXIT_CODES as k}from"../lib/errors.js";import{canPrompt as h,emitJson as b,isColorEnabled as w,isDryRun as v,isJsonMode as x,isNonInteractive as C,shouldAutoConfirm as j}from"../lib/runtime-options.js";import{appsForTools as K,appUpgradeCommand as $,getApp as T}from"../lib/app-update.js";import{join as M}from"path";import{confirmWritePlan as G,printWritePlan as S,toolWritePlan as I}from"../lib/write-plan.js";import N from"inquirer";import{logger as E}from"../utils/logger.js";import{forgetInstalls as A}from"../lib/tools/tool-installs.js";import{CLI_NAME as O,brandEnv as B}from"../lib/brand.js";export const DEDICATED_KEYS=[{option:"claudekey",tool:"claude-code",need:"claude"},{option:"codexkey",tool:"codex",need:"gpt"},{option:"grokkey",tool:"grok",need:"grok"},{option:"desktopkey",tool:"claude-desktop",need:"claude"},{option:"agykey",tool:"agy",need:"gemini"},{option:"openclawkey",tool:"openclaw",need:"any",hidden:!0},{option:"hermeskey",tool:"hermes",need:"any",hidden:!0},{option:"atomcodekey",tool:"atomcode",need:"any",hidden:!0}];async function D(o){const e=await p(o);if(!e.ok)return{hasClaude:!0,hasGpt:!0,hasGrok:!0,hasGemini:!0,probed:!1};const t=e.models.map(o=>o.toLowerCase());return{hasClaude:t.some(o=>o.includes("claude")),hasGpt:t.some(o=>o.startsWith("gpt")||o.includes("gpt-")||/^o\d/.test(o)),hasGrok:t.some(o=>o.includes("grok")),hasGemini:t.some(o=>o.startsWith("gemini")),probed:!0}}function F(o,e){switch(e){case"claude":return o.hasClaude;case"gpt":return o.hasGpt;case"grok":return o.hasGrok;case"gemini":return o.hasGemini;default:return!0}}export function parseOnOff(o){const e=(o??"").trim().toLowerCase();return!!["on","true","show","1","yes","y"].includes(e)||!["off","false","hide","0","no","n"].includes(e)&&void 0}function L(e){console.log(o.green(e))}function R(e){console.log(o.yellow(e))}function P(e){console.error(o.red(e))}let Y=null;function q(o){const e={operation:1,authentication:2,network:3};(!Y||e[o]>e[Y])&&(Y=o)}async function z(e,t){x()||console.log(o.gray(l.t("setup.validating",{label:e})));const i=await r(t);if(i.valid)return!0;const a=l.t("errors.key_placeholder"),n="--key"===e?`${O} setup --key ${a}`:`${O} setup ${e} ${a}`;let p;if("network_error"===i.error?(q("network"),p=_(l.t("setup.unreachable",{url:s.getBaseUrl(),reason:i.message||"network error"}),`${O} base auto`,"network_error")):"invalid_api_key"===i.error?(q("authentication"),p=y(l.t("setup.invalid_key",{label:e,reason:i.message||"invalid"}),n,"invalid_api_key")):(q("operation"),p=g(l.t("setup.validate_failed",{label:e,reason:i.message||i.error||"unknown"}),n,i.error)),x())throw p;return P(p.message),console.log(o.gray(l.t("errors.next_step",{command:p.hint}))),!1}async function H(t,i,r={}){const{oneMillion:p,officialLogin:c,desktopRegistry:u,force:y}=r,_=n[t];try{"claude-desktop"===t&&void 0!==u&&s.setToolSetting("claude-desktop",f,u),"codex"===t&&void 0!==p&&s.setToolSetting("codex",d,p),"claude-desktop"===t&&void 0!==c&&s.setToolSetting("claude-desktop",m,!c),await a.applyPrivateConfig(t,i,s.getModelsForTool(t),{force:y})}catch(o){if(x())throw g(o instanceof Error?o.message:String(o),`${O} setup --key ${l.t("errors.key_placeholder")}`,"config_write_failed");const e=(o instanceof Error?o.message:String(o)).replace(/\s+/g," ").trim();return P("× "+l.t("setup.apply_failed",{tool:_.displayName,reason:e})),!1}return a.printApplied(t),y&&console.log(o.gray(" "+l.t("config.force_done",{tool:_.displayName,name:t}))),"codex"===t&&void 0!==p&&console.log(o.gray(" "+l.t(p?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))),e(t)?.isInstalled&&!a.isToolInstalled(t)&&console.log(o.yellow(" ! "+l.t("setup.gui_preconfigured",{tool:_.displayName,install:_.installCommand}))),!0}function W(o){return!e(o)?.isInstalled&&!a.isToolInstalled(o)}export async function installMissingApps(e,t){const i=K(e).filter(o=>{const e=T(o)?.appInstaller;try{return Boolean(e&&!e.blocker()&&!e.installedPath())}catch(e){return E.logError(`setup: app installed? ${o}`,e),!1}});if(0===i.length)return!1;const s=o=>T(o)?.displayName??o;let a=t;if(void 0===a&&!x())if(j())a=!C();else if(h()){const o=i.map(s).join(l.t("setup.list_separator")),e=i.reduce((o,e)=>o+(T(e)?.appInstaller.sizeMb()??0),0);console.log(""),a=(await N.prompt([{type:"confirm",name:"go",message:l.t("setup.install_app_confirm",{tools:o,size:String(e)}),default:!0}])).go}if(!a&&!x())for(const e of i)console.log(o.gray(l.t("setup.install_app_hint",{tool:s(e),command:$(e)})));return Boolean(a)}function U(o){R(l.t("setup.still_missing",{tools:o.map(o=>n[o].displayName).join(l.t("setup.list_separator")),commands:o.map(o=>`${O} config ${o} --yes`).join(l.t("setup.list_separator"))}))}function J(e,t,i=[]){const a=I(e),n=M(s.getConfigDir(),"config.yaml");if(x())b({dry_run:!0,tools:a,install_first:i,key_file:t?n:null});else{if(0===a.length&&0===i.length){const e=(w()?o.cyan:o=>o)(l.t("runtime.dry_run_label"));return void console.log(`${e} ${l.t("setup.dry_run_none")}`)}S(a,{label:l.t("runtime.dry_run_label"),keyFile:t?n:void 0,installFirst:i})}}function Q(){return"darwin"===process.platform||"win32"===process.platform||a.isToolInstalled("claude-desktop")}export async function setupCommand(r){Y=null;const p=r.key?.trim(),f=DEDICATED_KEYS.map(o=>({...o,apiKey:r[o.option]?.trim()})).filter(o=>Boolean(o.apiKey)),m=void 0!==r.oneMillion,g=void 0!==r.claudeOfficial;let y;if(g&&(y=parseOnOff(r.claudeOfficial),void 0===y))return P(l.t("setup.claude_official_invalid",{value:String(r.claudeOfficial)})),void(process.exitCode=1);const _=void 0!==r.desktopRegistry;let h;if(_&&(h=parseOnOff(r.desktopRegistry),void 0===h))return P(l.t("setup.desktop_registry_invalid",{value:String(r.desktopRegistry)})),void(process.exitCode=1);const b=g||_,w=Boolean(p||f.length>0),K={oneMillion:r.oneMillion,officialLogin:y,desktopRegistry:h,force:r.force};if(!w&&(m||b)&&v())return void J([...m?["codex"]:[],...b?["claude-desktop"]:[]],!1);if(!w&&(m||b)){if(m){const o=s.getApiKeyForTool("codex");if(o)try{const e=!0===r.oneMillion;s.setToolSetting("codex",d,e);const t=await u(o);c(e,t??void 0),L(l.t(e?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))}catch(o){P(l.t("setup.apply_failed",{tool:n.codex.displayName,reason:o instanceof Error?o.message:String(o)})),process.exitCode=1}else P(l.t("setup.codex_1m_missing_key")),process.exitCode=1}if(b){const o=s.getApiKeyForTool("claude-desktop");o?await H("claude-desktop",o,{...K,oneMillion:void 0})?await t(["claude-desktop"],{apps:{install:!1}}):process.exitCode=1:(P(l.t("setup.desktop_official_login_missing_key")),process.exitCode=1)}return}if(!w)return P(l.t("setup.usage")),void(process.exitCode=1);const $=v();$||await i(p||f[0].apiKey);const T=[],S=[];let I,q=0,V=0;const X=new Set,Z=f.map(o=>({tool:o.tool,label:`--${o.option}`,apiKey:o.apiKey,need:o.need}));for(const o of Z){if(!await z(o.label,o.apiKey)){V+=1;continue}const e=await D(o.apiKey);!e.probed||F(e,o.need)?(e.probed||R(l.t("setup.probe_failed",{label:o.label})),T.push({tool:o.tool,apiKey:o.apiKey,dedicated:!0}),X.add(o.tool)):(P(l.t(`setup.key_no_${o.need}`,{label:o.label,tool:n[o.tool].displayName})),V+=1)}const oo=!!p&&await z("--key",p);p&&!oo&&(I=k[Y??"operation"]);const eo=p&&oo?await D(p):void 0;if(p&&eo&&!eo.probed&&R(l.t("setup.probe_failed",{label:"--key"})),!(p&&eo&&eo.probed)||eo.hasClaude||eo.hasGpt||eo.hasGrok||eo.hasGemini||(P(l.t("setup.key_no_family")),I=1),p&&eo&&void 0===I){const o=[{tool:"claude-code",capable:eo.hasClaude,skipMsg:l.t(Q()?"setup.skip_no_claude_desktop":"setup.skip_no_claude")},{tool:"codex",capable:eo.hasGpt,skipMsg:l.t("setup.skip_no_gpt")},{tool:"grok",capable:eo.hasGrok,skipMsg:l.t("setup.skip_no_grok")},{tool:"claude-desktop",capable:eo.hasClaude&&Q(),skipMsg:"",quiet:!0},{tool:"agy",capable:eo.hasGemini,skipMsg:l.t("setup.skip_no_gemini")}];for(const e of o)X.has(e.tool)||(e.capable?(T.push({tool:e.tool,apiKey:p,dedicated:!1}),X.add(e.tool)):e.quiet||R(e.skipMsg))}const to=Boolean(p&&oo&&void 0===I);if($)return J(T.filter(o=>!W(o.tool)).map(o=>o.tool),T.length>0,T.filter(o=>W(o.tool)).map(o=>o.tool)),void((V>0||void 0!==I)&&(process.exitCode=I??k[Y??"operation"]));const io=T.filter(o=>!W(o.tool)),lo=to||T.some(o=>o.dedicated);if((T.length>0||lo)&&!r.fromInstaller&&!await G(io.map(o=>o.tool),{keyFile:lo?M(s.getConfigDir(),"config.yaml"):void 0,installFirst:T.filter(o=>W(o.tool)).map(o=>o.tool)}))return void(process.exitCode=k.cancelled);to&&p&&s.setApiKey(p);for(const o of T)o.dedicated&&s.setToolApiKey(o.tool,o.apiKey),W(o.tool)?S.push({tool:o.tool,apiKey:o.apiKey}):await H(o.tool,o.apiKey,K)?q+=1:(V+=1,X.delete(o.tool));if(void 0!==I)return S.length>0&&U(S.map(o=>o.tool)),void(process.exitCode=I);let so=[];if(S.length>0){const e=await async function(e,t,i){const s=e.map(o=>o.tool);if(0===s.length)return{still:[],applied:[],failed:[]};const r=s.map(o=>n[o].displayName).join(l.t("setup.list_separator"));let p=t;if(void 0!==p||x()||(j()?p=!C():process.stdin.isTTY&&(console.log(""),p=(await N.prompt([{type:"confirm",name:"go",message:l.t("setup.install_confirm",{tools:r}),default:!0}])).go)),!p){console.log("");for(const e of s)console.log(o.yellow("! "+l.t("setup.install_hint",{tool:n[e].displayName,install:n[e].installCommand})));return x()||console.log(o.gray(l.t("setup.install_all_hint",{cli:O}))),{still:s,applied:[],failed:[]}}const d=[];for(const e of s){console.log(o.bold(`\n${n[e].displayName}`));try{await a.installTool(e)}catch(o){E.logError(`setup: install ${e}`,o)}A(),a.isToolInstalled(e)||d.push(e)}const c=s.filter(o=>!d.includes(o));c.length>0&&console.log("");const u=[],f=[];for(const{tool:o,apiKey:t}of e.filter(o=>c.includes(o.tool)))await H(o,t,i)?u.push(o):f.push(o);return{still:d,applied:u,failed:f}}(S,r.install,K);so=e.still,q+=e.applied.length,V+=e.failed.length;for(const o of[...so,...e.failed])X.delete(o)}if(q>0){const t=[...X].filter(o=>!e(o)?.isInstalled&&!so.includes(o)),i=t.map(o=>n[o]?.command).filter(Boolean).join(" / "),s=l.t("setup.done",{count:String(q)});L("\n"+(i?`${s}${l.t("setup.done_commands",{commands:i})}`:s)),so.length>0&&U(so);for(const o of t){const e=a.ensureNativeOnPath(o);e&&L(l.t("setup.path_added",{tool:n[o].displayName,dir:e}))}const r=t.filter(o=>!a.isOnPath(o));if(r.length>0&&!x()&&"1"!==B("PATH_HINT_BY_CALLER")){const e=r.map(o=>n[o].command).join(" / ");console.log(o.yellow(" ! "+l.t("win32"===process.platform?"setup.path_reload_windows":"setup.path_reload",{commands:e})))}}0===q&&so.length>0&&U(so),V>0&&(P(l.t("setup.failed_summary",{count:String(V)})),x()||console.error(`${O} setup --key ${l.t("errors.key_placeholder")}`)),await t([...X],{apps:{install:await installMissingApps([...X],r.install)}}),(V>0||0===q&&0===so.length)&&(process.exitCode=k[Y??"operation"])}
|
|
1
|
+
import o from"chalk";import{getAdapter as e}from"../lib/tools/registry.js";import{postApplyPrompts as t}from"../lib/post-apply.js";import{ensureReachableGatewayAddress as i}from"./base.js";import{i18n as l}from"../lib/i18n.js";import{configManager as s}from"../lib/config.js";import{toolManager as a,SUPPORTED_TOOLS as n}from"../lib/tool-manager.js";import{validateApiKey as r,fetchModels as p}from"../lib/api-validator.js";import{CODEX_ONE_MILLION_CONTEXT_SETTING as d,setCodexOneMillionContext as c}from"../lib/tools/codex.js";import{resolveModelCatalog as u}from"../lib/model-catalog.js";import{CLAUDE_DESKTOP_FORCE_REGISTRY_SETTING as f,CLAUDE_DESKTOP_HIDE_OFFICIAL_LOGIN_SETTING as m}from"../lib/tools/claude-desktop.js";import{operationError as g,authenticationError as y,networkError as _,EXIT_CODES as k}from"../lib/errors.js";import{canPrompt as h,emitJson as b,isColorEnabled as v,isDryRun as w,isJsonMode as x,isNonInteractive as C,shouldAutoConfirm as j}from"../lib/runtime-options.js";import{appsForTools as K,appUpgradeCommand as $,getApp as T}from"../lib/app-update.js";import{join as M}from"path";import{asksBeforeWriting as G,confirmWritePlan as O,printWritePlan as S,toolWritePlan as I}from"../lib/write-plan.js";import N from"inquirer";import{logger as E}from"../utils/logger.js";import{forgetInstalls as A}from"../lib/tools/tool-installs.js";import{CLI_NAME as B,brandEnv as D}from"../lib/brand.js";export const DEDICATED_KEYS=[{option:"claudekey",tool:"claude-code",need:"claude"},{option:"codexkey",tool:"codex",need:"gpt"},{option:"grokkey",tool:"grok",need:"grok"},{option:"desktopkey",tool:"claude-desktop",need:"claude"},{option:"agykey",tool:"agy",need:"gemini"},{option:"openclawkey",tool:"openclaw",need:"any",hidden:!0},{option:"hermeskey",tool:"hermes",need:"any",hidden:!0},{option:"atomcodekey",tool:"atomcode",need:"any",hidden:!0}];async function F(o){const e=await p(o);if(!e.ok)return{hasClaude:!0,hasGpt:!0,hasGrok:!0,hasGemini:!0,probed:!1};const t=e.models.map(o=>o.toLowerCase());return{hasClaude:t.some(o=>o.includes("claude")),hasGpt:t.some(o=>o.startsWith("gpt")||o.includes("gpt-")||/^o\d/.test(o)),hasGrok:t.some(o=>o.includes("grok")),hasGemini:t.some(o=>o.startsWith("gemini")),probed:!0}}function L(o,e){switch(e){case"claude":return o.hasClaude;case"gpt":return o.hasGpt;case"grok":return o.hasGrok;case"gemini":return o.hasGemini;default:return!0}}export function parseOnOff(o){const e=(o??"").trim().toLowerCase();return!!["on","true","show","1","yes","y"].includes(e)||!["off","false","hide","0","no","n"].includes(e)&&void 0}function R(e){console.log(o.green(e))}function P(e){console.log(o.yellow(e))}function Y(e){console.error(o.red(e))}let q=null;function z(o){const e={operation:1,authentication:2,network:3};(!q||e[o]>e[q])&&(q=o)}async function H(e,t){x()||console.log(o.gray(l.t("setup.validating",{label:e})));const i=await r(t);if(i.valid)return!0;const a=l.t("errors.key_placeholder"),n="--key"===e?`${B} setup --key ${a}`:`${B} setup ${e} ${a}`;let p;if("network_error"===i.error?(z("network"),p=_(l.t("setup.unreachable",{url:s.getBaseUrl(),reason:i.message||"network error"}),`${B} base auto`,"network_error")):"invalid_api_key"===i.error?(z("authentication"),p=y(l.t("setup.invalid_key",{label:e,reason:i.message||"invalid"}),n,"invalid_api_key")):(z("operation"),p=g(l.t("setup.validate_failed",{label:e,reason:i.message||i.error||"unknown"}),n,i.error)),x())throw p;return Y(p.message),console.log(o.gray(l.t("errors.next_step",{command:p.hint}))),!1}async function W(t,i,r={}){const{oneMillion:p,officialLogin:c,desktopRegistry:u,force:y,takeOver:_}=r,k=n[t];try{"claude-desktop"===t&&void 0!==u&&s.setToolSetting("claude-desktop",f,u),"codex"===t&&void 0!==p&&s.setToolSetting("codex",d,p),"claude-desktop"===t&&void 0!==c&&s.setToolSetting("claude-desktop",m,!c),await a.applyPrivateConfig(t,i,s.getModelsForTool(t),{force:y,takeOver:_})}catch(o){if(x())throw g(o instanceof Error?o.message:String(o),`${B} setup --key ${l.t("errors.key_placeholder")}`,"config_write_failed");const e=(o instanceof Error?o.message:String(o)).replace(/\s+/g," ").trim();return Y("× "+l.t("setup.apply_failed",{tool:k.displayName,reason:e})),!1}return a.printApplied(t),y&&console.log(o.gray(" "+l.t("config.force_done",{tool:k.displayName,name:t}))),"codex"===t&&void 0!==p&&console.log(o.gray(" "+l.t(p?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))),e(t)?.isInstalled&&!a.isToolInstalled(t)&&console.log(o.yellow(" ! "+l.t("setup.gui_preconfigured",{tool:k.displayName,install:k.installCommand}))),!0}function U(o){return!e(o)?.isInstalled&&!a.isToolInstalled(o)}export async function installMissingApps(e,t){const i=K(e).filter(o=>{const e=T(o)?.appInstaller;try{return Boolean(e&&!e.blocker()&&!e.installedPath())}catch(e){return E.logError(`setup: app installed? ${o}`,e),!1}});if(0===i.length)return!1;const s=o=>T(o)?.displayName??o;let a=t;if(void 0===a&&!x())if(j())a=!C();else if(h()){const o=i.map(s).join(l.t("setup.list_separator")),e=i.reduce((o,e)=>o+(T(e)?.appInstaller.sizeMb()??0),0);console.log(""),a=(await N.prompt([{type:"confirm",name:"go",message:l.t("setup.install_app_confirm",{tools:o,size:String(e)}),default:!0}])).go}if(!a&&!x())for(const e of i)console.log(o.gray(l.t("setup.install_app_hint",{tool:s(e),command:$(e)})));return Boolean(a)}function J(o){P(l.t("setup.still_missing",{tools:o.map(o=>n[o].displayName).join(l.t("setup.list_separator")),commands:o.map(o=>`${B} config ${o} --yes`).join(l.t("setup.list_separator"))}))}function Q(e,t,i=[]){const a=I(e),n=M(s.getConfigDir(),"config.yaml");if(x())b({dry_run:!0,tools:a,install_first:i,key_file:t?n:null});else{if(0===a.length&&0===i.length){const e=(v()?o.cyan:o=>o)(l.t("runtime.dry_run_label"));return void console.log(`${e} ${l.t("setup.dry_run_none")}`)}S(a,{label:l.t("runtime.dry_run_label"),keyFile:t?n:void 0,installFirst:i})}}function V(){return"darwin"===process.platform||"win32"===process.platform||a.isToolInstalled("claude-desktop")}export async function setupCommand(r){q=null;const p=r.key?.trim(),f=DEDICATED_KEYS.map(o=>({...o,apiKey:r[o.option]?.trim()})).filter(o=>Boolean(o.apiKey)),m=void 0!==r.oneMillion,g=void 0!==r.claudeOfficial;let y;if(g&&(y=parseOnOff(r.claudeOfficial),void 0===y))return Y(l.t("setup.claude_official_invalid",{value:String(r.claudeOfficial)})),void(process.exitCode=1);const _=void 0!==r.desktopRegistry;let h;if(_&&(h=parseOnOff(r.desktopRegistry),void 0===h))return Y(l.t("setup.desktop_registry_invalid",{value:String(r.desktopRegistry)})),void(process.exitCode=1);const b=g||_,v=Boolean(p||f.length>0),K={oneMillion:r.oneMillion,officialLogin:y,desktopRegistry:h,force:r.force};if(!v&&(m||b)&&w())return void Q([...m?["codex"]:[],...b?["claude-desktop"]:[]],!1);if(!v&&(m||b)){if(m){const o=s.getApiKeyForTool("codex");if(o)try{const e=!0===r.oneMillion;s.setToolSetting("codex",d,e);const t=await u(o);c(e,t??void 0),R(l.t(e?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))}catch(o){Y(l.t("setup.apply_failed",{tool:n.codex.displayName,reason:o instanceof Error?o.message:String(o)})),process.exitCode=1}else Y(l.t("setup.codex_1m_missing_key")),process.exitCode=1}if(b){const o=s.getApiKeyForTool("claude-desktop");o?await W("claude-desktop",o,{...K,oneMillion:void 0,takeOver:!0})?await t(["claude-desktop"],{apps:{install:!1}}):process.exitCode=1:(Y(l.t("setup.desktop_official_login_missing_key")),process.exitCode=1)}return}if(!v)return Y(l.t("setup.usage")),void(process.exitCode=1);const $=w();$||await i(p||f[0].apiKey);const T=[],S=[];let I,z=0,X=0;const Z=new Set,oo=f.map(o=>({tool:o.tool,label:`--${o.option}`,apiKey:o.apiKey,need:o.need}));for(const o of oo){if(!await H(o.label,o.apiKey)){X+=1;continue}const e=await F(o.apiKey);!e.probed||L(e,o.need)?(e.probed||P(l.t("setup.probe_failed",{label:o.label})),T.push({tool:o.tool,apiKey:o.apiKey,dedicated:!0}),Z.add(o.tool)):(Y(l.t(`setup.key_no_${o.need}`,{label:o.label,tool:n[o.tool].displayName})),X+=1)}const eo=!!p&&await H("--key",p);p&&!eo&&(I=k[q??"operation"]);const to=p&&eo?await F(p):void 0;if(p&&to&&!to.probed&&P(l.t("setup.probe_failed",{label:"--key"})),!(p&&to&&to.probed)||to.hasClaude||to.hasGpt||to.hasGrok||to.hasGemini||(Y(l.t("setup.key_no_family")),I=1),p&&to&&void 0===I){const o=[{tool:"claude-code",capable:to.hasClaude,skipMsg:l.t(V()?"setup.skip_no_claude_desktop":"setup.skip_no_claude")},{tool:"codex",capable:to.hasGpt,skipMsg:l.t("setup.skip_no_gpt")},{tool:"grok",capable:to.hasGrok,skipMsg:l.t("setup.skip_no_grok")},{tool:"claude-desktop",capable:to.hasClaude&&V(),skipMsg:"",quiet:!0},{tool:"agy",capable:to.hasGemini,skipMsg:l.t("setup.skip_no_gemini")}];for(const e of o)Z.has(e.tool)||(e.capable?(T.push({tool:e.tool,apiKey:p,dedicated:!1}),Z.add(e.tool)):e.quiet||P(e.skipMsg))}const io=Boolean(p&&eo&&void 0===I);if($)return Q(T.filter(o=>!U(o.tool)).map(o=>o.tool),T.length>0,T.filter(o=>U(o.tool)).map(o=>o.tool)),void((X>0||void 0!==I)&&(process.exitCode=I??k[q??"operation"]));const lo=T.filter(o=>!U(o.tool)),so=io||T.some(o=>o.dedicated),ao=!r.fromInstaller&&G();if((T.length>0||so)&&!r.fromInstaller&&!await O(lo.map(o=>o.tool),{keyFile:so?M(s.getConfigDir(),"config.yaml"):void 0,installFirst:T.filter(o=>U(o.tool)).map(o=>o.tool)}))return void(process.exitCode=k.cancelled);io&&p&&s.setApiKey(p);for(const o of T)o.dedicated&&s.setToolApiKey(o.tool,o.apiKey),U(o.tool)?S.push({tool:o.tool,apiKey:o.apiKey}):await W(o.tool,o.apiKey,{...K,takeOver:o.dedicated||ao})?z+=1:(X+=1,Z.delete(o.tool));if(void 0!==I)return S.length>0&&J(S.map(o=>o.tool)),void(process.exitCode=I);let no=[];if(S.length>0){const e=await async function(e,t,i){const s=e.map(o=>o.tool);if(0===s.length)return{still:[],applied:[],failed:[]};const r=s.map(o=>n[o].displayName).join(l.t("setup.list_separator"));let p=t;if(void 0!==p||x()||(j()?p=!C():process.stdin.isTTY&&(console.log(""),p=(await N.prompt([{type:"confirm",name:"go",message:l.t("setup.install_confirm",{tools:r}),default:!0}])).go)),!p){console.log("");for(const e of s)console.log(o.yellow("! "+l.t("setup.install_hint",{tool:n[e].displayName,install:n[e].installCommand})));return x()||console.log(o.gray(l.t("setup.install_all_hint",{cli:B}))),{still:s,applied:[],failed:[]}}const d=[];for(const e of s){console.log(o.bold(`\n${n[e].displayName}`));try{await a.installTool(e)}catch(o){E.logError(`setup: install ${e}`,o)}A(),a.isToolInstalled(e)||d.push(e)}const c=s.filter(o=>!d.includes(o));c.length>0&&console.log("");const u=[],f=[];for(const{tool:o,apiKey:t}of e.filter(o=>c.includes(o.tool)))await W(o,t,i)?u.push(o):f.push(o);return{still:d,applied:u,failed:f}}(S,r.install,K);no=e.still,z+=e.applied.length,X+=e.failed.length;for(const o of[...no,...e.failed])Z.delete(o)}if(z>0){const t=[...Z].filter(o=>!e(o)?.isInstalled&&!no.includes(o)),i=t.map(o=>n[o]?.command).filter(Boolean).join(" / "),s=l.t("setup.done",{count:String(z)});R("\n"+(i?`${s}${l.t("setup.done_commands",{commands:i})}`:s)),no.length>0&&J(no);for(const o of t){const e=a.ensureNativeOnPath(o);e&&R(l.t("setup.path_added",{tool:n[o].displayName,dir:e}))}const r=t.filter(o=>!a.isOnPath(o));if(r.length>0&&!x()&&"1"!==D("PATH_HINT_BY_CALLER")){const e=r.map(o=>n[o].command).join(" / ");console.log(o.yellow(" ! "+l.t("win32"===process.platform?"setup.path_reload_windows":"setup.path_reload",{commands:e})))}}0===z&&no.length>0&&J(no),X>0&&(Y(l.t("setup.failed_summary",{count:String(X)})),x()||console.error(`${B} setup --key ${l.t("errors.key_placeholder")}`)),await t([...Z],{apps:{install:await installMissingApps([...Z],r.install)}}),(X>0||0===z&&0===no.length)&&(process.exitCode=k[q??"operation"])}
|
|
@@ -64,6 +64,8 @@ export declare class ClaudeCodeManager {
|
|
|
64
64
|
private settingsPath;
|
|
65
65
|
private mcpConfigPath;
|
|
66
66
|
private lastTierFallbacks;
|
|
67
|
+
/** `NAME=value` of model settings the user chose that the last loadPrivateConfig left alone. */
|
|
68
|
+
private lastKept;
|
|
67
69
|
private constructor();
|
|
68
70
|
static getInstance(): ClaudeCodeManager;
|
|
69
71
|
/**
|
|
@@ -110,6 +112,8 @@ export declare class ClaudeCodeManager {
|
|
|
110
112
|
* @returns 实际被清除的键名(未改动时为空数组)
|
|
111
113
|
*/
|
|
112
114
|
clearPinnedModels(availableModels?: string[]): string[];
|
|
115
|
+
/** 最近一次 loadPrivateConfig 保留下来的用户自己的模型设置(`名字=值`)。 */
|
|
116
|
+
getLastKept(): string[];
|
|
113
117
|
/** 最近一次 loadPrivateConfig 实际写入的档位回退(用于应用后的提示)。 */
|
|
114
118
|
getLastTierFallbacks(): TierFallback[];
|
|
115
119
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as e,mkdirSync as t,readFileSync as n,writeFileSync as o,renameSync as
|
|
1
|
+
import{existsSync as e,mkdirSync as t,readFileSync as n,writeFileSync as o,renameSync as s,unlinkSync as i}from"fs";import{join as r,dirname as a}from"path";import{toolHome as l}from"./tools/types.js";import{logger as c}from"../utils/logger.js";import{configManager as d,DEFAULT_MODELS as g}from"./config.js";import{loadSettingsTemplate as u}from"./settings-template.js";import{cleanupAnthropicEnvVars as f}from"./env-cleanup.js";import{getClaudeCodeTierChains as m,getGatewayModelRules as O,getRetiredDefaultModelIds as C}from"./model-registry.js";import{claimField as h,forgetField as p,ownsValue as _}from"./written-values.js";import{noteWrittenGatewayUrl as E}from"./gateway-addresses.js";import{autoCompactTokens as A,modelBlurb as T,prettyModelName as v}from"./gateway-models.js";import{baseModelId as P}from"./model-catalog.js";export const MODEL_ENV_VARS=["ANTHROPIC_DEFAULT_HAIKU_MODEL","ANTHROPIC_DEFAULT_SONNET_MODEL","ANTHROPIC_DEFAULT_OPUS_MODEL","ANTHROPIC_DEFAULT_FABLE_MODEL","ANTHROPIC_MODEL","ANTHROPIC_REASONING_MODEL"];const N="claude-code.model",S=e=>`claude-code.env.${e}`;function M(e){const t=Object.values(m()).flat(),n=[...Object.values(g),...Object.values(e)].filter(e=>"string"==typeof e&&""!==e);return[...new Set(["opus[1m]",...n,...C(),...t])]}const D={opus:"ANTHROPIC_DEFAULT_OPUS_MODEL",sonnet:"ANTHROPIC_DEFAULT_SONNET_MODEL",haiku:"ANTHROPIC_DEFAULT_HAIKU_MODEL",fable:"ANTHROPIC_DEFAULT_FABLE_MODEL"},L="CLAUDE_CODE_MAX_CONTEXT_TOKENS";function y(){return r(d.getConfigDir(),"claude-code-picker.json")}function I(){try{const e=JSON.parse(n(y(),"utf-8")).ids;return Array.isArray(e)?e.map(String):[]}catch{return[]}}function R(e,t=[]){if(!e||"object"!=typeof e)return!1;const{options:n,replaceBuiltInOptions:o}=e;if(!Array.isArray(n)||o)return!1;const s=O(),i=new Set([...s.models.map(e=>e.id),...s.retired,...I(),...t]);return n.every(e=>e&&"object"==typeof e&&i.has(P(String(e.model))))}export function applyGatewayModels(e,n){if(void 0===n)return e;const s={...e};if(void 0!==s.modelPicker&&!R(s.modelPicker,n.map(e=>e.id)))return s;const i=R(s.modelPicker)?s.modelPicker.options.map(e=>P(e.model)):[],r={...s.env||{}},a={...s.modelSettings||{}};for(const e of i){if(!a[e])continue;const{autoCompactWindow:t,...n}=a[e];Object.keys(n).length>0?a[e]=n:delete a[e]}const l=e=>(e.contextWindow??0)>=1e6,g=n.filter(e=>!l(e)).map(e=>e.contextWindow).filter(e=>"number"==typeof e&&e>0);!function(e){try{t(d.getConfigDir(),{recursive:!0}),o(y(),JSON.stringify({ids:e})+"\n")}catch(e){c.logError("claudeCode.recordWrittenPickerIds",e)}}(n.map(e=>e.id));const u=e=>T(e.description,{millionContext:l(e)});if(0===n.length)delete s.modelPicker,delete r[L];else{s.modelPicker={options:n.map(e=>({model:l(e)?`${e.id}[1m]`:e.id,label:e.name||v(e.id),...u(e)?{description:u(e)}:{}}))},g.length>0?r[L]=String(Math.min(...g)):delete r[L];for(const e of n){if(!e.contextWindow)continue;const t=A(e.contextWindow,e.autoCompactTokens);a[e.id]={...a[e.id],autoCompactWindow:Math.min(Math.max(t,1e5),1e6)}}}return Object.keys(a).length>0?s.modelSettings=a:delete s.modelSettings,Object.keys(r).length>0?s.env=r:delete s.env,s}export function resolveTierFallbacks(e){if(!e||0===e.length)return[];const t=new Set(e.map(P)),n=[];for(const[e,o]of Object.entries(m())){const s=D[e],[i,...r]=o;if(!s||!i||t.has(i))continue;const a=r.find(e=>t.has(e));a&&n.push({tier:e,envVar:s,from:i,to:a})}return n}export class ClaudeCodeManager{static instance;settingsPath;mcpConfigPath;lastTierFallbacks=[];lastKept=[];constructor(){this.settingsPath=r(l(),".claude","settings.json"),this.mcpConfigPath=r(l(),".claude.json")}static getInstance(){return ClaudeCodeManager.instance||(ClaudeCodeManager.instance=new ClaudeCodeManager),ClaudeCodeManager.instance}ensureConfigDir(n){const o=a(n);e(o)||t(o,{recursive:!0})}atomicWrite(t,n){const r=`${t}.tmp`;try{o(r,n,"utf-8"),s(r,t)}catch(t){if(e(r))try{i(r)}catch{}throw t}}getSettings(){try{if(e(this.settingsPath)){const e=n(this.settingsPath,"utf-8");return JSON.parse(e)}}catch(e){c.warn("Failed to read Claude Code settings:",e),c.logError("ClaudeCodeManager.getSettings",e)}return{}}saveSettings(e){try{this.ensureConfigDir(this.settingsPath),this.atomicWrite(this.settingsPath,JSON.stringify(e,null,2))}catch(e){throw new Error(`Failed to save Claude Code settings: ${e}`)}}getMCPConfig(){try{if(e(this.mcpConfigPath)){const e=n(this.mcpConfigPath,"utf-8");return JSON.parse(e)}}catch(e){c.warn("Failed to read Claude Code MCP config:",e),c.logError("ClaudeCodeManager.getMCPConfig",e)}return{}}saveMCPConfig(e){try{this.ensureConfigDir(this.mcpConfigPath),this.atomicWrite(this.mcpConfigPath,JSON.stringify(e,null,2))}catch(e){throw new Error(`Failed to save Claude Code MCP config: ${e}`)}}loadPrivateConfig(e,t,n,o){this.ensureOnboardingCompleted(),f();const s=u(),i=s.env||{},{env:r,...a}=s,l=this.getSettings(),{ANTHROPIC_API_KEY:c,...m}=l.env||{},O={...m},C={},A=e=>{const n=(t[e]||"").trim();return n&&n!==g[e]?n:null},T=A("haiku"),v=A("sonnet"),P=A("opus"),D=new Set([T&&"haiku",v&&"sonnet",P&&"opus"].filter(Boolean)),L=resolveTierFallbacks(n).filter(e=>!D.has(e.tier));for(const e of L)C[e.envVar]=e.to;const y=new Set;T&&(C.ANTHROPIC_DEFAULT_HAIKU_MODEL=T),v&&(C.ANTHROPIC_DEFAULT_SONNET_MODEL=v),P&&(C.ANTHROPIC_DEFAULT_OPUS_MODEL=P,C.ANTHROPIC_MODEL=P),T&&y.add("ANTHROPIC_DEFAULT_HAIKU_MODEL"),v&&y.add("ANTHROPIC_DEFAULT_SONNET_MODEL"),P&&["ANTHROPIC_DEFAULT_OPUS_MODEL","ANTHROPIC_MODEL"].forEach(e=>y.add(e));const I=M(t);this.lastKept=[];for(const e of MODEL_ENV_VARS){const t=O[e],n=C[e];if(void 0!==n)h(S(e),t,n,{explicit:y.has(e),legacy:I})?O[e]=n:this.lastKept.push(`${e}=${t}`);else{if(void 0===t)continue;_(S(e),t,I)?(delete O[e],p(S(e))):this.lastKept.push(`${e}=${t}`)}}this.lastTierFallbacks=L.filter(e=>O[e.envVar]===e.to);const R={...l};for(const[e,t]of Object.entries(a))"model"!==e&&(e in R||(R[e]=t));R.showThinkingSummaries=!0;const U={...R,env:{...O,...Object.fromEntries(Object.entries(i).filter(([e])=>!(e in O))),ANTHROPIC_AUTH_TOKEN:e,ANTHROPIC_BASE_URL:d.getBaseUrl()}};E(d.getBaseUrl());const H=P??("string"==typeof a.model?a.model:void 0),F=l.model;void 0===H?void 0!==F&&_(N,F,I)&&(delete U.model,p(N)):h(N,F,H,{explicit:Boolean(P),legacy:I})?U.model=H:this.lastKept.push(`model=${String(F)}`),this.saveSettings(applyGatewayModels(U,o))}ensureOnboardingCompleted(){try{const e=this.getMCPConfig();e.hasCompletedOnboarding||this.saveMCPConfig({...e,hasCompletedOnboarding:!0})}catch(e){c.warn("Failed to ensure onboarding completed:",e),c.logError("ClaudeCodeManager.ensureOnboardingCompleted",e)}}unloadPrivateConfig(){const e=this.getSettings(),t=M(d.getModelsForTool("claude-code")),n=void 0!==e.model&&_(N,e.model,t);if(p(N),!e.env){if(n){const{model:t,...n}=e;this.saveSettings(n)}return}const{ANTHROPIC_AUTH_TOKEN:o,ANTHROPIC_BASE_URL:s,API_TIMEOUT_MS:i,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:r,CLAUDE_CODE_AUTO_MODE_SERVER:a,...l}=e.env,c={...l};for(const e of MODEL_ENV_VARS)e in c&&_(S(e),c[e],t)&&delete c[e],p(S(e));const g={...applyGatewayModels({...e,env:c},[])};g.env=g.env||{},n&&delete g.model,g.env&&0===Object.keys(g.env).length&&delete g.env,this.saveSettings(g)}clearPinnedModels(e){const t=this.getSettings(),n=t.env,o=n?.ANTHROPIC_AUTH_TOKEN?Object.fromEntries(resolveTierFallbacks(e).map(e=>[e.envVar,e.to])):{},s=[];for(const e of MODEL_ENV_VARS)n&&e in n&&n[e]!==o[e]&&s.push(e);void 0!==t.model&&s.push("model");const i=Object.entries(o).some(([e,t])=>n?.[e]!==t);if(0===s.length&&!i)return[];const r={...t};if(delete r.model,n){const e={...n};for(const t of MODEL_ENV_VARS)delete e[t];Object.assign(e,o),0===Object.keys(e).length?delete r.env:r.env=e}this.saveSettings(r),p(N);for(const e of MODEL_ENV_VARS)o[e]?h(S(e),o[e],o[e]):p(S(e));return s}getLastKept(){return[...this.lastKept]}getLastTierFallbacks(){return[...this.lastTierFallbacks]}detectCurrentConfig(){try{const e=this.getSettings();return e.env&&e.env.ANTHROPIC_AUTH_TOKEN?{apiKey:e.env.ANTHROPIC_AUTH_TOKEN}:{apiKey:null}}catch{return{apiKey:null}}}getExperimentalFeature(e){const t=this.getSettings();return t.env?.[e]}setExperimentalFeature(e,t){const n=this.getSettings(),o=n.env||{},s={...n,env:{...o,[e]:t}};this.saveSettings(s)}removeExperimentalFeature(e){const t=this.getSettings();if(!t.env)return;const{[e]:n,...o}=t.env,s={...t,env:o};this.saveSettings(s)}}export const claudeCodeManager=ClaudeCodeManager.getInstance();
|
package/dist/lib/command.d.ts
CHANGED
|
@@ -6,11 +6,6 @@ export declare class Command {
|
|
|
6
6
|
/** Set by `preflightArgs`: this run configures tools (see CONFIGURING_COMMANDS). */
|
|
7
7
|
private configuring;
|
|
8
8
|
constructor();
|
|
9
|
-
/**
|
|
10
|
-
* 配置类命令(CONFIGURING_COMMANDS 与向导)启动时检测 shell 环境里泄漏的
|
|
11
|
-
* ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY 并清除,否则它们会覆盖 vibecli 写进各工具配置文件的凭据。
|
|
12
|
-
*/
|
|
13
|
-
private cleanupLeakedEnvVars;
|
|
14
9
|
/**
|
|
15
10
|
* Reads the global flags before commander runs. A help request (`-h` / `--help`
|
|
16
11
|
* anywhere, or `vibecli help ...`) only prints help: it neither applies `--base`
|
package/dist/lib/command.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{i18n as
|
|
1
|
+
import{i18n as r,detectSystemLang as n}from"./i18n.js";import{configManager as o}from"./config.js";import{runModelRefresh as t}from"./model-refresh.js";import{refreshClientConfig as e}from"./client-config-sync.js";import{normalizeBaseUrl as i}from"./gateway-addresses.js";import s from"chalk";import{setRuntimeOptions as m,detectColorPreferenceFromEnv as a,isDryRun as c}from"./runtime-options.js";import{runStartupUpdate as d}from"./auto-update.js";import{buildProgram as u}from"./program.js";import{firstCommandWord as l,isHelpRequest as p,renderHelpTopic as g}from"./cli-help.js";import{reportCliError as f,usageError as h,CliError as y}from"./errors.js";import{CLI_NAME as _}from"./brand.js";import{dryRunUnsupportedCommand as w}from"./dry-run.js";import{flushTelemetry as j,installClientHeaders as k,setTelemetryCommand as v,track as C}from"./telemetry.js";export{buildProgram}from"./program.js";const x=new Set(["setup","init","auth","enter","config"]);export class Command{program;configuring=!1;constructor(){const t=o.isLangChosen()?o.getLang():n();r.loadFromConfig(t),this.program=u()}preflightArgs(r){const n=n=>r.includes(n),t=!n("--no-color")&&a();if(m({nonInteractive:n("--non-interactive"),json:n("--json"),yes:n("--yes")||n("-y"),dryRun:n("--dry-run"),color:t}),t||(s.level=0),this.configuring=!1,p(r))return;let e;for(let n=0;n<r.length;n+=1){const o=r[n];"--base"===o&&r[n+1]?(e=r[n+1],n+=1):o.startsWith("--base=")&&(e=o.slice(7))}const c=l(r),d=void 0===c&&!["--version","-v"].some(n);this.configuring=d||void 0!==c&&x.has(c),e&&o.setBaseUrl(i(e)??e,this.configuring&&!n("--dry-run"))}async execute(r){const n=Date.now();let o=!1;try{if(this.preflightArgs(r),k(),p(r)||["--version","-v"].some(n=>r.includes(n))||(v(l(r)??"wizard"),C("command","start",{detail:{args:r.filter(r=>r.startsWith("-")&&!/key/i.test(r))}}),o=!0),"help"===r[0]&&r[1]){const n=g(r[1]);if(n)return void process.stdout.write(n)}if(this.rejectUnknownCommand(r),!p(r)){if(c()&&this.rejectUnsupportedDryRun(r),(await d(r)).handled)return;this.configuring&&await e({timeoutMs:5e3}),await t(r)}if(await this.program.parseAsync(r,{from:"user"}),o){const r=Boolean(process.exitCode)&&0!==process.exitCode;C("command",r?"fail":"ok",{duration_ms:Date.now()-n,...r?{detail:{exit_code:process.exitCode}}:{}})}}catch(t){if("commander.helpDisplayed"===t?.code||"commander.version"===t?.code)return;const e=this.normalizeCommanderError(t,r),i=f(e);if(o){const r=e instanceof y?`${e.type}${e.subtype?"/"+e.subtype:""}`:void 0;C("command",130===i?"cancel":"fail",{duration_ms:Date.now()-n,error:e,detail:{exit_code:i,...r?{type:r}:{}}})}}finally{o&&await j()}}rejectUnsupportedDryRun(n){const o=w(n);if(null!==o)throw h(r.t("runtime.dry_run_unsupported",{command:o,cli:_}),r.t("runtime.dry_run_unsupported_hint",{cli:_}),"dry_run_unsupported")}rejectUnknownCommand(n){const o=l(n);if(!o||"help"===o)return;if(this.program.commands.some(r=>r.name()===o))return;const t=this.suggestCommand(o),e=t?r.t("cli.unknown_hint_suggestion",{command:t}):r.t("cli.unknown_hint");throw h(r.t("cli.error_unknown",{command:o}),e,"unknown_command")}suggestCommand(r){const n=(r,n)=>{const o=Array.from({length:n.length+1},(r,n)=>n);for(let t=1;t<=r.length;t+=1){let e=o[0];o[0]=t;for(let i=1;i<=n.length;i+=1){const s=o[i];o[i]=Math.min(o[i]+1,o[i-1]+1,e+(r[t-1]===n[i-1]?0:1)),e=s}}return o[n.length]};return this.program.commands.map(r=>r.name()).map(o=>({name:o,score:n(r,o)})).filter(({score:n})=>n<=Math.max(2,Math.floor(r.length/2))).sort((r,n)=>r.score-n.score)[0]?.name}normalizeCommanderError(n,o){if(n instanceof y)return n;const t=n?.code,e=n instanceof Error?n.message:String(n);if("commander.unknownOption"===t||"commander.missingArgument"===t||"commander.missingMandatoryOption"===t||"commander.invalidArgument"===t){const n=l(o),i=/'([^']+)'/.exec(e)?.[1]??"",s="commander.unknownOption"===t?"cli.err_unknown_option":"commander.missingArgument"===t?"cli.err_missing_argument":"commander.missingMandatoryOption"===t?"cli.err_missing_option":null,m=s&&i?r.t(s,{name:i}):e.replace(/^error:\s*/,"");return h(m,n?r.t("cli.unknown_option_hint",{command:n}):r.t("cli.unknown_option_hint_root"),t)}return n}getProgram(){return this.program}}
|
package/dist/lib/config.d.ts
CHANGED
package/dist/lib/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as e,mkdirSync as t,readFileSync as i,writeFileSync as o,renameSync as s,unlinkSync as r,readdirSync as n,rmdirSync as a,statSync as l,chmodSync as c}from"fs";import{join as f,dirname as g}from"path";import{homedir as h}from"os";import{createHash as p}from"crypto";import*as m from"js-yaml";import{logger as u}from"../utils/logger.js";import{getDefaultModels as d,isRetiredDefaultModel as y}from"./model-registry.js";import{readLegacySecretsFile as _}from"./secret-store.js";import{brand as k,brandEnv as b}from"./brand.js";export const DEFAULT_BASE_URL=k().gatewayAddresses[0].url;export const DEFAULT_MODELS=d();export const DEFAULT_PROFILE_NAME="default";const v="plaintext";export class ConfigManager{static instance;configDir;configPath;config;runtimeBaseUrl;constructor(){this.configDir=this.resolveConfigDir(),this.migrateLegacyConfigDir(),this.configPath=f(this.configDir,"config.yaml"),this.config=this.loadConfig()}migrateLegacyConfigDir(){try{const t=f(g(this.configDir),".chelper");if(t===this.configDir||!e(t))return;if(["config.yaml","secrets.enc"].some(t=>e(f(this.configDir,t))))return;if(e(this.configDir)){for(const i of n(t)){const o=f(this.configDir,i);e(o)||s(f(t,i),o)}0===n(t).length&&a(t)}else s(t,this.configDir);u.record(`Migrated config dir: ${t} -> ${this.configDir}`)}catch(e){u.warn("Failed to migrate legacy .chelper config dir:",e)}}resolveConfigDir(){const e=b("CONFIG_DIR");if(e&&e.trim())return e.trim();const t=b("TEST_HOME");return t&&t.trim()?f(t.trim(),k().configDirName):f(h(),k().configDirName)}createDefaultConfig(){return this.syncLegacyFields({lang:"en_US",active_profile:"default",profiles:{[DEFAULT_PROFILE_NAME]:{}},presets:{}})}normalizeModels(e){const t=t=>{const i=e?.[t]?.trim();return i&&!y(t,i)?i:DEFAULT_MODELS[t]},i={haiku:t("haiku"),sonnet:t("sonnet"),opus:t("opus"),reasoning:t("reasoning")};if(e&&"object"==typeof e)for(const[t,o]of Object.entries(e))t in i||"string"==typeof o&&o.trim()&&(i[t]=o.trim());return i}normalizeToolModels(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||"string"!=typeof e||!t||"object"!=typeof t)return[];const i={};for(const[e,o]of Object.entries(t))"string"==typeof o&&o.trim()&&(i[e]=o.trim());return Object.keys(i).length>0?[[e,i]]:[]});return Object.fromEntries(t)}normalizeToolSettings(e){if(!e||"object"!=typeof e||Array.isArray(e))return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||!t||"object"!=typeof t||Array.isArray(t))return[];const i=Object.fromEntries(Object.entries(t).filter(([,e])=>"string"==typeof e||"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e)));return Object.keys(i).length>0?[[e,i]]:[]});return Object.fromEntries(t)}normalizeStringRecord(e){return!e||"object"!=typeof e||Array.isArray(e)?{}:Object.fromEntries(Object.entries(e).filter(e=>"string"==typeof e[1]&&Boolean(e[1].trim())).map(([e,t])=>[e,t.trim()]))}normalizeProfile(e){if(!e)return{};const t=this.normalizeToolModels(e.tool_models),i=this.normalizeToolSettings(e.tool_settings),o=this.normalizeStringRecord(e.tool_api_keys);return{..."string"==typeof e.api_key&&e.api_key?{api_key:e.api_key}:{},...Object.keys(o).length>0?{tool_api_keys:o}:{},..."string"==typeof e.base_url&&e.base_url?{base_url:e.base_url}:{},...e.models?{models:this.normalizeModels(e.models)}:{},...Object.keys(t).length>0?{tool_models:t}:{},...Object.keys(i).length>0?{tool_settings:i}:{}}}normalizeProfiles(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>e&&"string"==typeof e&&t&&"object"==typeof t?[[e,this.normalizeProfile(t)]]:[]);return Object.fromEntries(t)}normalizePresets(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||"string"!=typeof e||!t||"object"!=typeof t)return[];const i=t;return i.models?[[e,{models:this.normalizeModels(i.models),updated_at:i.updated_at||new Date(0).toISOString()}]]:[]});return Object.fromEntries(t)}getResolvedActiveProfile(e){const t=e.active_profile||"default";return e.profiles?.[t]||{}}syncLegacyFields(e){const t=this.normalizeProfiles(e.profiles),i=e.active_profile?.trim()||Object.keys(t)[0]||"default",o=t[i]?t:{...t,[i]:{}},s=this.getResolvedActiveProfile({...e,profiles:o,active_profile:i});return{...e,lang:e.lang||"en_US",active_profile:i,profiles:o,presets:this.normalizePresets(e.presets),api_key:s.api_key,base_url:s.base_url,models:s.models?this.normalizeModels(s.models):DEFAULT_MODELS}}sanitizeBackupLabel(e){return(e||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)}getBackupDir(){return f(this.configDir,"backups")}getLegacySecretsPath(){return f(this.configDir,"secrets.enc")}getSecretsBackupPath(e){return e.replace(/\.yaml$/,".secrets.enc")}static getInstance(){return ConfigManager.instance||(ConfigManager.instance=new ConfigManager),ConfigManager.instance}ensureConfigDir(){e(this.configDir)||t(this.configDir,{recursive:!0})}loadConfig(){try{if(e(this.configPath)){const t=i(this.configPath,"utf-8"),o=m.load(t);let s=this.migrateOldConfig(o||{lang:"en_US"}),n=!1;const a=this.getLegacySecretsPath();if(e(a)&&o?.credential_store!==v)try{s=this.mergeLegacySecrets(s,_(a)),n=!0}catch(e){u.warn("Legacy secrets.enc is unreadable on this host; it was kept for recovery and will not block new credentials."),u.logError("ConfigManager.migrateLegacySecrets",e)}if((this.needsMigration(o)||n)&&(this.backupConfig(),this.atomicWrite(this.configPath,m.dump(this.prepareForPersistence(s))),n))try{r(a)}catch(e){u.logError("ConfigManager.removeLegacySecrets",e)}try{c(this.configPath,384)}catch{}return s}const t=this.getLegacySecretsPath();if(e(t))try{const e=this.mergeLegacySecrets(this.createDefaultConfig(),_(t));this.ensureConfigDir(),this.atomicWrite(this.configPath,m.dump(this.prepareForPersistence(e)));try{r(t)}catch(e){u.logError("ConfigManager.removeLegacySecrets",e)}return e}catch(e){u.warn("Legacy secrets.enc is unreadable on this host; it was kept for recovery and will not block new credentials."),u.logError("ConfigManager.migrateLegacySecretsWithoutConfig",e)}}catch(e){u.warn("Failed to load config, using defaults:",e),u.logError("ConfigManager.loadConfig",e)}return this.createDefaultConfig()}needsMigration(e){return!(!e||"object"!=typeof e)&&("plan"in e||!("profiles"in e)||!("active_profile"in e)||!("presets"in e))}migrateOldConfig(e){if(!e||"object"!=typeof e)return this.createDefaultConfig();const t=e,{plan:i,...o}=t,s=this.normalizeProfiles(o.profiles),r=this.normalizeProfile({api_key:"string"==typeof o.api_key?o.api_key:void 0,base_url:"string"==typeof o.base_url?o.base_url:void 0,models:o.models}),n=Object.keys(s).length>0?s:{[DEFAULT_PROFILE_NAME]:r},a="string"==typeof o.active_profile&&o.active_profile.trim()?o.active_profile.trim():Object.keys(n)[0]||"default";return this.syncLegacyFields({lang:"string"==typeof o.lang&&o.lang?o.lang:"en_US",...!0===o.lang_chosen?{lang_chosen:!0}:{},...o.credential_store===v?{credential_store:v}:{},active_profile:a,profiles:n,presets:this.normalizePresets(o.presets)})}atomicWrite(t,i){const n=`${t}.tmp`;try{o(n,i,{encoding:"utf-8",mode:384}),s(n,t);try{c(t,384)}catch{}}catch(t){if(e(n))try{r(n)}catch{}throw t}}backupConfig(){if(!e(this.configPath))return;const s=this.getBackupDir();e(s)||t(s,{recursive:!0});const r=(new Date).toISOString().replace(/[:.]/g,"-"),n=f(s,`config-${r}.yaml`);try{const e=i(this.configPath,"utf-8");o(n,e,{encoding:"utf-8",mode:384}),this.rotateBackups(s,10,30)}catch(e){u.logError("ConfigManager.backupConfig",e)}}rotateBackups(t,i,o){try{const s=Date.now()-24*o*60*60*1e3,a=n(t).filter(e=>e.startsWith("config-")&&e.endsWith(".yaml")).map(e=>({name:e,path:f(t,e),mtime:l(f(t,e)).mtime})).sort((e,t)=>t.mtime.getTime()-e.mtime.getTime()),c=new Set;a.slice(i).forEach(e=>c.add(e.path)),a.forEach(e=>{e.mtime.getTime()<s&&c.add(e.path)});for(const t of c){let i=!1;try{r(t),i=!0}catch(e){u.logError("ConfigManager.rotateBackups",e)}if(i){const i=this.getSecretsBackupPath(t);if(e(i))try{r(i)}catch(e){u.logError("ConfigManager.rotateBackups.secrets",e)}}}}catch(e){u.logError("ConfigManager.rotateBackups",e)}}saveConfig(e){try{this.ensureConfigDir(),this.backupConfig();const t=this.syncLegacyFields(this.migrateOldConfig(e||this.config)),i=this.prepareForPersistence(t),o=m.dump(i);this.atomicWrite(this.configPath,o),this.config=t}catch(e){throw u.error("Failed to save config:",e),u.logError("ConfigManager.saveConfig",e),e}}prepareForPersistence(e){const t={...e,credential_store:v};return delete t.api_key,delete t.base_url,delete t.models,t}mergeLegacySecrets(e,t){const i={...e.profiles||{}},o=new Set([...Object.keys(t.api_keys||{}),...Object.keys(t.tool_api_keys||{})]);for(const e of o){const o=i[e]||{},s=o.api_key||t.api_keys?.[e],r={...t.tool_api_keys?.[e]||{},...o.tool_api_keys||{}};i[e]={...o,...s?{api_key:s}:{},...Object.keys(r).length>0?{tool_api_keys:r}:{}}}return this.syncLegacyFields({...e,profiles:i})}getConfig(){return this.syncLegacyFields({...this.config})}updateConfig(e){this.saveConfig({...this.config,...e})}isFirstRun(){return!e(this.configPath)}getLang(){return this.config.lang||"en_US"}isLangChosen(){return!0===this.config.lang_chosen}setLang(e){this.updateConfig({lang:e,lang_chosen:!0})}getActiveProfileName(){return this.config.active_profile||"default"}listProfiles(){return Object.keys(this.config.profiles||{}).sort()}getProfile(e=this.getActiveProfileName()){const t=this.config.profiles?.[e]||{};return{...t.api_key?{api_key:t.api_key}:{},...t.tool_api_keys&&Object.keys(t.tool_api_keys).length>0?{tool_api_keys:this.normalizeStringRecord(t.tool_api_keys)}:{},...t.base_url?{base_url:t.base_url}:{},models:t.models?this.normalizeModels(t.models):DEFAULT_MODELS,...t.tool_models&&Object.keys(t.tool_models).length>0?{tool_models:this.normalizeToolModels(t.tool_models)}:{},...t.tool_settings&&Object.keys(t.tool_settings).length>0?{tool_settings:this.normalizeToolSettings(t.tool_settings)}:{}}}createProfile(e,t=this.getActiveProfileName()){const i=this.getProfile(t),o={...this.config.profiles||{},[e]:{...i.api_key?{api_key:i.api_key}:{},...i.base_url?{base_url:i.base_url}:{},models:this.normalizeModels(i.models)}};this.saveConfig({...this.config,profiles:o})}setActiveProfile(e){if(!this.config.profiles?.[e])throw new Error(`Profile not found: ${e}`);this.saveConfig({...this.config,active_profile:e})}updateProfile(e,t){const i=this.config.profiles?.[e]||{},o=t.models?this.normalizeModels({...i.models,...t.models}):i.models?this.normalizeModels(i.models):void 0,s={...i,...void 0!==t.api_key?{api_key:t.api_key}:{},...void 0!==t.base_url?{base_url:t.base_url}:{},...o?{models:o}:{}},r={...this.config.profiles||{},[e]:s};this.saveConfig({...this.config,profiles:r})}deleteProfile(e){const t={...this.config.profiles||{}};if(!t[e])return;const i=Object.keys(t).filter(t=>t!==e);if(0===i.length)throw new Error("At least one profile must remain");delete t[e];const o=this.getActiveProfileName()===e?i[0]:this.getActiveProfileName();this.saveConfig({...this.config,profiles:t,active_profile:o})}getApiKey(){return this.config.profiles?.[this.getActiveProfileName()]?.api_key}setApiKey(e){this.updateProfile(this.getActiveProfileName(),{api_key:e})}revokeApiKey(){const e=this.config.profiles?.[this.getActiveProfileName()]||{},{api_key:t,tool_api_keys:i,...o}=e,s={...this.config.profiles||{},[this.getActiveProfileName()]:o};this.saveConfig({...this.config,profiles:s})}getApiKeyFingerprint(){const e=this.getApiKey();return e?p("sha256").update(e).digest("hex").slice(0,12):""}getApiKeyForTool(e){const t=this.config.profiles?.[this.getActiveProfileName()];return t?.tool_api_keys?.[e]||t?.api_key||void 0}setToolApiKey(e,t){const i=this.getActiveProfileName(),o=this.config.profiles?.[i]||{},s={...this.config.profiles||{},[i]:{...o,tool_api_keys:{...o.tool_api_keys||{},[e]:t}}};this.saveConfig({...this.config,profiles:s})}clearToolApiKey(e){const t=this.getActiveProfileName(),i=this.config.profiles?.[t]||{},{[e]:o,...s}=i.tool_api_keys||{},r={...i};Object.keys(s).length>0?r.tool_api_keys=s:delete r.tool_api_keys,this.saveConfig({...this.config,profiles:{...this.config.profiles||{},[t]:r}})}listToolApiKeys(){return Object.keys(this.config.profiles?.[this.getActiveProfileName()]?.tool_api_keys||{}).sort()}getToolApiKeyFingerprint(e){const t=this.config.profiles?.[this.getActiveProfileName()]?.tool_api_keys?.[e];return t?p("sha256").update(t).digest("hex").slice(0,12):""}getModels(){const e=this.config.profiles?.[this.getActiveProfileName()]?.models;return e?this.normalizeModels(e):DEFAULT_MODELS}setModels(e){this.updateProfile(this.getActiveProfileName(),{models:this.normalizeModels(e)})}updateModels(e){this.setModels(this.normalizeModels({...this.getModels(),...e}))}getModelsForTool(e){const t=this.getModels(),i=this.config.profiles?.[this.getActiveProfileName()]?.tool_models?.[e];return i?this.normalizeModels({...t,...i}):t}setToolModel(e,t){const i=this.getActiveProfileName(),o=this.config.profiles?.[i]||{},s={...o.tool_models||{}};t&&0!==Object.keys(t).length?s[e]=t:delete s[e];const r={...o};Object.keys(s).length>0?r.tool_models=s:delete r.tool_models;const n={...this.config.profiles||{},[i]:r};this.saveConfig({...this.config,profiles:n})}getToolSettings(e){const t=this.config.profiles?.[this.getActiveProfileName()]?.tool_settings?.[e];return t?{...t}:{}}getToolSetting(e,t){return this.config.profiles?.[this.getActiveProfileName()]?.tool_settings?.[e]?.[t]}setToolSetting(e,t,i){if(this.getToolSetting(e,t)===i)return;const o=this.getActiveProfileName(),s=this.config.profiles?.[o]||{},r={...s.tool_settings||{}};r[e]={...r[e]||{},[t]:i};const n={...this.config.profiles||{},[o]:{...s,tool_settings:r}};this.saveConfig({...this.config,profiles:n})}getCustomPresets(){return{...this.config.presets||{}}}saveCustomPreset(e,t){const i={...this.config.presets||{},[e]:{models:this.normalizeModels(t),updated_at:(new Date).toISOString()}};this.saveConfig({...this.config,presets:i})}deleteCustomPreset(e){const t={...this.config.presets||{}};t[e]&&(delete t[e],this.saveConfig({...this.config,presets:t}))}listBackups(){const t=this.getBackupDir();return e(t)?n(t).filter(e=>e.startsWith("config-")&&e.endsWith(".yaml")).map(e=>{const i=f(t,e);return{name:e,path:i,mtimeMs:l(i).mtimeMs}}).sort((e,t)=>t.mtimeMs-e.mtimeMs):[]}createBackup(s){this.ensureConfigDir();const r=this.getBackupDir();e(r)||t(r,{recursive:!0});const n=(new Date).toISOString().replace(/[:.]/g,"-"),a=this.sanitizeBackupLabel(s),l=f(r,a?`config-${n}-${a}.yaml`:`config-${n}.yaml`),c=e(this.configPath)?i(this.configPath,"utf-8"):m.dump(this.prepareForPersistence(this.getConfig()));return o(l,c,{encoding:"utf-8",mode:384}),this.rotateBackups(r,10,30),l}restoreBackup(t){if(!t||"."===t||".."===t||/[/\\]|\.\./.test(t))throw new Error(`Invalid backup name: ${t}`);const o=this.getBackupDir(),s=f(o,t);if(!e(s))throw new Error(`Backup not found: ${t}`);const r=i(s,"utf-8"),n=m.load(r),a=this.getSecretsBackupPath(s);let l=this.migrateOldConfig(n||this.createDefaultConfig());if(n?.credential_store!==v&&e(a))try{l=this.mergeLegacySecrets(l,_(a))}catch(e){throw new Error(`Legacy backup credentials could not be decrypted: ${a}`,{cause:e})}this.saveConfig(l)}getConfigDir(){return this.configDir}getBaseUrl(){return this.runtimeBaseUrl||this.config.profiles?.[this.getActiveProfileName()]?.base_url||DEFAULT_BASE_URL}hasRuntimeBaseUrl(){return Boolean(this.runtimeBaseUrl)}setBaseUrl(e,t=!1){this.runtimeBaseUrl=e,t&&this.updateProfile(this.getActiveProfileName(),{base_url:e})}resetBaseUrl(){this.runtimeBaseUrl=void 0;const e=this.config.profiles?.[this.getActiveProfileName()]||{},{base_url:t,...i}=e,o={...this.config.profiles||{},[this.getActiveProfileName()]:i};this.saveConfig({...this.config,profiles:o})}}export const configManager=ConfigManager.getInstance();
|
|
1
|
+
import{existsSync as e,mkdirSync as t,readFileSync as i,writeFileSync as o,renameSync as s,unlinkSync as r,readdirSync as n,rmdirSync as a,statSync as c,chmodSync as l}from"fs";import{join as f,dirname as g}from"path";import{homedir as h}from"os";import{createHash as p}from"crypto";import*as m from"js-yaml";import{logger as u}from"../utils/logger.js";import{getDefaultModels as d,isRetiredDefaultModel as y}from"./model-registry.js";import{readLegacySecretsFile as _}from"./secret-store.js";import{brand as k,brandEnv as b}from"./brand.js";export const DEFAULT_BASE_URL=k().gatewayAddresses[0].url;export const DEFAULT_MODELS=d();export const DEFAULT_PROFILE_NAME="default";const v="plaintext";export class ConfigManager{static instance;configDir;configPath;config;runtimeBaseUrl;constructor(){this.configDir=this.resolveConfigDir(),this.migrateLegacyConfigDir(),this.configPath=f(this.configDir,"config.yaml"),this.config=this.loadConfig()}migrateLegacyConfigDir(){try{const t=f(g(this.configDir),".chelper");if(t===this.configDir||!e(t))return;if(["config.yaml","secrets.enc"].some(t=>e(f(this.configDir,t))))return;if(e(this.configDir)){for(const i of n(t)){const o=f(this.configDir,i);e(o)||s(f(t,i),o)}0===n(t).length&&a(t)}else s(t,this.configDir);u.record(`Migrated config dir: ${t} -> ${this.configDir}`)}catch(e){u.warn("Failed to migrate legacy .chelper config dir:",e)}}resolveConfigDir(){const e=b("CONFIG_DIR");if(e&&e.trim())return e.trim();const t=b("TEST_HOME");return t&&t.trim()?f(t.trim(),k().configDirName):f(h(),k().configDirName)}createDefaultConfig(){return this.syncLegacyFields({lang:"en_US",active_profile:"default",profiles:{[DEFAULT_PROFILE_NAME]:{}},presets:{}})}normalizeModels(e){const t=t=>{const i=e?.[t]?.trim();return i&&!y(t,i)?i:DEFAULT_MODELS[t]},i={haiku:t("haiku"),sonnet:t("sonnet"),opus:t("opus"),reasoning:t("reasoning")};if(e&&"object"==typeof e)for(const[t,o]of Object.entries(e))t in i||"string"==typeof o&&o.trim()&&(i[t]=o.trim());return i}normalizeToolModels(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||"string"!=typeof e||!t||"object"!=typeof t)return[];const i={};for(const[e,o]of Object.entries(t))"string"==typeof o&&o.trim()&&(i[e]=o.trim());return Object.keys(i).length>0?[[e,i]]:[]});return Object.fromEntries(t)}normalizeToolSettings(e){if(!e||"object"!=typeof e||Array.isArray(e))return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||!t||"object"!=typeof t||Array.isArray(t))return[];const i=Object.fromEntries(Object.entries(t).filter(([,e])=>"string"==typeof e||"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e)));return Object.keys(i).length>0?[[e,i]]:[]});return Object.fromEntries(t)}normalizeStringRecord(e){return!e||"object"!=typeof e||Array.isArray(e)?{}:Object.fromEntries(Object.entries(e).filter(e=>"string"==typeof e[1]&&Boolean(e[1].trim())).map(([e,t])=>[e,t.trim()]))}normalizeProfile(e){if(!e)return{};const t=this.normalizeToolModels(e.tool_models),i=this.normalizeToolSettings(e.tool_settings),o=this.normalizeStringRecord(e.tool_api_keys);return{..."string"==typeof e.api_key&&e.api_key?{api_key:e.api_key}:{},...Object.keys(o).length>0?{tool_api_keys:o}:{},..."string"==typeof e.base_url&&e.base_url?{base_url:e.base_url}:{},...e.models?{models:this.normalizeModels(e.models)}:{},...Object.keys(t).length>0?{tool_models:t}:{},...Object.keys(i).length>0?{tool_settings:i}:{}}}normalizeProfiles(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>e&&"string"==typeof e&&t&&"object"==typeof t?[[e,this.normalizeProfile(t)]]:[]);return Object.fromEntries(t)}normalizePresets(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||"string"!=typeof e||!t||"object"!=typeof t)return[];const i=t;return i.models?[[e,{models:this.normalizeModels(i.models),updated_at:i.updated_at||new Date(0).toISOString()}]]:[]});return Object.fromEntries(t)}getResolvedActiveProfile(e){const t=e.active_profile||"default";return e.profiles?.[t]||{}}syncLegacyFields(e){const t=this.normalizeProfiles(e.profiles),i=e.active_profile?.trim()||Object.keys(t)[0]||"default",o=t[i]?t:{...t,[i]:{}},s=this.getResolvedActiveProfile({...e,profiles:o,active_profile:i});return{...e,lang:e.lang||"en_US",active_profile:i,profiles:o,presets:this.normalizePresets(e.presets),api_key:s.api_key,base_url:s.base_url,models:s.models?this.normalizeModels(s.models):DEFAULT_MODELS}}sanitizeBackupLabel(e){return(e||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)}getBackupDir(){return f(this.configDir,"backups")}getLegacySecretsPath(){return f(this.configDir,"secrets.enc")}getSecretsBackupPath(e){return e.replace(/\.yaml$/,".secrets.enc")}static getInstance(){return ConfigManager.instance||(ConfigManager.instance=new ConfigManager),ConfigManager.instance}ensureConfigDir(){e(this.configDir)||t(this.configDir,{recursive:!0})}loadConfig(){try{if(e(this.configPath)){const t=i(this.configPath,"utf-8"),o=m.load(t);let s=this.migrateOldConfig(o||{lang:"en_US"}),n=!1;const a=this.getLegacySecretsPath();if(e(a)&&o?.credential_store!==v)try{s=this.mergeLegacySecrets(s,_(a)),n=!0}catch(e){u.warn("Legacy secrets.enc is unreadable on this host; it was kept for recovery and will not block new credentials."),u.logError("ConfigManager.migrateLegacySecrets",e)}if((this.needsMigration(o)||n)&&(this.backupConfig(),this.atomicWrite(this.configPath,m.dump(this.prepareForPersistence(s))),n))try{r(a)}catch(e){u.logError("ConfigManager.removeLegacySecrets",e)}try{l(this.configPath,384)}catch{}return s}const t=this.getLegacySecretsPath();if(e(t))try{const e=this.mergeLegacySecrets(this.createDefaultConfig(),_(t));this.ensureConfigDir(),this.atomicWrite(this.configPath,m.dump(this.prepareForPersistence(e)));try{r(t)}catch(e){u.logError("ConfigManager.removeLegacySecrets",e)}return e}catch(e){u.warn("Legacy secrets.enc is unreadable on this host; it was kept for recovery and will not block new credentials."),u.logError("ConfigManager.migrateLegacySecretsWithoutConfig",e)}}catch(e){u.warn("Failed to load config, using defaults:",e),u.logError("ConfigManager.loadConfig",e)}return this.createDefaultConfig()}needsMigration(e){return!(!e||"object"!=typeof e)&&("plan"in e||!("profiles"in e)||!("active_profile"in e)||!("presets"in e))}migrateOldConfig(e){if(!e||"object"!=typeof e)return this.createDefaultConfig();const t=e,{plan:i,...o}=t,s=this.normalizeProfiles(o.profiles),r=this.normalizeProfile({api_key:"string"==typeof o.api_key?o.api_key:void 0,base_url:"string"==typeof o.base_url?o.base_url:void 0,models:o.models}),n=Object.keys(s).length>0?s:{[DEFAULT_PROFILE_NAME]:r},a="string"==typeof o.active_profile&&o.active_profile.trim()?o.active_profile.trim():Object.keys(n)[0]||"default";return this.syncLegacyFields({lang:"string"==typeof o.lang&&o.lang?o.lang:"en_US",...!0===o.lang_chosen?{lang_chosen:!0}:{},...o.credential_store===v?{credential_store:v}:{},active_profile:a,profiles:n,presets:this.normalizePresets(o.presets)})}atomicWrite(t,i){const n=`${t}.tmp`;try{o(n,i,{encoding:"utf-8",mode:384}),s(n,t);try{l(t,384)}catch{}}catch(t){if(e(n))try{r(n)}catch{}throw t}}backupConfig(){if(!e(this.configPath))return;const s=this.getBackupDir();e(s)||t(s,{recursive:!0});const r=(new Date).toISOString().replace(/[:.]/g,"-"),n=f(s,`config-${r}.yaml`);try{const e=i(this.configPath,"utf-8");o(n,e,{encoding:"utf-8",mode:384}),this.rotateBackups(s,10,30)}catch(e){u.logError("ConfigManager.backupConfig",e)}}rotateBackups(t,i,o){try{const s=Date.now()-24*o*60*60*1e3,a=n(t).filter(e=>e.startsWith("config-")&&e.endsWith(".yaml")).map(e=>({name:e,path:f(t,e),mtime:c(f(t,e)).mtime})).sort((e,t)=>t.mtime.getTime()-e.mtime.getTime()),l=new Set;a.slice(i).forEach(e=>l.add(e.path)),a.forEach(e=>{e.mtime.getTime()<s&&l.add(e.path)});for(const t of l){let i=!1;try{r(t),i=!0}catch(e){u.logError("ConfigManager.rotateBackups",e)}if(i){const i=this.getSecretsBackupPath(t);if(e(i))try{r(i)}catch(e){u.logError("ConfigManager.rotateBackups.secrets",e)}}}}catch(e){u.logError("ConfigManager.rotateBackups",e)}}saveConfig(e){try{this.ensureConfigDir(),this.backupConfig();const t=this.syncLegacyFields(this.migrateOldConfig(e||this.config)),i=this.prepareForPersistence(t),o=m.dump(i);this.atomicWrite(this.configPath,o),this.config=t}catch(e){throw u.error("Failed to save config:",e),u.logError("ConfigManager.saveConfig",e),e}}prepareForPersistence(e){const t={...e,credential_store:v};return delete t.api_key,delete t.base_url,delete t.models,t}mergeLegacySecrets(e,t){const i={...e.profiles||{}},o=new Set([...Object.keys(t.api_keys||{}),...Object.keys(t.tool_api_keys||{})]);for(const e of o){const o=i[e]||{},s=o.api_key||t.api_keys?.[e],r={...t.tool_api_keys?.[e]||{},...o.tool_api_keys||{}};i[e]={...o,...s?{api_key:s}:{},...Object.keys(r).length>0?{tool_api_keys:r}:{}}}return this.syncLegacyFields({...e,profiles:i})}getConfig(){return this.syncLegacyFields({...this.config})}updateConfig(e){this.saveConfig({...this.config,...e})}isFirstRun(){return!e(this.configPath)}getLang(){return this.config.lang||"en_US"}isLangChosen(){return!0===this.config.lang_chosen}setLang(e){this.updateConfig({lang:e,lang_chosen:!0})}getActiveProfileName(){return this.config.active_profile||"default"}listProfiles(){return Object.keys(this.config.profiles||{}).sort()}getProfile(e=this.getActiveProfileName()){const t=this.config.profiles?.[e]||{};return{...t.api_key?{api_key:t.api_key}:{},...t.tool_api_keys&&Object.keys(t.tool_api_keys).length>0?{tool_api_keys:this.normalizeStringRecord(t.tool_api_keys)}:{},...t.base_url?{base_url:t.base_url}:{},models:t.models?this.normalizeModels(t.models):DEFAULT_MODELS,...t.tool_models&&Object.keys(t.tool_models).length>0?{tool_models:this.normalizeToolModels(t.tool_models)}:{},...t.tool_settings&&Object.keys(t.tool_settings).length>0?{tool_settings:this.normalizeToolSettings(t.tool_settings)}:{}}}createProfile(e,t=this.getActiveProfileName()){const i=this.getProfile(t),o={...this.config.profiles||{},[e]:{...i.api_key?{api_key:i.api_key}:{},...i.base_url?{base_url:i.base_url}:{},models:this.normalizeModels(i.models)}};this.saveConfig({...this.config,profiles:o})}setActiveProfile(e){if(!this.config.profiles?.[e])throw new Error(`Profile not found: ${e}`);this.saveConfig({...this.config,active_profile:e})}updateProfile(e,t){const i=this.config.profiles?.[e]||{},o=t.models?this.normalizeModels({...i.models,...t.models}):i.models?this.normalizeModels(i.models):void 0,s={...i,...void 0!==t.api_key?{api_key:t.api_key}:{},...void 0!==t.base_url?{base_url:t.base_url}:{},...o?{models:o}:{}},r={...this.config.profiles||{},[e]:s};this.saveConfig({...this.config,profiles:r})}deleteProfile(e){const t={...this.config.profiles||{}};if(!t[e])return;const i=Object.keys(t).filter(t=>t!==e);if(0===i.length)throw new Error("At least one profile must remain");delete t[e];const o=this.getActiveProfileName()===e?i[0]:this.getActiveProfileName();this.saveConfig({...this.config,profiles:t,active_profile:o})}getApiKey(){return this.config.profiles?.[this.getActiveProfileName()]?.api_key}setApiKey(e){this.updateProfile(this.getActiveProfileName(),{api_key:e})}revokeApiKey(){const e=this.config.profiles?.[this.getActiveProfileName()]||{},{api_key:t,tool_api_keys:i,...o}=e,s={...this.config.profiles||{},[this.getActiveProfileName()]:o};this.saveConfig({...this.config,profiles:s})}getApiKeyFingerprint(){const e=this.getApiKey();return e?p("sha256").update(e).digest("hex").slice(0,12):""}getApiKeyForTool(e){const t=this.config.profiles?.[this.getActiveProfileName()];return t?.tool_api_keys?.[e]||t?.api_key||void 0}setToolApiKey(e,t){const i=this.getActiveProfileName(),o=this.config.profiles?.[i]||{},s={...this.config.profiles||{},[i]:{...o,tool_api_keys:{...o.tool_api_keys||{},[e]:t}}};this.saveConfig({...this.config,profiles:s})}clearToolApiKey(e){const t=this.getActiveProfileName(),i=this.config.profiles?.[t]||{},{[e]:o,...s}=i.tool_api_keys||{},r={...i};Object.keys(s).length>0?r.tool_api_keys=s:delete r.tool_api_keys,this.saveConfig({...this.config,profiles:{...this.config.profiles||{},[t]:r}})}listToolApiKeys(){return Object.keys(this.config.profiles?.[this.getActiveProfileName()]?.tool_api_keys||{}).sort()}getToolApiKeyFingerprint(e){const t=this.config.profiles?.[this.getActiveProfileName()]?.tool_api_keys?.[e];return t?p("sha256").update(t).digest("hex").slice(0,12):""}getModels(){const e=this.config.profiles?.[this.getActiveProfileName()]?.models;return e?this.normalizeModels(e):DEFAULT_MODELS}setModels(e){this.updateProfile(this.getActiveProfileName(),{models:this.normalizeModels(e)})}updateModels(e){this.setModels(this.normalizeModels({...this.getModels(),...e}))}getModelsForTool(e){const t=this.getModels(),i=this.config.profiles?.[this.getActiveProfileName()]?.tool_models?.[e];return i?this.normalizeModels({...t,...i}):t}setToolModel(e,t){const i=this.getActiveProfileName(),o=this.config.profiles?.[i]||{},s={...o.tool_models||{}};t&&0!==Object.keys(t).length?s[e]=t:delete s[e];const r={...o};Object.keys(s).length>0?r.tool_models=s:delete r.tool_models;const n={...this.config.profiles||{},[i]:r};this.saveConfig({...this.config,profiles:n})}getToolSettings(e){const t=this.config.profiles?.[this.getActiveProfileName()]?.tool_settings?.[e];return t?{...t}:{}}getToolSetting(e,t){return this.config.profiles?.[this.getActiveProfileName()]?.tool_settings?.[e]?.[t]}setToolSetting(e,t,i){if(this.getToolSetting(e,t)===i)return;const o=this.getActiveProfileName(),s=this.config.profiles?.[o]||{},r={...s.tool_settings||{}};r[e]={...r[e]||{},[t]:i};const n={...this.config.profiles||{},[o]:{...s,tool_settings:r}};this.saveConfig({...this.config,profiles:n})}getCustomPresets(){return{...this.config.presets||{}}}saveCustomPreset(e,t){const i={...this.config.presets||{},[e]:{models:this.normalizeModels(t),updated_at:(new Date).toISOString()}};this.saveConfig({...this.config,presets:i})}deleteCustomPreset(e){const t={...this.config.presets||{}};t[e]&&(delete t[e],this.saveConfig({...this.config,presets:t}))}listBackups(){const t=this.getBackupDir();return e(t)?n(t).filter(e=>e.startsWith("config-")&&e.endsWith(".yaml")).map(e=>{const i=f(t,e);return{name:e,path:i,mtimeMs:c(i).mtimeMs}}).sort((e,t)=>t.mtimeMs-e.mtimeMs):[]}createBackup(s){this.ensureConfigDir();const r=this.getBackupDir();e(r)||t(r,{recursive:!0});const n=(new Date).toISOString().replace(/[:.]/g,"-"),a=this.sanitizeBackupLabel(s),c=f(r,a?`config-${n}-${a}.yaml`:`config-${n}.yaml`),l=e(this.configPath)?i(this.configPath,"utf-8"):m.dump(this.prepareForPersistence(this.getConfig()));return o(c,l,{encoding:"utf-8",mode:384}),this.rotateBackups(r,10,30),c}restoreBackup(t){if(!t||"."===t||".."===t||/[/\\]|\.\./.test(t))throw new Error(`Invalid backup name: ${t}`);const o=this.getBackupDir(),s=f(o,t);if(!e(s))throw new Error(`Backup not found: ${t}`);const r=i(s,"utf-8"),n=m.load(r),a=this.getSecretsBackupPath(s);let c=this.migrateOldConfig(n||this.createDefaultConfig());if(n?.credential_store!==v&&e(a))try{c=this.mergeLegacySecrets(c,_(a))}catch(e){throw new Error(`Legacy backup credentials could not be decrypted: ${a}`,{cause:e})}this.saveConfig(c)}getConfigDir(){return this.configDir}getBaseUrl(){return this.runtimeBaseUrl||this.config.profiles?.[this.getActiveProfileName()]?.base_url||DEFAULT_BASE_URL}hasRuntimeBaseUrl(){return Boolean(this.runtimeBaseUrl)}setBaseUrl(e,t=!1){this.runtimeBaseUrl=e,t&&this.updateProfile(this.getActiveProfileName(),{base_url:e})}resetBaseUrl(){this.runtimeBaseUrl=void 0;const e=this.config.profiles?.[this.getActiveProfileName()]||{},{base_url:t,...i}=e,o={...this.config.profiles||{},[this.getActiveProfileName()]:i};this.saveConfig({...this.config,profiles:o})}}export const configManager=ConfigManager.getInstance();export function savedApiKeys(){const e=new Set;for(const t of configManager.listProfiles()){const i=configManager.getProfile(t);for(const t of[i.api_key,...Object.values(i.tool_api_keys??{})])t?.trim()&&e.add(t.trim())}return[...e]}
|
|
@@ -1,41 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shell 环境变量里的 ANTHROPIC_API_KEY / ANTHROPIC_BASE_URL 会覆盖 vibecli 写进
|
|
3
3
|
* settings.json 的凭据,导致"改了配置却不生效";同理,export 过的 ANTHROPIC_*_MODEL
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 会盖住模型选择。给 Claude Code 写配置时(写入计划确认之后)只删指向本站的那几处,
|
|
5
|
+
* 先备份;指向别家服务的不动、只提示(new-api#174)。
|
|
6
6
|
*/
|
|
7
|
-
export declare const ANTHROPIC_ENV_VARS: readonly ["ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL", "ANTHROPIC_DEFAULT_HAIKU_MODEL", "ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_DEFAULT_OPUS_MODEL", "ANTHROPIC_DEFAULT_FABLE_MODEL", "ANTHROPIC_MODEL", "ANTHROPIC_REASONING_MODEL"];
|
|
7
|
+
export declare const ANTHROPIC_ENV_VARS: readonly ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_BASE_URL", "ANTHROPIC_DEFAULT_HAIKU_MODEL", "ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_DEFAULT_OPUS_MODEL", "ANTHROPIC_DEFAULT_FABLE_MODEL", "ANTHROPIC_MODEL", "ANTHROPIC_REASONING_MODEL"];
|
|
8
8
|
/**
|
|
9
9
|
* 获取当前 shell 的 rc 文件路径(Windows 不使用 rc 文件,返回 null)。
|
|
10
10
|
*/
|
|
11
11
|
export declare function getShellRcFilePath(): string | null;
|
|
12
|
+
/** A place that sets ANTHROPIC_* for new terminals: a shell / PowerShell profile or a Windows env scope. */
|
|
13
|
+
export interface EnvSource {
|
|
14
|
+
kind: 'file' | 'windows-user' | 'windows-machine';
|
|
15
|
+
/** File path, or `HKCU\Environment` / `HKLM\…\Environment`. */
|
|
16
|
+
location: string;
|
|
17
|
+
vars: Record<string, string>;
|
|
18
|
+
/** Points at this gateway (or carries a key vibecli saved): ours to remove after confirmation. */
|
|
19
|
+
ours: boolean;
|
|
20
|
+
}
|
|
12
21
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
22
|
+
* Every persistent ANTHROPIC_* assignment new terminals pick up: the current shell's rc file
|
|
23
|
+
* (macOS / Linux), Windows user and system environment variables and PowerShell profiles.
|
|
15
24
|
*/
|
|
16
|
-
export declare function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
export declare function anthropicEnvSources(): EnvSource[];
|
|
26
|
+
export interface EnvCleanupResult {
|
|
27
|
+
/** Sources that pointed at this gateway, with what was removed from each. */
|
|
28
|
+
removed: EnvSource[];
|
|
29
|
+
/** Sources pointing somewhere else: left alone, the user decides. */
|
|
30
|
+
foreign: EnvSource[];
|
|
31
|
+
/** System-scope names that need an elevated run to delete. */
|
|
23
32
|
machineLeft: string[];
|
|
24
|
-
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* 检测并清除环境中的 ANTHROPIC_API_KEY / ANTHROPIC_BASE_URL:
|
|
28
|
-
* 1. 从当前 shell 的 rc 文件删掉对应的 export 行(持久来源);
|
|
29
|
-
* 2. 从本进程 process.env 删除——vibecli 会以子进程启动各工具,
|
|
30
|
-
* 不清掉的话残留变量仍会盖过刚写好的配置文件。
|
|
31
|
-
* 返回被清除的变量名(未检测到时为空数组)。
|
|
32
|
-
*/
|
|
33
|
-
export declare function cleanupAnthropicEnvVars(): string[];
|
|
33
|
+
}
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
36
|
-
* environment
|
|
37
|
-
*
|
|
35
|
+
* Removes ANTHROPIC_* settings that point at this gateway from shell profiles and the Windows
|
|
36
|
+
* environment (they override the settings.json vibecli writes), after backing each one up.
|
|
37
|
+
* Settings for another service are left alone and reported (new-api#174: a profile someone else
|
|
38
|
+
* wrote is not ours to delete). Runs when Claude Code is configured, after the write plan was
|
|
39
|
+
* confirmed; the plan lists the same sources (`envCleanupPlan`).
|
|
38
40
|
*/
|
|
39
|
-
export declare function
|
|
40
|
-
/**
|
|
41
|
-
export declare function
|
|
41
|
+
export declare function cleanupAnthropicEnvVars(): EnvCleanupResult;
|
|
42
|
+
/** What the last `cleanupAnthropicEnvVars()` did, for the notes after Claude Code is configured. */
|
|
43
|
+
export declare function lastEnvCleanup(): EnvCleanupResult;
|
|
44
|
+
/** Shown in the write plan before Claude Code is configured: profiles whose ANTHROPIC_* lines go. */
|
|
45
|
+
export declare function envCleanupPlan(): EnvSource[];
|
package/dist/lib/env-cleanup.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as o,mkdirSync as n,readFileSync as e,writeFileSync as r}from"fs";import{join as t}from"path";import{logger as s}from"../utils/logger.js";import{toolHome as c}from"./tools/types.js";import{backupBeforeWrite as i}from"./tools/backup.js";import{configManager as f,savedApiKeys as u}from"./config.js";import{isOurGatewayUrl as a}from"./gateway-addresses.js";import{broadcastEnvChange as l,deleteWindowsEnv as p,readWindowsEnv as m}from"./windows-env.js";export const ANTHROPIC_ENV_VARS=["ANTHROPIC_API_KEY","ANTHROPIC_AUTH_TOKEN","ANTHROPIC_BASE_URL","ANTHROPIC_DEFAULT_HAIKU_MODEL","ANTHROPIC_DEFAULT_SONNET_MODEL","ANTHROPIC_DEFAULT_OPUS_MODEL","ANTHROPIC_DEFAULT_FABLE_MODEL","ANTHROPIC_MODEL","ANTHROPIC_REASONING_MODEL"];export function getShellRcFilePath(){if("win32"===process.platform)return null;const o=c();switch((process.env.SHELL||"").split("/").pop()||""){case"bash":return t(o,".bashrc");case"zsh":return t(o,".zshrc");case"fish":return t(o,".config","fish","config.fish");default:return t(o,".profile")}}function h(o){const n=o.trim().replace(/\s+#.*$/,""),e=n.match(/^(['"])(.*)\1$/);return e?e[2]:n}const E=o=>new RegExp(String.raw`^\s*export\s+${o}=(.*)$`,"gm"),v=o=>new RegExp(String.raw`^[ \t]*\$env:${o}[ \t]*=(.*)$`,"gim");function g(o){if(a(o.ANTHROPIC_BASE_URL))return!0;const n=u();return[o.ANTHROPIC_API_KEY,o.ANTHROPIC_AUTH_TOKEN].some(o=>Boolean(o)&&n.includes(o.trim()))}export function anthropicEnvSources(){const n=[],r=(r,t)=>{if(r&&o(r))try{const o=function(o,n){const e={};for(const r of ANTHROPIC_ENV_VARS)for(const t of o.matchAll(n(r)))e[r]=h(t[1]);return e}(e(r,"utf-8"),t);Object.keys(o).length>0&&n.push({kind:"file",location:r,vars:o,ours:g(o)})}catch(o){s.logError("envCleanup.read",o)}};if("win32"!==process.platform)return r(getShellRcFilePath(),E),n;for(const o of["user","machine"]){const e={};for(const n of ANTHROPIC_ENV_VARS){const r=m(o,n);void 0!==r&&(e[n]=r)}Object.keys(e).length>0&&n.push({kind:"user"===o?"windows-user":"windows-machine",location:"user"===o?"HKCU\\Environment":"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment",vars:e,ours:g(e)})}for(const o of function(){const o=c(),n=[];for(const e of[t(o,"Documents"),t(o,"OneDrive","Documents"),t(o,"OneDrive","文档")])for(const o of["PowerShell","WindowsPowerShell"])n.push(t(e,o,"Microsoft.PowerShell_profile.ps1"),t(e,o,"profile.ps1"));return n}())r(o,v);return n}function O(o,n,t){const c=e(o,"utf-8");let f=c;for(const o of n)f=f.replace(new RegExp(t(o).source+String.raw`(\r?\n)?`,t(o).flags),"");return t===E&&(f=f.replace(/^\s*#\s*Claude Code environment variables\s*$\n?/gm,"")),f!==c&&(i(o),r(o,f,"utf-8"),s.debug(`Removed ${n.join(", ")} from ${o}`),!0)}function A(o,e){try{const s=t(f.getConfigDir(),"backups");n(s,{recursive:!0});const c=t(s,`windows-env-${o}-${(new Date).toISOString().replace(/[:.]/g,"-")}.json`);return r(c,JSON.stringify(e,null,2)+"\n",{mode:384}),c}catch(o){return s.logError("envCleanup.backupWindowsValues",o),null}}let _={removed:[],foreign:[],machineLeft:[]};export function cleanupAnthropicEnvVars(){const o={removed:[],foreign:[],machineLeft:[]};let n=!1;for(const e of anthropicEnvSources()){if(!e.ours){o.foreign.push(e);continue}const r=Object.keys(e.vars);try{if("file"===e.kind){O(e.location,r,e.location.endsWith(".ps1")?v:E)&&o.removed.push(e);continue}const t="windows-user"===e.kind?"user":"machine";A(t,e.vars);const c=[];for(const n of r)try{p(t,n),c.push(n)}catch(e){"machine"===t&&o.machineLeft.push(n),s.logError("envCleanup.deleteWindowsEnv",e)}c.length>0&&(n=!0,o.removed.push({...e,vars:Object.fromEntries(c.map(o=>[o,e.vars[o]]))}))}catch(o){s.logError("envCleanup.cleanupAnthropicEnvVars",o)}}n&&l();for(const n of o.removed)for(const[o,e]of Object.entries(n.vars))process.env[o]===e&&delete process.env[o];return _=o,o}export function lastEnvCleanup(){return _}export function envCleanupPlan(){return anthropicEnvSources().filter(o=>o.ours)}
|
|
@@ -33,6 +33,10 @@ export declare function rememberPublishedAddresses(statusData: unknown, source:
|
|
|
33
33
|
* gateway's order. A built-in address takes the gateway's label when it has one.
|
|
34
34
|
*/
|
|
35
35
|
export declare function gatewayAddresses(): GatewayAddress[];
|
|
36
|
+
/** `url` is one of this gateway's addresses (built-in, published, or the configured one). */
|
|
37
|
+
export declare function isOurGatewayUrl(url: unknown): boolean;
|
|
38
|
+
/** Remember an address vibecli is about to write into a tool, so `isOurGatewayUrl` knows it later. */
|
|
39
|
+
export declare function noteWrittenGatewayUrl(url: string): void;
|
|
36
40
|
export interface ProbeOptions {
|
|
37
41
|
/** With a key the probe lists the models (`GET /v1/models`): the whole relay path works, not just the front page. */
|
|
38
42
|
apiKey?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as r,mkdirSync as e,readFileSync as t,writeFileSync as o}from"fs";import{join as s}from"path";import{configManager as n}from"./config.js";import{
|
|
1
|
+
import{existsSync as r,mkdirSync as e,readFileSync as t,writeFileSync as o}from"fs";import{join as s}from"path";import{configManager as n}from"./config.js";import{noteGatewayUrl as a,wroteGatewayUrl as i}from"./written-values.js";import{getBuiltinGatewayAddresses as l}from"./model-registry.js";import{logger as u}from"../utils/logger.js";const c=new Set(["SELF_SIGNED_CERT_IN_CHAIN","DEPTH_ZERO_SELF_SIGNED_CERT","UNABLE_TO_VERIFY_LEAF_SIGNATURE","UNABLE_TO_GET_ISSUER_CERT_LOCALLY","CERT_HAS_EXPIRED","CERT_NOT_YET_VALID","ERR_TLS_CERT_ALTNAME_INVALID"]);function f(){return s(n.getConfigDir(),"gateway-addresses.json")}export function normalizeBaseUrl(r){let e,t=r.trim();if(!t)return null;/^[a-z][a-z0-9+.-]*:\/\//i.test(t)||(t=`https://${t}`);try{e=new URL(t)}catch{return null}if("https:"!==e.protocol&&"http:"!==e.protocol)return null;const o=e.pathname.replace(/\/+$/,"").replace(/\/v1$/i,"");return`${e.protocol}//${e.host}${o}`}export function parsePublishedAddresses(r){if(!Array.isArray(r))return[];const e=new Set,t=[];for(const o of r){if(!o||"object"!=typeof o)continue;const{url:r,route:s,description:n}=o,a="string"==typeof r?normalizeBaseUrl(r):null;if(!a?.startsWith("https://")||e.has(a))continue;e.add(a);const i="string"==typeof s?s.trim():"",l="string"==typeof n?n.trim():"";t.push({url:a,label:i&&l?`${i}(${l})`:i||l})}return t}export function rememberPublishedAddresses(r,t){if(!r||"object"!=typeof r)return;if(!l().some(r=>normalizeBaseUrl(r.url)===t))return;const s=r;if(!1===s.api_info_enabled)return;const a=parsePublishedAddresses(s.api_info);if(0!==a.length)try{e(n.getConfigDir(),{recursive:!0}),o(f(),JSON.stringify({addresses:a},null,2)+"\n")}catch(r){u.logError("rememberPublishedAddresses",r)}}export function gatewayAddresses(){const e=function(){const e=f();if(!r(e))return[];try{const r=JSON.parse(t(e,"utf-8"));return(Array.isArray(r.addresses)?r.addresses:[]).flatMap(r=>{const e="string"==typeof r?.url?normalizeBaseUrl(r.url):null;return e?[{url:e,label:"string"==typeof r.label?r.label:""}]:[]})}catch{return[]}}(),o=[];for(const r of l()){const t=normalizeBaseUrl(r.url);if(!t||o.some(r=>r.url===t))continue;const s=e.find(r=>r.url===t)?.label||r.label;o.push({url:t,label:s})}for(const r of e)o.some(e=>e.url===r.url)||o.push(r);return o}export function isOurGatewayUrl(r){if("string"!=typeof r)return!1;const e=normalizeBaseUrl(r);return!!e&&(gatewayAddresses().some(r=>r.url===e)||e===normalizeBaseUrl(n.getBaseUrl())||i(e))}export function noteWrittenGatewayUrl(r){const e=normalizeBaseUrl(r);e&&a(e)}function m(r){const e=r?.cause,t="string"==typeof e?.code?e.code:"",o="string"==typeof e?.message?e.message:r instanceof Error?r.message:String(r);return r instanceof Error&&"AbortError"===r.name?{error:"timeout",detail:o}:c.has(t)||/certificate/i.test(o)?{error:"certificate",detail:t||o}:{error:"network",detail:t||o}}export async function probeGatewayAddress(r,e={}){e.apiKey&&!function(r){return r===n.getBaseUrl()||l().some(e=>normalizeBaseUrl(e.url)===r)}(r)&&(e={...e,apiKey:void 0});const t=Math.max(1,e.samples??1),o=[];let s;for(let n=0;n<t;n+=1){const t=await d(r,e);if(!t.ok)return t;o.push(t.ms),s=t.models}return o.sort((r,e)=>r-e),{url:r,ok:!0,ms:o[Math.floor(o.length/2)],...void 0===s?{}:{models:s}}}async function d(r,e){const{apiKey:t,timeoutMs:o=8e3,fetchFn:s=fetch}=e,n=new AbortController,a=setTimeout(()=>n.abort(),o),i=Date.now();try{const e=await s(t?`${r}/v1/models`:`${r}/api/status`,{method:"GET",headers:t?{Authorization:`Bearer ${t}`}:void 0,signal:n.signal});let o;try{o=(await e.json())?.data}catch{o=void 0}const a=Date.now()-i;return!t||401!==e.status&&403!==e.status?t?e.ok&&Array.isArray(o)?{url:r,ok:!0,ms:a,models:o.length}:{url:r,ok:!1,ms:a,error:"not_gateway",detail:`HTTP ${e.status}`}:e.ok&&o&&"object"==typeof o?(rememberPublishedAddresses(o,r),{url:r,ok:!0,ms:a}):{url:r,ok:!1,ms:a,error:"not_gateway",detail:`HTTP ${e.status}`}:{url:r,ok:!1,ms:a,error:"key_rejected",detail:`HTTP ${e.status}`}}catch(e){return{url:r,ok:!1,ms:Date.now()-i,...m(e)}}finally{clearTimeout(a)}}
|
|
@@ -102,6 +102,8 @@ export declare function overlayClientSection(base: ModelRegistryData, v: ClientS
|
|
|
102
102
|
export declare function getDefaultModels(): Models;
|
|
103
103
|
/** Earlier default for this slot, saved into a profile by the wizard's prefilled inputs. */
|
|
104
104
|
export declare function isRetiredDefaultModel(slot: string, model: string): boolean;
|
|
105
|
+
/** Every earlier default of any slot (older vibecli versions wrote these into tool settings). */
|
|
106
|
+
export declare function getRetiredDefaultModelIds(): string[];
|
|
105
107
|
/** The model non-Claude tools use unless the user configured something else. */
|
|
106
108
|
export declare function getDefaultToolModel(): string;
|
|
107
109
|
/**
|