@vibeapi/cli 0.0.56 → 0.0.58

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.
Files changed (46) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +1 -1
  3. package/dist/commands/completion.js +1 -1
  4. package/dist/commands/doctor.d.ts +3 -1
  5. package/dist/commands/doctor.js +1 -1
  6. package/dist/commands/setup.js +1 -1
  7. package/dist/commands/upgrade.d.ts +2 -0
  8. package/dist/commands/upgrade.js +1 -0
  9. package/dist/lib/claude-code-manager.d.ts +4 -5
  10. package/dist/lib/claude-code-manager.js +1 -1
  11. package/dist/lib/command.js +1 -1
  12. package/dist/lib/config.d.ts +6 -0
  13. package/dist/lib/config.js +1 -1
  14. package/dist/lib/env-cleanup.d.ts +12 -0
  15. package/dist/lib/env-cleanup.js +1 -1
  16. package/dist/lib/gateway-models.d.ts +1 -1
  17. package/dist/lib/gateway-models.js +1 -1
  18. package/dist/lib/model-refresh.d.ts +21 -0
  19. package/dist/lib/model-refresh.js +1 -0
  20. package/dist/lib/model-registry.d.ts +4 -0
  21. package/dist/lib/model-registry.js +1 -1
  22. package/dist/lib/post-apply.d.ts +6 -0
  23. package/dist/lib/post-apply.js +1 -0
  24. package/dist/lib/tool-manager.js +1 -1
  25. package/dist/lib/tool-upgrade.d.ts +10 -0
  26. package/dist/lib/tool-upgrade.js +1 -0
  27. package/dist/lib/tools/claude-code.js +1 -1
  28. package/dist/lib/tools/claude-desktop.js +1 -1
  29. package/dist/lib/tools/claude-managed.d.ts +8 -0
  30. package/dist/lib/tools/claude-managed.js +1 -0
  31. package/dist/lib/tools/claude-native-mirror.d.ts +39 -0
  32. package/dist/lib/tools/claude-native-mirror.js +1 -0
  33. package/dist/lib/tools/codex.js +1 -1
  34. package/dist/lib/tools/desktop-process.d.ts +24 -0
  35. package/dist/lib/tools/desktop-process.js +1 -0
  36. package/dist/lib/tools/tool-installs.d.ts +50 -0
  37. package/dist/lib/tools/tool-installs.js +1 -0
  38. package/dist/lib/tools/types.d.ts +26 -3
  39. package/dist/lib/wizard.js +1 -1
  40. package/dist/locales/en_US.json +81 -26
  41. package/dist/locales/zh_CN.json +81 -26
  42. package/dist/models.json +77 -31
  43. package/models.json +77 -31
  44. package/package.json +1 -1
  45. package/dist/lib/tools/claude-installs.d.ts +0 -20
  46. package/dist/lib/tools/claude-installs.js +0 -1
package/README.md CHANGED
@@ -94,7 +94,8 @@ vibecli config <tool> restore # 还原该工具到上次更改前
94
94
  vibecli config <tool> clear-model # 清掉钉死的模型设置,回到工具自带默认(保留 API Key)
95
95
  vibecli config tool-model set codex gpt-6-astra # 单独给某工具指定模型
96
96
  vibecli config profile use work # 切换 profile
97
- vibecli doctor # 健康巡检
97
+ vibecli doctor # 健康巡检(--deep 实发一条请求)
98
+ vibecli upgrade # 升级 Claude Code / Codex,官方渠道连不上自动改走国内镜像
98
99
  ```
99
100
 
100
101
  ## 支持
package/dist/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as r}from"./lib/command.js";import{i18n as o}from"./lib/i18n.js";import{logger as e}from"./utils/logger.js";!async function(){const s=process.argv.slice(2),i=new r;try{await i.execute(s)}catch(r){e.logError("CLI",r),console.error(`${o.t("cli.error_general")}`,r instanceof Error?r.message:String(r)),process.exit(1)}}();
2
+ Number(process.versions.node.split(".")[0])<20&&(console.error(`vibecli 需要 Node.js 20 或更高版本,当前是 v${process.versions.node}。\n请安装 Node.js LTS(国内镜像:https://registry.npmmirror.com/binary.html?path=node/ ,或用 nvm install --lts),装好后重开终端再运行。\nvibecli needs Node.js 20 or later (found v${process.versions.node}); install the LTS release and open a new terminal.`),process.exit(1));const{Command:e}=await import("./lib/command.js"),{i18n:r}=await import("./lib/i18n.js"),{logger:o}=await import("./utils/logger.js");try{await(new e).execute(process.argv.slice(2))}catch(e){o.logError("CLI",e),console.error(`${r.t("cli.error_general")}`,e instanceof Error?e.message:String(e)),process.exit(1)}export{};
@@ -1 +1 @@
1
- import{i18n as n}from"../lib/i18n.js";import{isJsonMode as e,emitJson as o}from"../lib/runtime-options.js";const i=["init","setup","lang","auth","doctor","status","logs","completion","update","bench","config","enter"],c=["profile","preset","backup","claude-code","codex"],s=["list","create","use","delete"],l=["list","save","use","delete"],t=["list","create","restore"],r=["revoke","reload"],m=["show","set"],a=["path","list","tail"],u=["bash","zsh","fish"],p=["--base","--non-interactive","--json","--yes","--dry-run","--no-color","--help","--version"];export async function completionCommand(_){const b=(_[0]||"").toLowerCase();if(!u.includes(b))return e()?void o({ok:!1,error:"unsupported_shell",shell:b}):void console.log(n.t("completion.unsupported_shell",{shell:b}));const $="bash"===b?`# vibecli bash completion\n_vibecli_completions() {\n local cur prev\n COMPREPLY=()\n cur="\${COMP_WORDS[COMP_CWORD]}"\n prev="\${COMP_WORDS[COMP_CWORD-1]}"\n\n case "\${prev}" in\n vibecli)\n COMPREPLY=( $(compgen -W "${i.join(" ")} ${p.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n config)\n COMPREPLY=( $(compgen -W "${c.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n profile)\n COMPREPLY=( $(compgen -W "${s.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n preset)\n COMPREPLY=( $(compgen -W "${l.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n backup)\n COMPREPLY=( $(compgen -W "${t.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n auth)\n COMPREPLY=( $(compgen -W "${r.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n lang)\n COMPREPLY=( $(compgen -W "${m.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n logs)\n COMPREPLY=( $(compgen -W "${a.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n completion)\n COMPREPLY=( $(compgen -W "${u.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n esac\n\n if [[ "\${cur}" == --* ]]; then\n COMPREPLY=( $(compgen -W "${p.join(" ")}" -- "\${cur}") )\n return 0\n fi\n}\ncomplete -F _vibecli_completions vibecli\n`:"zsh"===b?`#compdef vibecli\n_vibecli() {\n local -a commands subcommands\n local context state line\n\n _arguments -C \\\n '(-h --help)'{-h,--help}'[Show help]' \\\n '(-v --version)'{-v,--version}'[Show version]' \\\n '--base[Custom API base URL]:url:' \\\n '--non-interactive[Disable interactive prompts]' \\\n '--json[Emit JSON output]' \\\n '(-y --yes)'{-y,--yes}'[Auto-confirm prompts]' \\\n '--dry-run[Show planned changes without writing]' \\\n '--no-color[Disable colored output]' \\\n '1:command:->command' \\\n '*::arg:->args'\n\n case $state in\n command)\n commands=(\n${i.map(n=>` '${n}:vibecli ${n}'`).join("\n")}\n )\n _describe 'command' commands\n ;;\n args)\n case $line[1] in\n config)\n subcommands=(${c.map(n=>`'${n}'`).join(" ")})\n _describe 'config sub' subcommands\n ;;\n auth) _values 'auth sub' ${r.join(" ")} ;;\n lang) _values 'lang sub' ${m.join(" ")} ;;\n logs) _values 'logs action' ${a.join(" ")} ;;\n completion) _values 'shell' ${u.join(" ")} ;;\n esac\n ;;\n esac\n}\ncompdef _vibecli vibecli\n`:`# vibecli fish completion\ncomplete -c vibecli -n "__fish_use_subcommand" -a "${i.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from config" -a "${c.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from profile" -a "${s.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from preset" -a "${l.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from backup" -a "${t.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from auth" -a "${r.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from lang" -a "${m.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from logs" -a "${a.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from completion" -a "${u.join(" ")}"\ncomplete -c vibecli -l base -d 'Custom API base URL'\ncomplete -c vibecli -l non-interactive -d 'Disable interactive prompts'\ncomplete -c vibecli -l json -d 'Emit JSON output'\ncomplete -c vibecli -l yes -s y -d 'Auto-confirm prompts'\ncomplete -c vibecli -l dry-run -d 'Show planned changes without writing'\ncomplete -c vibecli -l no-color -d 'Disable colored output'\n`;e()?o({shell:b,script:$}):(console.log($),"bash"===b?console.log("# "+n.t("completion.instructions_bash")):"zsh"===b?console.log("# "+n.t("completion.instructions_zsh")):console.log("# "+n.t("completion.instructions_fish")))}
1
+ import{i18n as n}from"../lib/i18n.js";import{isJsonMode as e,emitJson as o}from"../lib/runtime-options.js";const i=["init","setup","lang","auth","doctor","status","logs","completion","update","upgrade","bench","config","enter"],c=["profile","preset","backup","claude-code","codex"],s=["list","create","use","delete"],l=["list","save","use","delete"],t=["list","create","restore"],r=["revoke","reload"],m=["show","set"],a=["path","list","tail"],u=["bash","zsh","fish"],p=["--base","--non-interactive","--json","--yes","--dry-run","--no-color","--help","--version"];export async function completionCommand(_){const b=(_[0]||"").toLowerCase();if(!u.includes(b))return e()?void o({ok:!1,error:"unsupported_shell",shell:b}):void console.log(n.t("completion.unsupported_shell",{shell:b}));const $="bash"===b?`# vibecli bash completion\n_vibecli_completions() {\n local cur prev\n COMPREPLY=()\n cur="\${COMP_WORDS[COMP_CWORD]}"\n prev="\${COMP_WORDS[COMP_CWORD-1]}"\n\n case "\${prev}" in\n vibecli)\n COMPREPLY=( $(compgen -W "${i.join(" ")} ${p.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n config)\n COMPREPLY=( $(compgen -W "${c.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n profile)\n COMPREPLY=( $(compgen -W "${s.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n preset)\n COMPREPLY=( $(compgen -W "${l.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n backup)\n COMPREPLY=( $(compgen -W "${t.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n auth)\n COMPREPLY=( $(compgen -W "${r.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n lang)\n COMPREPLY=( $(compgen -W "${m.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n logs)\n COMPREPLY=( $(compgen -W "${a.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n completion)\n COMPREPLY=( $(compgen -W "${u.join(" ")}" -- "\${cur}") )\n return 0\n ;;\n esac\n\n if [[ "\${cur}" == --* ]]; then\n COMPREPLY=( $(compgen -W "${p.join(" ")}" -- "\${cur}") )\n return 0\n fi\n}\ncomplete -F _vibecli_completions vibecli\n`:"zsh"===b?`#compdef vibecli\n_vibecli() {\n local -a commands subcommands\n local context state line\n\n _arguments -C \\\n '(-h --help)'{-h,--help}'[Show help]' \\\n '(-v --version)'{-v,--version}'[Show version]' \\\n '--base[Custom API base URL]:url:' \\\n '--non-interactive[Disable interactive prompts]' \\\n '--json[Emit JSON output]' \\\n '(-y --yes)'{-y,--yes}'[Auto-confirm prompts]' \\\n '--dry-run[Show planned changes without writing]' \\\n '--no-color[Disable colored output]' \\\n '1:command:->command' \\\n '*::arg:->args'\n\n case $state in\n command)\n commands=(\n${i.map(n=>` '${n}:vibecli ${n}'`).join("\n")}\n )\n _describe 'command' commands\n ;;\n args)\n case $line[1] in\n config)\n subcommands=(${c.map(n=>`'${n}'`).join(" ")})\n _describe 'config sub' subcommands\n ;;\n auth) _values 'auth sub' ${r.join(" ")} ;;\n lang) _values 'lang sub' ${m.join(" ")} ;;\n logs) _values 'logs action' ${a.join(" ")} ;;\n completion) _values 'shell' ${u.join(" ")} ;;\n esac\n ;;\n esac\n}\ncompdef _vibecli vibecli\n`:`# vibecli fish completion\ncomplete -c vibecli -n "__fish_use_subcommand" -a "${i.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from config" -a "${c.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from profile" -a "${s.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from preset" -a "${l.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from backup" -a "${t.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from auth" -a "${r.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from lang" -a "${m.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from logs" -a "${a.join(" ")}"\ncomplete -c vibecli -n "__fish_seen_subcommand_from completion" -a "${u.join(" ")}"\ncomplete -c vibecli -l base -d 'Custom API base URL'\ncomplete -c vibecli -l non-interactive -d 'Disable interactive prompts'\ncomplete -c vibecli -l json -d 'Emit JSON output'\ncomplete -c vibecli -l yes -s y -d 'Auto-confirm prompts'\ncomplete -c vibecli -l dry-run -d 'Show planned changes without writing'\ncomplete -c vibecli -l no-color -d 'Disable colored output'\n`;e()?o({shell:b,script:$}):(console.log($),"bash"===b?console.log("# "+n.t("completion.instructions_bash")):"zsh"===b?console.log("# "+n.t("completion.instructions_zsh")):console.log("# "+n.t("completion.instructions_fish")))}
@@ -1 +1,3 @@
1
- export declare function doctorCommand(): Promise<void>;
1
+ export declare function doctorCommand(options?: {
2
+ deep?: boolean;
3
+ }): Promise<void>;
@@ -1 +1 @@
1
- import o from"chalk";import e from"ora";import{configManager as t}from"../lib/config.js";import{toolManager as s}from"../lib/tool-manager.js";import{i18n as n}from"../lib/i18n.js";import{validateApiKey as l}from"../lib/api-validator.js";export async function doctorCommand(){const a=e(n.t("doctor.checking")).start(),r=[];a.text=n.t("doctor.check_path");try{const o=process.env.PATH||"";r.push({name:"PATH",passed:o.length>0,message:o.length>0?void 0:"PATH is empty"})}catch(o){r.push({name:"PATH",passed:!1,message:String(o)})}a.text=n.t("doctor.check_api_key_network");const i=t.getApiKey();if(i){const e=await l(i);e.valid?r.push({name:"API Key & Network",passed:!0}):"invalid_api_key"===e.error?r.push({name:"API Key & Network",passed:!1,message:o.yellow(n.t("doctor.api_key_invalid"))}):r.push({name:"API Key & Network",passed:!1,message:o.yellow(n.t("doctor.network_error"))})}else r.push({name:"API Key & Network",passed:!1,message:o.yellow(n.t("doctor.api_key_missing"))});if("win32"===process.platform&&s.isToolInstalled("claude-code")){a.text=n.t("doctor.check_git_env");const e=s.isGitInstalled();r.push({name:"Git Environment",passed:e,message:e?void 0:o.yellow(n.t("doctor.git_not_installed"))})}a.text=n.t("doctor.check_tools");const c=s.getInstalledTools();r.push({name:"Installed Tools",passed:c.length>0,message:c.length>0?`${c.map(o=>s.getSupportedTools().find(e=>e.name===o)?.displayName||o).join(", ")}`:o.yellow(n.t("doctor.no_tools_installed"))}),a.stop(),console.log("\n"+o.bold(n.t("doctor.results_title"))+"\n");for(const e of r){const t=e.passed?o.green("✓"):o.red("✗"),s=e.passed?o.green("PASS"):o.red("FAIL");console.log(`${t} ${e.name}: ${s}`),e.message&&console.log(` ${e.message}`)}r.every(o=>o.passed)?console.log("\n"+o.green.bold(n.t("doctor.all_good"))):(console.log("\n"+o.yellow.bold(n.t("doctor.suggestions"))),console.log(o.gray(" - "+n.t("doctor.suggestion_auth"))),console.log(o.gray(" - "+n.t("doctor.suggestion_interactive"))))}
1
+ import t from"chalk";import o from"ora";import{spawnSync as s}from"child_process";import{readFileSync as e}from"fs";import{connect as a}from"net";import{join as n}from"path";import{configManager as r}from"../lib/config.js";import{toolManager as c}from"../lib/tool-manager.js";import{i18n as i}from"../lib/i18n.js";import{validateApiKey as l}from"../lib/api-validator.js";import{getAdapter as d}from"../lib/tools/registry.js";import{findInstalls as p,installLabel as u}from"../lib/tools/tool-installs.js";import{detectManagedOverrides as m}from"../lib/tools/claude-managed.js";import{toolHome as g}from"../lib/tools/types.js";const _=["HTTPS_PROXY","https_proxy","HTTP_PROXY","http_proxy","ALL_PROXY","all_proxy"];function f(t,o,s=3e3){return new Promise(e=>{const n=a({host:t,port:o}),r=t=>{n.destroy(),e(t)};n.setTimeout(s,()=>r(!1)),n.once("connect",()=>r(!0)),n.once("error",()=>r(!1))})}export async function doctorCommand(a={}){const h=o(i.t("doctor.checking")).start(),y=[],w=Number(process.versions.node.split(".")[0]);y.push({name:"Node.js",status:w>=20?"pass":"fail",message:`v${process.versions.node}`}),h.text=i.t("doctor.check_proxy"),y.push(await async function(){const t=i.t("doctor.proxy"),o=new Map;for(const t of _){const s=process.env[t]?.trim();s&&!o.has(s)&&o.set(s,t)}if(0===o.size)return{name:t,status:"pass",message:i.t("doctor.proxy_none")};const s=[];for(const[t,e]of o){let o;try{o=new URL(t.includes("://")?t:`http://${t}`)}catch{s.push(`${e}=${t}`);continue}const a=Number(o.port)||("https:"===o.protocol?443:o.protocol.startsWith("socks")?1080:80);await f(o.hostname,a)||s.push(`${e}=${t}`)}return 0===s.length?{name:t,status:"pass",message:[...o.keys()].join(", ")}:{name:t,status:"fail",message:i.t("doctor.proxy_dead",{proxies:s.join(", ")})}}()),h.text=i.t("doctor.check_api_key_network");const $=r.getApiKey(),T=i.t("doctor.api_key_network");if($){const t=await l($);t.valid?y.push({name:T,status:"pass"}):"invalid_api_key"===t.error?y.push({name:T,status:"fail",message:i.t("doctor.api_key_invalid")}):y.push({name:T,status:"fail",message:`${i.t("doctor.network_error")}: ${t.message??t.error}`})}else y.push({name:T,status:"fail",message:i.t("doctor.api_key_missing")});h.text=i.t("doctor.check_tools");const j=c.getInstalledTools();y.push({name:i.t("doctor.installed_tools"),status:j.length>0?"pass":"warn",message:j.length>0?j.map(t=>d(t)?.displayName??t).join(", "):i.t("doctor.no_tools_installed")}),y.push(...function(){const t=[];for(const o of c.getInstalledTools()){const s=d(o);if(!s?.installSpec)continue;const e=p(s.installSpec);if(0===e.length)continue;const a=[],n=c.outdatedVersion(o);n&&a.push(i.t("doctor.tool_outdated",{min:n.min})),e.length>1&&a.push(i.t("doctor.tool_duplicates",{others:e.slice(1).map(u).join("、")})),t.push({name:s.displayName,status:a.length>0?"warn":"pass",message:[u(e[0]),...a,...a.length>0?[i.t("doctor.run_upgrade",{tool:o})]:[]].join("\n ")})}return t}());const k=function(){if(!c.isToolInstalled("claude-code"))return null;const t=i.t("doctor.claude_config"),o=m();if(o.length>0){const s=o.map(t=>`${t.source}: ${t.keys.join(", ")}`).join("; ");return{name:t,status:"fail",message:i.t("doctor.claude_managed",{detail:s})}}let s={};try{s=JSON.parse(e(n(g(),".claude","settings.json"),"utf-8")).env??{}}catch{}const a=r.getApiKeyForTool("claude-code");return s.ANTHROPIC_AUTH_TOKEN&&s.ANTHROPIC_BASE_URL?s.ANTHROPIC_BASE_URL!==r.getBaseUrl()||a&&s.ANTHROPIC_AUTH_TOKEN!==a?{name:t,status:"warn",message:i.t("doctor.claude_config_changed")}:{name:t,status:"pass"}:{name:t,status:"warn",message:i.t("doctor.claude_not_configured")}}();k&&y.push(k),"win32"===process.platform&&j.includes("claude-code")&&!process.env.CLAUDE_CODE_GIT_BASH_PATH&&y.push({name:"Git for Windows",status:c.isGitInstalled()?"pass":"warn",message:c.isGitInstalled()?void 0:i.t("tools.claude_code.no_git_bash")}),a.deep&&j.includes("claude-code")&&(h.text=i.t("doctor.check_claude_request"),y.push(function(){const t=i.t("doctor.claude_request"),o=p(d("claude-code").installSpec)[0];if(!o)return{name:t,status:"warn",message:i.t("doctor.claude_request_skipped")};const e={encoding:"utf-8",timeout:12e4,windowsHide:!0,stdio:["ignore","pipe","pipe"]},a=["-p","Reply with the single word OK."],n=/\.cmd$/i.test(o.path)?s(`"${o.path}" ${a.map(t=>`"${t}"`).join(" ")}`,{...e,shell:!0}):s(o.path,a,e),r=`${n.stdout??""}`.trim();if(0===n.status&&r)return{name:t,status:"pass",message:r.slice(0,80)};const c=(`${n.stderr??""}`.trim()||r||n.error?.message||`exit ${n.status}`).slice(0,300);return{name:t,status:"fail",message:i.t("doctor.claude_request_failed",{reason:c})}}())),h.stop(),console.log("\n"+t.bold(i.t("doctor.results_title"))+"\n");const x={pass:t.green("√"),warn:t.yellow("!"),fail:t.red("×")};for(const o of y)console.log(`${x[o.status]} ${o.name}`),o.message&&console.log(t.gray(` ${o.message}`));y.every(t=>"pass"===t.status)&&console.log("\n"+t.green.bold(i.t("doctor.all_good"))),!a.deep&&j.includes("claude-code")&&console.log(t.gray("\n"+i.t("doctor.deep_hint"))),y.some(t=>"fail"===t.status)&&(process.exitCode=1)}
@@ -1 +1 @@
1
- import e from"chalk";import{getAdapter as o}from"../lib/tools/registry.js";import{i18n as t}from"../lib/i18n.js";import{configManager as s}from"../lib/config.js";import{toolManager as l,SUPPORTED_TOOLS as a}from"../lib/tool-manager.js";import{validateApiKey as i,fetchModels as n}from"../lib/api-validator.js";import{CODEX_ONE_MILLION_CONTEXT_SETTING as d,setCodexOneMillionContext as r}from"../lib/tools/codex.js";import{resolveModelCatalog as p}from"../lib/model-catalog.js";import{CLAUDE_DESKTOP_HIDE_OFFICIAL_LOGIN_SETTING as c}from"../lib/tools/claude-desktop.js";async function u(e){const o=await n(e);if(!o.ok)return{hasClaude:!0,hasGpt:!0,hasGrok:!0,probed:!1};const t=o.models.map(e=>e.toLowerCase());return{hasClaude:t.some(e=>e.includes("claude")),hasGpt:t.some(e=>e.startsWith("gpt")||e.includes("gpt-")||/^o\d/.test(e)),hasGrok:t.some(e=>e.includes("grok")),probed:!0}}function f(e,o){return"claude"===o?e.hasClaude:"gpt"===o?e.hasGpt:e.hasGrok}export function parseOnOff(e){const o=(e??"").trim().toLowerCase();return!!["on","true","show","1","yes","y"].includes(o)||!["off","false","hide","0","no","n"].includes(o)&&void 0}function m(o){console.log(e.green(o))}function g(o){console.log(e.yellow(o))}function y(o){console.error(e.red(o))}async function k(o,s){console.log(e.gray(t.t("setup.validating",{label:o})));const l=await i(s);return!!l.valid||(y(t.t("setup.invalid_key",{label:o,reason:l.message||l.error||"invalid"})),!1)}async function _(o,i,n,r,p){const u=a[o];try{"codex"===o&&void 0!==n&&s.setToolSetting("codex",d,n),"claude-desktop"===o&&void 0!==r&&s.setToolSetting("claude-desktop",c,!r),await l.applyPrivateConfig(o,i,s.getModelsForTool(o),{force:p})}catch(e){const o=(e instanceof Error?e.message:String(e)).replace(/\s+/g," ").trim();return y("× "+t.t("setup.apply_failed",{tool:u.displayName,reason:o})),!1}return l.printApplied(o),p&&console.log(e.gray(" "+t.t("config.force_done",{tool:u.displayName}))),"codex"===o&&void 0!==n&&console.log(e.gray(" "+t.t(n?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))),l.isToolInstalled(o)||console.log(e.yellow(" ! "+t.t("setup.install_hint",{tool:u.displayName,install:u.installCommand}))),!0}export async function setupCommand(e){const i=e.key?.trim(),n=e.claudekey?.trim(),c=e.codexkey?.trim(),b=e.grokkey?.trim(),h=e.desktopkey?.trim(),x=void 0!==e.oneMillion,v=void 0!==e.claudeOfficial;let C;if(v&&(C=parseOnOff(e.claudeOfficial),void 0===C))return y(t.t("setup.claude_official_invalid",{value:String(e.claudeOfficial)})),void(process.exitCode=1);const w=Boolean(i||n||c||b||h);if(!w&&(x||v)){if(x){const o=s.getApiKeyForTool("codex");if(o)try{const l=!0===e.oneMillion;s.setToolSetting("codex",d,l);const a=await p(o);r(l,a??void 0),m(t.t(l?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))}catch(e){y(t.t("setup.apply_failed",{tool:a.codex.displayName,reason:e instanceof Error?e.message:String(e)})),process.exitCode=1}else y(t.t("setup.codex_1m_missing_key")),process.exitCode=1}if(v){const o=s.getApiKeyForTool("claude-desktop");o?await _("claude-desktop",o,void 0,C,e.force)||(process.exitCode=1):(y(t.t("setup.desktop_official_login_missing_key")),process.exitCode=1)}return}if(!w)return y(t.t("setup.usage")),void(process.exitCode=1);let K=0,G=0;const M=new Set,j=[];n&&j.push({tool:"claude-code",label:"--claudekey",apiKey:n,need:"claude"}),c&&j.push({tool:"codex",label:"--codexkey",apiKey:c,need:"gpt"}),b&&j.push({tool:"grok",label:"--grokkey",apiKey:b,need:"grok"}),h&&j.push({tool:"claude-desktop",label:"--desktopkey",apiKey:h,need:"claude"});for(const o of j){if(!await k(o.label,o.apiKey)){G+=1;continue}const l=await u(o.apiKey);!l.probed||f(l,o.need)?(l.probed||g(t.t("setup.probe_failed",{label:o.label})),s.setToolApiKey(o.tool,o.apiKey),await _(o.tool,o.apiKey,e.oneMillion,C,e.force)?(K+=1,M.add(o.tool)):G+=1):(y(t.t(`setup.key_no_${o.need}`,{label:o.label,tool:a[o.tool].displayName})),G+=1)}if(i){if(!await k("--key",i))return void(process.exitCode=1);s.setApiKey(i);const o=await u(i);if(o.probed||g(t.t("setup.probe_failed",{label:"--key"})),o.probed&&!o.hasClaude&&!o.hasGpt&&!o.hasGrok)return y(t.t("setup.key_no_family")),void(process.exitCode=1);const a=[{tool:"claude-code",capable:o.hasClaude,skipMsg:t.t("setup.skip_no_claude")},{tool:"codex",capable:o.hasGpt,skipMsg:t.t("setup.skip_no_gpt")},{tool:"grok",capable:o.hasGrok,skipMsg:t.t("setup.skip_no_grok")},{tool:"claude-desktop",capable:o.hasClaude&&l.isToolInstalled("claude-desktop"),skipMsg:t.t("setup.skip_desktop_not_installed"),quiet:!o.hasClaude}];for(const o of a)M.has(o.tool)||(o.capable?await _(o.tool,i,e.oneMillion,C,e.force)?(K+=1,M.add(o.tool)):G+=1:o.quiet||g(o.skipMsg))}if(K>0){const e=[...M].filter(e=>!o(e)?.isInstalled).map(e=>a[e]?.command).filter(Boolean).join(" / "),s=t.t("setup.done",{count:String(K)});m("\n"+(e?`${s}${t.t("setup.done_commands",{commands:e})}`:s))}G>0&&y(t.t("setup.failed_summary",{count:String(G)})),(G>0||0===K)&&(process.exitCode=1)}
1
+ import e from"chalk";import{getAdapter as o}from"../lib/tools/registry.js";import{postApplyPrompts as t}from"../lib/post-apply.js";import{i18n as s}from"../lib/i18n.js";import{configManager as l}from"../lib/config.js";import{toolManager as a,SUPPORTED_TOOLS as i}from"../lib/tool-manager.js";import{validateApiKey as n,fetchModels as d}from"../lib/api-validator.js";import{CODEX_ONE_MILLION_CONTEXT_SETTING as r,setCodexOneMillionContext as p}from"../lib/tools/codex.js";import{resolveModelCatalog as c}from"../lib/model-catalog.js";import{CLAUDE_DESKTOP_HIDE_OFFICIAL_LOGIN_SETTING as u}from"../lib/tools/claude-desktop.js";async function f(e){const o=await d(e);if(!o.ok)return{hasClaude:!0,hasGpt:!0,hasGrok:!0,probed:!1};const t=o.models.map(e=>e.toLowerCase());return{hasClaude:t.some(e=>e.includes("claude")),hasGpt:t.some(e=>e.startsWith("gpt")||e.includes("gpt-")||/^o\d/.test(e)),hasGrok:t.some(e=>e.includes("grok")),probed:!0}}function m(e,o){return"claude"===o?e.hasClaude:"gpt"===o?e.hasGpt:e.hasGrok}export function parseOnOff(e){const o=(e??"").trim().toLowerCase();return!!["on","true","show","1","yes","y"].includes(o)||!["off","false","hide","0","no","n"].includes(o)&&void 0}function g(o){console.log(e.green(o))}function y(o){console.log(e.yellow(o))}function k(o){console.error(e.red(o))}async function _(o,t){console.log(e.gray(s.t("setup.validating",{label:o})));const l=await n(t);return!!l.valid||(k(s.t("setup.invalid_key",{label:o,reason:l.message||l.error||"invalid"})),!1)}async function b(o,t,n,d,p){const c=i[o];try{"codex"===o&&void 0!==n&&l.setToolSetting("codex",r,n),"claude-desktop"===o&&void 0!==d&&l.setToolSetting("claude-desktop",u,!d),await a.applyPrivateConfig(o,t,l.getModelsForTool(o),{force:p})}catch(e){const o=(e instanceof Error?e.message:String(e)).replace(/\s+/g," ").trim();return k("× "+s.t("setup.apply_failed",{tool:c.displayName,reason:o})),!1}return a.printApplied(o),p&&console.log(e.gray(" "+s.t("config.force_done",{tool:c.displayName}))),"codex"===o&&void 0!==n&&console.log(e.gray(" "+s.t(n?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))),a.isToolInstalled(o)||console.log(e.yellow(" ! "+s.t("setup.install_hint",{tool:c.displayName,install:c.installCommand}))),!0}export async function setupCommand(e){const n=e.key?.trim(),d=e.claudekey?.trim(),u=e.codexkey?.trim(),h=e.grokkey?.trim(),x=e.desktopkey?.trim(),v=void 0!==e.oneMillion,C=void 0!==e.claudeOfficial;let w;if(C&&(w=parseOnOff(e.claudeOfficial),void 0===w))return k(s.t("setup.claude_official_invalid",{value:String(e.claudeOfficial)})),void(process.exitCode=1);const K=Boolean(n||d||u||h||x);if(!K&&(v||C)){if(v){const o=l.getApiKeyForTool("codex");if(o)try{const t=!0===e.oneMillion;l.setToolSetting("codex",r,t);const a=await c(o);p(t,a??void 0),g(s.t(t?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))}catch(e){k(s.t("setup.apply_failed",{tool:i.codex.displayName,reason:e instanceof Error?e.message:String(e)})),process.exitCode=1}else k(s.t("setup.codex_1m_missing_key")),process.exitCode=1}if(C){const o=l.getApiKeyForTool("claude-desktop");o?await b("claude-desktop",o,void 0,w,e.force)||(process.exitCode=1):(k(s.t("setup.desktop_official_login_missing_key")),process.exitCode=1)}return}if(!K)return k(s.t("setup.usage")),void(process.exitCode=1);let j=0,G=0;const M=new Set,S=[];d&&S.push({tool:"claude-code",label:"--claudekey",apiKey:d,need:"claude"}),u&&S.push({tool:"codex",label:"--codexkey",apiKey:u,need:"gpt"}),h&&S.push({tool:"grok",label:"--grokkey",apiKey:h,need:"grok"}),x&&S.push({tool:"claude-desktop",label:"--desktopkey",apiKey:x,need:"claude"});for(const o of S){if(!await _(o.label,o.apiKey)){G+=1;continue}const t=await f(o.apiKey);!t.probed||m(t,o.need)?(t.probed||y(s.t("setup.probe_failed",{label:o.label})),l.setToolApiKey(o.tool,o.apiKey),await b(o.tool,o.apiKey,e.oneMillion,w,e.force)?(j+=1,M.add(o.tool)):G+=1):(k(s.t(`setup.key_no_${o.need}`,{label:o.label,tool:i[o.tool].displayName})),G+=1)}if(n){if(!await _("--key",n))return void(process.exitCode=1);l.setApiKey(n);const o=await f(n);if(o.probed||y(s.t("setup.probe_failed",{label:"--key"})),o.probed&&!o.hasClaude&&!o.hasGpt&&!o.hasGrok)return k(s.t("setup.key_no_family")),void(process.exitCode=1);const t=[{tool:"claude-code",capable:o.hasClaude,skipMsg:s.t("setup.skip_no_claude")},{tool:"codex",capable:o.hasGpt,skipMsg:s.t("setup.skip_no_gpt")},{tool:"grok",capable:o.hasGrok,skipMsg:s.t("setup.skip_no_grok")},{tool:"claude-desktop",capable:o.hasClaude&&a.isToolInstalled("claude-desktop"),skipMsg:s.t("setup.skip_desktop_not_installed"),quiet:!o.hasClaude}];for(const o of t)M.has(o.tool)||(o.capable?await b(o.tool,n,e.oneMillion,w,e.force)?(j+=1,M.add(o.tool)):G+=1:o.quiet||y(o.skipMsg))}if(j>0){const e=[...M].filter(e=>!o(e)?.isInstalled).map(e=>i[e]?.command).filter(Boolean).join(" / "),t=s.t("setup.done",{count:String(j)});g("\n"+(e?`${t}${s.t("setup.done_commands",{commands:e})}`:t))}G>0&&k(s.t("setup.failed_summary",{count:String(G)})),await t([...M]),(G>0||0===j)&&(process.exitCode=1)}
@@ -0,0 +1,2 @@
1
+ /** `vibecli upgrade [tool...]`: no argument = every installed tool vibecli knows how to upgrade. */
2
+ export declare function upgradeCommand(tools: string[]): Promise<void>;
@@ -0,0 +1 @@
1
+ import o from"chalk";import{i18n as r}from"../lib/i18n.js";import{toolManager as t}from"../lib/tool-manager.js";import{listAdapters as e}from"../lib/tools/registry.js";import{upgradeTool as i}from"../lib/tool-upgrade.js";export async function upgradeCommand(l){const n=e().filter(o=>o.installSpec).map(o=>o.name),s=l.length>0?l:n.filter(o=>t.isToolInstalled(o)),p=s.filter(o=>!n.includes(o));if(p.length>0)return console.error(o.red(r.t("upgrade.unsupported",{tool:p.join(", ")}))),console.error(r.t("upgrade.supported",{tools:n.join(" / ")})),void(process.exitCode=1);if(0===s.length)return void console.log(r.t("upgrade.nothing_installed",{tools:n.join(" / ")}));let a=0;for(const o of s)await i(o)||(a+=1);a>0&&(process.exitCode=1)}
@@ -6,11 +6,10 @@ import { type GatewayModelSpec } from './model-registry.js';
6
6
  */
7
7
  export declare const MODEL_ENV_VARS: readonly ["ANTHROPIC_DEFAULT_HAIKU_MODEL", "ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_DEFAULT_OPUS_MODEL", "ANTHROPIC_DEFAULT_FABLE_MODEL", "ANTHROPIC_MODEL", "ANTHROPIC_REASONING_MODEL"];
8
8
  /**
9
- * Add the gateway's other models (domestic models, …) to Claude Code's `/model`
10
- * menu: `modelPicker` rows after the built-in lineup, `modelSettings.<id>.autoCompactWindow`
11
- * at 80% of each window, and `CLAUDE_CODE_MAX_CONTEXT_TOKENS` — Claude Code's only
12
- * window setting for non-Claude ids, one value for all of them — at the smallest
13
- * listed window. `models` undefined (offline) keeps what is there; empty removes ours.
9
+ * Add the gateway's other models (GPT, Gemini, Grok, domestic …) to Claude Code's
10
+ * `/model` menu: `modelPicker` rows after the built-in lineup (`<id>[1m]` for 1M
11
+ * models), `modelSettings.<id>.autoCompactWindow` at 80% of each window, and
12
+ * `CLAUDE_CODE_MAX_CONTEXT_TOKENS` at the smallest window among the models below 1M. `models` undefined (offline) keeps what is there; empty removes ours.
14
13
  */
15
14
  export declare function applyGatewayModels(settings: ClaudeCodeSettingsConfig, models: GatewayModelSpec[] | undefined): ClaudeCodeSettingsConfig;
16
15
  export interface TierFallback {
@@ -1 +1 @@
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 m}from"./settings-template.js";import{cleanupAnthropicEnvVars as C}from"./env-cleanup.js";import{getClaudeCodeTierChains as f,getGatewayModelRules as O}from"./model-registry.js";import{autoCompactTokens as h,prettyModelName as u}from"./gateway-models.js";import{baseModelId as _}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 p={opus:"ANTHROPIC_DEFAULT_OPUS_MODEL",sonnet:"ANTHROPIC_DEFAULT_SONNET_MODEL",haiku:"ANTHROPIC_DEFAULT_HAIKU_MODEL",fable:"ANTHROPIC_DEFAULT_FABLE_MODEL"},E="CLAUDE_CODE_MAX_CONTEXT_TOKENS";function T(e,t=[]){if(!e||"object"!=typeof e)return!1;const{options:n,replaceBuiltInOptions:o}=e;if(!Array.isArray(n)||o)return!1;const s=new Set([...O().models.map(e=>e.id),...t]);return n.every(e=>e&&"object"==typeof e&&s.has(e.model))}export function applyGatewayModels(e,t){if(void 0===t)return e;const n={...e};if(void 0!==n.modelPicker&&!T(n.modelPicker,t.map(e=>e.id)))return n;const o=T(n.modelPicker)?n.modelPicker.options.map(e=>e.model):[],s={...n.env||{}},i={...n.modelSettings||{}};for(const e of o){if(!i[e])continue;const{autoCompactWindow:t,...n}=i[e];Object.keys(n).length>0?i[e]=n:delete i[e]}const r=t.map(e=>e.contextWindow).filter(e=>"number"==typeof e&&e>0);if(0===t.length)delete n.modelPicker,delete s[E];else{n.modelPicker={options:t.map(e=>({model:e.id,label:e.name||u(e.id),...e.description?{description:e.description}:{}}))},r.length>0&&(s[E]=String(Math.min(...r)));for(const e of t){if(!e.contextWindow)continue;const t=h(e.contextWindow,e.autoCompactTokens);i[e.id]={...i[e.id],autoCompactWindow:Math.min(Math.max(t,1e5),1e6)}}}return Object.keys(i).length>0?n.modelSettings=i:delete n.modelSettings,Object.keys(s).length>0?n.env=s:delete n.env,n}export function resolveTierFallbacks(e){if(!e||0===e.length)return[];const t=new Set(e.map(_)),n=[];for(const[e,o]of Object.entries(f())){const s=p[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=[];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(),C();const s=m(),i=s.env||{},{env:r,...a}=s,l=this.getSettings(),{ANTHROPIC_API_KEY:c,...f}=l.env||{},O={...f};for(const e of MODEL_ENV_VARS)delete O[e];const h={},u=e=>{const n=(t[e]||"").trim();return n&&n!==g[e]?n:null},_=u("haiku"),p=u("sonnet"),E=u("opus"),T=new Set([_&&"haiku",p&&"sonnet",E&&"opus"].filter(Boolean));this.lastTierFallbacks=resolveTierFallbacks(n).filter(e=>!T.has(e.tier));for(const e of this.lastTierFallbacks)h[e.envVar]=e.to;_&&(h.ANTHROPIC_DEFAULT_HAIKU_MODEL=_),p&&(h.ANTHROPIC_DEFAULT_SONNET_MODEL=p),E&&(h.ANTHROPIC_DEFAULT_OPUS_MODEL=E,h.ANTHROPIC_MODEL=E);const A={...l};for(const[e,t]of Object.entries(a))e in A||(A[e]=t);A.showThinkingSummaries=!0;const P={...A,env:{...O,...Object.fromEntries(Object.entries(i).filter(([e])=>!(e in O))),ANTHROPIC_AUTH_TOKEN:e,ANTHROPIC_BASE_URL:d.getBaseUrl(),...h}};E?P.model=E:Object.prototype.hasOwnProperty.call(a,"model")?P.model=a.model:delete P.model,this.saveSettings(applyGatewayModels(P,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();if(!e.env){if(void 0!==e.model){const{model:t,...n}=e;this.saveSettings(n)}return}const{ANTHROPIC_AUTH_TOKEN:t,ANTHROPIC_BASE_URL:n,API_TIMEOUT_MS:o,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:s,CLAUDE_CODE_AUTO_MODE_SERVER:i,...r}=e.env,a={...r};for(const e of MODEL_ENV_VARS)delete a[e];const l={...applyGatewayModels({...e,env:a},[])};l.env=l.env||{},delete l.model,l.env&&0===Object.keys(l.env).length&&delete l.env,this.saveSettings(l)}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}return this.saveSettings(r),s}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();
1
+ import{existsSync as e,mkdirSync as t,readFileSync as n,writeFileSync as o,renameSync as i,unlinkSync as r}from"fs";import{join as s,dirname as a}from"path";import{toolHome as c}from"./tools/types.js";import{logger as l}from"../utils/logger.js";import{configManager as d,DEFAULT_MODELS as g}from"./config.js";import{loadSettingsTemplate as f}from"./settings-template.js";import{cleanupAnthropicEnvVars as m}from"./env-cleanup.js";import{getClaudeCodeTierChains as u,getGatewayModelRules as C}from"./model-registry.js";import{autoCompactTokens as O,prettyModelName as h}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 _={opus:"ANTHROPIC_DEFAULT_OPUS_MODEL",sonnet:"ANTHROPIC_DEFAULT_SONNET_MODEL",haiku:"ANTHROPIC_DEFAULT_HAIKU_MODEL",fable:"ANTHROPIC_DEFAULT_FABLE_MODEL"},E="CLAUDE_CODE_MAX_CONTEXT_TOKENS";function A(){return s(d.getConfigDir(),"claude-code-picker.json")}function T(){try{const e=JSON.parse(n(A(),"utf-8")).ids;return Array.isArray(e)?e.map(String):[]}catch{return[]}}function P(e,t=[]){if(!e||"object"!=typeof e)return!1;const{options:n,replaceBuiltInOptions:o}=e;if(!Array.isArray(n)||o)return!1;const i=C(),r=new Set([...i.models.map(e=>e.id),...i.retired,...T(),...t]);return n.every(e=>e&&"object"==typeof e&&r.has(p(String(e.model))))}export function applyGatewayModels(e,n){if(void 0===n)return e;const i={...e};if(void 0!==i.modelPicker&&!P(i.modelPicker,n.map(e=>e.id)))return i;const r=P(i.modelPicker)?i.modelPicker.options.map(e=>p(e.model)):[],s={...i.env||{}},a={...i.modelSettings||{}};for(const e of r){if(!a[e])continue;const{autoCompactWindow:t,...n}=a[e];Object.keys(n).length>0?a[e]=n:delete a[e]}const c=e=>(e.contextWindow??0)>=1e6,g=n.filter(e=>!c(e)).map(e=>e.contextWindow).filter(e=>"number"==typeof e&&e>0);if(function(e){try{t(d.getConfigDir(),{recursive:!0}),o(A(),JSON.stringify({ids:e})+"\n")}catch(e){l.logError("claudeCode.recordWrittenPickerIds",e)}}(n.map(e=>e.id)),0===n.length)delete i.modelPicker,delete s[E];else{i.modelPicker={options:n.map(e=>({model:c(e)?`${e.id}[1m]`:e.id,label:e.name||h(e.id),...e.description?{description:e.description}:{}}))},g.length>0?s[E]=String(Math.min(...g)):delete s[E];for(const e of n){if(!e.contextWindow)continue;const t=O(e.contextWindow,e.autoCompactTokens);a[e.id]={...a[e.id],autoCompactWindow:Math.min(Math.max(t,1e5),1e6)}}}return Object.keys(a).length>0?i.modelSettings=a:delete i.modelSettings,Object.keys(s).length>0?i.env=s:delete i.env,i}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(u())){const i=_[e],[r,...s]=o;if(!i||!r||t.has(r))continue;const a=s.find(e=>t.has(e));a&&n.push({tier:e,envVar:i,from:r,to:a})}return n}export class ClaudeCodeManager{static instance;settingsPath;mcpConfigPath;lastTierFallbacks=[];constructor(){this.settingsPath=s(c(),".claude","settings.json"),this.mcpConfigPath=s(c(),".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 s=`${t}.tmp`;try{o(s,n,"utf-8"),i(s,t)}catch(t){if(e(s))try{r(s)}catch{}throw t}}getSettings(){try{if(e(this.settingsPath)){const e=n(this.settingsPath,"utf-8");return JSON.parse(e)}}catch(e){l.warn("Failed to read Claude Code settings:",e),l.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){l.warn("Failed to read Claude Code MCP config:",e),l.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(),m();const i=f(),r=i.env||{},{env:s,...a}=i,c=this.getSettings(),{ANTHROPIC_API_KEY:l,...u}=c.env||{},C={...u};for(const e of MODEL_ENV_VARS)delete C[e];const O={},h=e=>{const n=(t[e]||"").trim();return n&&n!==g[e]?n:null},p=h("haiku"),_=h("sonnet"),E=h("opus"),A=new Set([p&&"haiku",_&&"sonnet",E&&"opus"].filter(Boolean));this.lastTierFallbacks=resolveTierFallbacks(n).filter(e=>!A.has(e.tier));for(const e of this.lastTierFallbacks)O[e.envVar]=e.to;p&&(O.ANTHROPIC_DEFAULT_HAIKU_MODEL=p),_&&(O.ANTHROPIC_DEFAULT_SONNET_MODEL=_),E&&(O.ANTHROPIC_DEFAULT_OPUS_MODEL=E,O.ANTHROPIC_MODEL=E);const T={...c};for(const[e,t]of Object.entries(a))e in T||(T[e]=t);T.showThinkingSummaries=!0;const P={...T,env:{...C,...Object.fromEntries(Object.entries(r).filter(([e])=>!(e in C))),ANTHROPIC_AUTH_TOKEN:e,ANTHROPIC_BASE_URL:d.getBaseUrl(),...O}};E?P.model=E:Object.prototype.hasOwnProperty.call(a,"model")?P.model=a.model:delete P.model,this.saveSettings(applyGatewayModels(P,o))}ensureOnboardingCompleted(){try{const e=this.getMCPConfig();e.hasCompletedOnboarding||this.saveMCPConfig({...e,hasCompletedOnboarding:!0})}catch(e){l.warn("Failed to ensure onboarding completed:",e),l.logError("ClaudeCodeManager.ensureOnboardingCompleted",e)}}unloadPrivateConfig(){const e=this.getSettings();if(!e.env){if(void 0!==e.model){const{model:t,...n}=e;this.saveSettings(n)}return}const{ANTHROPIC_AUTH_TOKEN:t,ANTHROPIC_BASE_URL:n,API_TIMEOUT_MS:o,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:i,CLAUDE_CODE_AUTO_MODE_SERVER:r,...s}=e.env,a={...s};for(const e of MODEL_ENV_VARS)delete a[e];const c={...applyGatewayModels({...e,env:a},[])};c.env=c.env||{},delete c.model,c.env&&0===Object.keys(c.env).length&&delete c.env,this.saveSettings(c)}clearPinnedModels(e){const t=this.getSettings(),n=t.env,o=n?.ANTHROPIC_AUTH_TOKEN?Object.fromEntries(resolveTierFallbacks(e).map(e=>[e.envVar,e.to])):{},i=[];for(const e of MODEL_ENV_VARS)n&&e in n&&n[e]!==o[e]&&i.push(e);void 0!==t.model&&i.push("model");const r=Object.entries(o).some(([e,t])=>n?.[e]!==t);if(0===i.length&&!r)return[];const s={...t};if(delete s.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 s.env:s.env=e}return this.saveSettings(s),i}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||{},i={...n,env:{...o,[e]:t}};this.saveSettings(i)}removeExperimentalFeature(e){const t=this.getSettings();if(!t.env)return;const{[e]:n,...o}=t.env,i={...t,env:o};this.saveSettings(i)}}export const claudeCodeManager=ClaudeCodeManager.getInstance();
@@ -1 +1 @@
1
- import{Command as e}from"commander";import{i18n as o,detectSystemLang as a}from"./i18n.js";import{configManager as t}from"./config.js";import{wizard as i}from"./wizard.js";import{langCommand as n,authCommand as s,doctorCommand as r,configCommand as c,statusCommand as m}from"../commands/index.js";import{logsCommand as l}from"../commands/logs.js";import{completionCommand as p}from"../commands/completion.js";import{benchCommand as d}from"../commands/bench.js";import{updateCommand as g}from"../commands/update.js";import{setupCommand as u}from"../commands/setup.js";import f from"chalk";import{readFileSync as h}from"fs";import{join as y,dirname as k}from"path";import{fileURLToPath as b}from"url";import{setRuntimeOptions as v,detectColorPreferenceFromEnv as w,isColorEnabled as $,isJsonMode as x}from"./runtime-options.js";import{cleanupAnthropicEnvVars as C}from"./env-cleanup.js";import{runStartupUpdate as j}from"./auto-update.js";export class Command{program;constructor(){const i=t.isFirstRun()?a():t.getLang();o.loadFromConfig(i),this.program=new e,this.setupProgram()}getVersion(){try{const e=b(import.meta.url),o=k(e),a=y(o,"../../package.json");return JSON.parse(h(a,"utf-8")).version}catch{return"1.0.0"}}setupProgram(){this.program.name("vibecli").description(o.t("cli.title")).version(this.getVersion(),"-v, --version",o.t("commands.version")).helpOption("-h, --help",o.t("commands.help")).option("--base <url>","Custom API base URL").option("--non-interactive","Disable interactive prompts (CI mode)").option("--json","Emit machine-readable JSON output where applicable").option("--yes, -y","Auto-confirm prompts that have a default action").option("--dry-run","Show planned changes without writing anywhere").option("--no-color","Disable colored output"),this.program.command("setup").description(o.t("commands.setup")).option("--key <key>","API key for all capable tools (capability auto-detected)").option("--claudekey <key>","Dedicated API key for Claude Code").option("--codexkey <key>","Dedicated API key for Codex").option("--grokkey <key>","Dedicated API key for Grok Build").option("--desktopkey <key>","Dedicated API key for Claude Desktop (3p mode)").option("--1m","Enable the 1M context window for Codex").option("--no-1m","Disable the 1M context window for Codex").option("--claude-official <on|off>","Claude Desktop: show (on) or hide (off, default) the official Claude.ai sign-in entry").option("--force","Replace each tool's config files wholesale (snapshotted first) instead of merging — repairs hand-broken configs").option("--all","Configure every tool the key can serve (default behavior)").action(async e=>{await u({key:e.key,claudekey:e.claudekey,codexkey:e.codexkey,grokkey:e.grokkey,desktopkey:e.desktopkey,all:e.all,oneMillion:e["1m"],claudeOfficial:e.claudeOfficial,force:e.force})}),this.program.command("init").description(o.t("commands.init")).action(async()=>{await this.handleInitCommand()});const e=this.program.command("lang").description(o.t("commands.lang"));e.command("show").description(o.t("lang.show_usage")).action(async()=>{await n(["show"])}),e.command("set <locale>").description(o.t("lang.set_usage")).action(async e=>{await n(["set",e])});const a=this.program.command("auth").description(o.t("commands.auth"));a.argument("[token]","API token").action(async e=>{const o=[];e&&o.push(e),await s(o)}),a.command("revoke").description("Revoke saved API key").action(async()=>{await s(["revoke"])}),a.command("reload <tool>").description("Reload plan configuration to the specified tool (e.g., claude)").action(async e=>{await s(["reload",e])}),this.program.command("doctor").description(o.t("commands.doctor")).action(async()=>{await r()}),this.program.command("status").description(o.t("commands.status")).action(async()=>{await m()}),this.program.command("logs").description("Inspect vibecli log files").argument("[action]","list|tail|path").argument("[file]","specific log file when using tail").action(async(e,o)=>{const a=[];e&&a.push(e),o&&a.push(o),await l(a)}),this.program.command("completion <shell>").description("Print shell completion script (bash|zsh|fish)").action(async e=>{await p([e])}),this.program.command("update").description("Check for a newer version across CNB and npm mirrors").action(async()=>{await g()}),this.program.command("bench").description("Benchmark the configured API endpoint").option("--samples <n>","number of samples to send","5").action(async e=>{const o=[];e.samples&&o.push("--samples",e.samples),await d(o)}),this.program.command("config").description("Manage profiles, presets, backups, and per-tool config").argument("[args...]","Subcommand and arguments").allowUnknownOption(!0).action(async e=>{await c(e||[])}),this.program.command("enter [option]").description(o.t("commands.enter")).action(async e=>{if(e)switch(e){case"lang":case"language":await i.configLanguage();break;case"apikey":case"api-key":await i.configApiKey();break;case"models":await i.configModels();break;default:{const o=[e];await c(o);break}}else await i.run()}),this.program.action(async()=>{await i.run()}),this.program.configureHelp({sortSubcommands:!0,subcommandTerm:e=>e.name()+" "+e.usage()}),this.program.addHelpText("after",`\n${f.bold(o.t("cli.examples"))}:\n ${f.gray("$ vibecli # Interactive main menu")}\n ${f.gray("$ vibecli init # Run first-time setup wizard")}\n ${f.gray("$ vibecli setup --key sk-xxx # One-shot setup (auto-detect capable tools)")}\n ${f.gray("$ vibecli setup --claudekey sk-a --codexkey sk-b --grokkey sk-c")}\n ${f.gray("$ vibecli setup --desktopkey sk-d --claude-official off # Claude Desktop, hide Claude.ai login")}\n ${f.gray("$ vibecli setup --key sk-xxx --force # Replace broken tool configs with the recommended ones")}\n ${f.gray("$ vibecli config codex --force # Same, for one tool")}\n ${f.gray("$ vibecli status --json # Machine-readable status")}\n ${f.gray("$ vibecli config profile list")}\n ${f.gray("$ vibecli config profile create work")}\n ${f.gray("$ vibecli config profile use work")}\n ${f.gray("$ vibecli config preset save fast")}\n ${f.gray("$ vibecli config backup create snapshot")}\n ${f.gray("$ vibecli auth <token> --non-interactive")}\n ${f.gray("$ vibecli auth revoke --yes")}\n ${f.gray("$ vibecli logs path")}\n ${f.gray("$ vibecli completion bash")}\n ${f.gray("$ vibecli bench --samples 3")}\n ${f.gray("$ vibecli update")}\n ${f.gray('$ vibecli --base "https://custom.api.com" init # Custom base URL')}\n`)}async handleInitCommand(){await i.runFirstTimeSetup()}cleanupLeakedEnvVars(){const e=C();if(0===e.length||x())return;const a=o.t("messages.env_vars_cleared",{vars:e.join(", ")});console.log($()?f.yellow(a):a)}preflightArgs(e){const o=o=>e.includes(o),a=!o("--no-color")&&w();let i;v({nonInteractive:o("--non-interactive"),json:o("--json"),yes:o("--yes")||o("-y"),dryRun:o("--dry-run"),color:a}),a||(f.level=0);for(let o=0;o<e.length;o+=1){const a=e[o];if("--base"===a&&e[o+1]){i=e[o+1];break}if(a.startsWith("--base=")){i=a.slice(7);break}}i&&t.setBaseUrl(i)}async execute(e){try{if(this.preflightArgs(e),this.cleanupLeakedEnvVars(),(await j(e)).handled)return;await this.program.parseAsync(e,{from:"user"})}catch(e){if(e instanceof Error){const a=$()?f.red:e=>e;console.error(a(o.t("cli.error_general")),e.message)}process.exit(1)}}getProgram(){return this.program}}
1
+ import{Command as e}from"commander";import{i18n as o,detectSystemLang as a}from"./i18n.js";import{configManager as t}from"./config.js";import{wizard as i}from"./wizard.js";import{langCommand as n,authCommand as s,doctorCommand as r,configCommand as c,statusCommand as m}from"../commands/index.js";import{logsCommand as l}from"../commands/logs.js";import{completionCommand as p}from"../commands/completion.js";import{benchCommand as d}from"../commands/bench.js";import{updateCommand as g}from"../commands/update.js";import{runModelRefresh as u}from"./model-refresh.js";import{upgradeCommand as f}from"../commands/upgrade.js";import{setupCommand as h}from"../commands/setup.js";import y from"chalk";import{readFileSync as k}from"fs";import{join as b,dirname as v}from"path";import{fileURLToPath as w}from"url";import{setRuntimeOptions as $,detectColorPreferenceFromEnv as C,isColorEnabled as x,isJsonMode as j}from"./runtime-options.js";import{cleanupAnthropicEnvVars as A,machineEnvVarsLeft as I}from"./env-cleanup.js";import{runStartupUpdate as P}from"./auto-update.js";export class Command{program;constructor(){const i=t.isLangChosen()?t.getLang():a();o.loadFromConfig(i),this.program=new e,this.setupProgram()}getVersion(){try{const e=w(import.meta.url),o=v(e),a=b(o,"../../package.json");return JSON.parse(k(a,"utf-8")).version}catch{return"1.0.0"}}setupProgram(){this.program.name("vibecli").description(o.t("cli.title")).version(this.getVersion(),"-v, --version",o.t("commands.version")).helpOption("-h, --help",o.t("commands.help")).option("--base <url>","Custom API base URL").option("--non-interactive","Disable interactive prompts (CI mode)").option("--json","Emit machine-readable JSON output where applicable").option("--yes, -y","Auto-confirm prompts that have a default action").option("--dry-run","Show planned changes without writing anywhere").option("--no-color","Disable colored output"),this.program.command("setup").description(o.t("commands.setup")).option("--key <key>","API key for all capable tools (capability auto-detected)").option("--claudekey <key>","Dedicated API key for Claude Code").option("--codexkey <key>","Dedicated API key for Codex").option("--grokkey <key>","Dedicated API key for Grok Build").option("--desktopkey <key>","Dedicated API key for Claude Desktop (3p mode)").option("--1m","Enable the 1M context window for Codex").option("--no-1m","Disable the 1M context window for Codex").option("--claude-official <on|off>","Claude Desktop: show (on) or hide (off, default) the official Claude.ai sign-in entry").option("--force","Replace each tool's config files wholesale (snapshotted first) instead of merging — repairs hand-broken configs").option("--all","Configure every tool the key can serve (default behavior)").action(async e=>{await h({key:e.key,claudekey:e.claudekey,codexkey:e.codexkey,grokkey:e.grokkey,desktopkey:e.desktopkey,all:e.all,oneMillion:e["1m"],claudeOfficial:e.claudeOfficial,force:e.force})}),this.program.command("init").description(o.t("commands.init")).action(async()=>{await this.handleInitCommand()});const e=this.program.command("lang").description(o.t("commands.lang"));e.command("show").description(o.t("lang.show_usage")).action(async()=>{await n(["show"])}),e.command("set <locale>").description(o.t("lang.set_usage")).action(async e=>{await n(["set",e])});const a=this.program.command("auth").description(o.t("commands.auth"));a.argument("[token]","API token").action(async e=>{const o=[];e&&o.push(e),await s(o)}),a.command("revoke").description("Revoke saved API key").action(async()=>{await s(["revoke"])}),a.command("reload <tool>").description("Reload plan configuration to the specified tool (e.g., claude)").action(async e=>{await s(["reload",e])}),this.program.command("doctor").description(o.t("commands.doctor")).option("--deep","Also send one real Claude Code request through the written config").action(async e=>{await r({deep:e.deep})}),this.program.command("status").description(o.t("commands.status")).action(async()=>{await m()}),this.program.command("logs").description("Inspect vibecli log files").argument("[action]","list|tail|path").argument("[file]","specific log file when using tail").action(async(e,o)=>{const a=[];e&&a.push(e),o&&a.push(o),await l(a)}),this.program.command("completion <shell>").description("Print shell completion script (bash|zsh|fish)").action(async e=>{await p([e])}),this.program.command("update").description("Check for a newer version across CNB and npm mirrors").action(async()=>{await g()}),this.program.command("upgrade [tools...]").description(o.t("commands.upgrade")).action(async(e=[])=>{await f(e)}),this.program.command("bench").description("Benchmark the configured API endpoint").option("--samples <n>","number of samples to send","5").action(async e=>{const o=[];e.samples&&o.push("--samples",e.samples),await d(o)}),this.program.command("config").description("Manage profiles, presets, backups, and per-tool config").argument("[args...]","Subcommand and arguments").allowUnknownOption(!0).action(async e=>{await c(e||[])}),this.program.command("enter [option]").description(o.t("commands.enter")).action(async e=>{if(e)switch(e){case"lang":case"language":await i.configLanguage();break;case"apikey":case"api-key":await i.configApiKey();break;case"models":await i.configModels();break;default:{const o=[e];await c(o);break}}else await i.run()}),this.program.action(async()=>{await i.run()}),this.program.configureHelp({sortSubcommands:!0,subcommandTerm:e=>e.name()+" "+e.usage()}),this.program.addHelpText("after",`\n${y.bold(o.t("cli.examples"))}:\n ${y.gray("$ vibecli # Interactive main menu")}\n ${y.gray("$ vibecli init # Run first-time setup wizard")}\n ${y.gray("$ vibecli setup --key sk-xxx # One-shot setup (auto-detect capable tools)")}\n ${y.gray("$ vibecli setup --claudekey sk-a --codexkey sk-b --grokkey sk-c")}\n ${y.gray("$ vibecli setup --desktopkey sk-d --claude-official off # Claude Desktop, hide Claude.ai login")}\n ${y.gray("$ vibecli setup --key sk-xxx --force # Replace broken tool configs with the recommended ones")}\n ${y.gray("$ vibecli config codex --force # Same, for one tool")}\n ${y.gray("$ vibecli status --json # Machine-readable status")}\n ${y.gray("$ vibecli config profile list")}\n ${y.gray("$ vibecli config profile create work")}\n ${y.gray("$ vibecli config profile use work")}\n ${y.gray("$ vibecli config preset save fast")}\n ${y.gray("$ vibecli config backup create snapshot")}\n ${y.gray("$ vibecli auth <token> --non-interactive")}\n ${y.gray("$ vibecli auth revoke --yes")}\n ${y.gray("$ vibecli logs path")}\n ${y.gray("$ vibecli completion bash")}\n ${y.gray("$ vibecli bench --samples 3")}\n ${y.gray("$ vibecli update")}\n ${y.gray("$ vibecli upgrade claude-code")}\n ${y.gray('$ vibecli --base "https://custom.api.com" init # Custom base URL')}\n`)}async handleInitCommand(){await i.runFirstTimeSetup()}cleanupLeakedEnvVars(){const e=A();if(0===e.length||j())return;const a=o.t("messages.env_vars_cleared",{vars:e.join(", ")});console.log(x()?y.yellow(a):a);const t=I();if(t.length>0){const e=o.t("messages.env_vars_machine_left",{vars:t.join(", ")});console.log(x()?y.yellow(e):e)}}preflightArgs(e){const o=o=>e.includes(o),a=!o("--no-color")&&C();let i;$({nonInteractive:o("--non-interactive"),json:o("--json"),yes:o("--yes")||o("-y"),dryRun:o("--dry-run"),color:a}),a||(y.level=0);for(let o=0;o<e.length;o+=1){const a=e[o];if("--base"===a&&e[o+1]){i=e[o+1];break}if(a.startsWith("--base=")){i=a.slice(7);break}}i&&t.setBaseUrl(i)}async execute(e){try{if(this.preflightArgs(e),this.cleanupLeakedEnvVars(),(await P(e)).handled)return;await u(e),await this.program.parseAsync(e,{from:"user"})}catch(e){if(e instanceof Error){const a=x()?y.red:e=>e;console.error(a(o.t("cli.error_general")),e.message)}process.exit(1)}}getProgram(){return this.program}}
@@ -33,6 +33,8 @@ export interface ConfigBackupInfo {
33
33
  }
34
34
  export interface VibecliConfig {
35
35
  lang: string;
36
+ /** Set once the user picked a language (`vibecli lang set`, the wizard); until then the system locale decides. */
37
+ lang_chosen?: boolean;
36
38
  credential_store?: 'plaintext';
37
39
  active_profile?: string;
38
40
  profiles?: Record<string, VibecliProfile>;
@@ -86,6 +88,8 @@ export declare class ConfigManager {
86
88
  updateConfig(updates: Partial<VibecliConfig>): void;
87
89
  isFirstRun(): boolean;
88
90
  getLang(): string;
91
+ /** Whether the stored language is the user's choice rather than the `en_US` every new config starts with. */
92
+ isLangChosen(): boolean;
89
93
  setLang(lang: string): void;
90
94
  getActiveProfileName(): string;
91
95
  listProfiles(): string[];
@@ -126,6 +130,8 @@ export declare class ConfigManager {
126
130
  listBackups(): ConfigBackupInfo[];
127
131
  createBackup(label?: string): string;
128
132
  restoreBackup(name: string): void;
133
+ /** vibecli's own directory (`~/.vibecli`, or the test / VIBECLI_CONFIG_DIR override). */
134
+ getConfigDir(): string;
129
135
  getBaseUrl(): string;
130
136
  setBaseUrl(url: string, persist?: boolean): void;
131
137
  resetBaseUrl(): void;
@@ -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 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 y,isRetiredDefaultModel as d}from"./model-registry.js";import{readLegacySecretsFile as _}from"./secret-store.js";export const DEFAULT_BASE_URL="https://www.vibeapi.cn";export const DEFAULT_MODELS=y();export const DEFAULT_PROFILE_NAME="default";const k="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=process.env.VIBECLI_CONFIG_DIR;if(e&&e.trim())return e.trim();const t=process.env.VIBECLI_TEST_HOME;return t&&t.trim()?f(t.trim(),".vibecli"):f(h(),".vibecli")}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&&!d(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!==k)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",...o.credential_store===k?{credential_store:k}:{},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:k};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"}setLang(e){this.updateConfig({lang:e})}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!==k&&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)}getBaseUrl(){return this.runtimeBaseUrl||this.config.profiles?.[this.getActiveProfileName()]?.base_url||DEFAULT_BASE_URL}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 y,isRetiredDefaultModel as d}from"./model-registry.js";import{readLegacySecretsFile as _}from"./secret-store.js";export const DEFAULT_BASE_URL="https://www.vibeapi.cn";export const DEFAULT_MODELS=y();export const DEFAULT_PROFILE_NAME="default";const k="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=process.env.VIBECLI_CONFIG_DIR;if(e&&e.trim())return e.trim();const t=process.env.VIBECLI_TEST_HOME;return t&&t.trim()?f(t.trim(),".vibecli"):f(h(),".vibecli")}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&&!d(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!==k)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===k?{credential_store:k}:{},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:k};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!==k&&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}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();
@@ -14,6 +14,16 @@ export declare function getShellRcFilePath(): string | null;
14
14
  * 返回 rc 文件是否被修改。
15
15
  */
16
16
  export declare function cleanupShellRcEnvVars(): boolean;
17
+ /** 测试注入假的 PowerShell。 */
18
+ export declare function setPowerShellRunnerForTests(fake: (script: string) => string): void;
19
+ /**
20
+ * Windows 上这些变量的持久来源是「用户环境变量」「系统环境变量」和 PowerShell 配置文件里的
21
+ * `$env:NAME = ...`。用户级用 .NET 接口删除(会广播变更,新开的终端立即生效,不需要管理员);
22
+ * 系统级需要管理员,删不掉的名字返回给调用方提示用户。
23
+ */
24
+ export declare function cleanupWindowsEnvVars(names: readonly string[]): {
25
+ machineLeft: string[];
26
+ };
17
27
  /**
18
28
  * 检测并清除环境中的 ANTHROPIC_API_KEY / ANTHROPIC_BASE_URL:
19
29
  * 1. 从当前 shell 的 rc 文件删掉对应的 export 行(持久来源);
@@ -22,3 +32,5 @@ export declare function cleanupShellRcEnvVars(): boolean;
22
32
  * 返回被清除的变量名(未检测到时为空数组)。
23
33
  */
24
34
  export declare function cleanupAnthropicEnvVars(): string[];
35
+ /** Windows 系统级环境变量里删不掉(需要管理员)的名字,来自最近一次 `cleanupAnthropicEnvVars()`。 */
36
+ export declare function machineEnvVarsLeft(): string[];
@@ -1 +1 @@
1
- import{existsSync as e,readFileSync as r,writeFileSync as n}from"fs";import{join as t}from"path";import{logger as o}from"../utils/logger.js";import{toolHome as s}from"./tools/types.js";export const ANTHROPIC_ENV_VARS=["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"];export function getShellRcFilePath(){if("win32"===process.platform)return null;const e=s();switch((process.env.SHELL||"").split("/").pop()||""){case"bash":return t(e,".bashrc");case"zsh":return t(e,".zshrc");case"fish":return t(e,".config","fish","config.fish");default:return t(e,".profile")}}export function cleanupShellRcEnvVars(){if("win32"===process.platform)return!1;try{const t=getShellRcFilePath();if(!t||!e(t))return!1;let s=r(t,"utf-8");const l=s,c=[...ANTHROPIC_ENV_VARS.map(e=>new RegExp(String.raw`^\s*export\s+${e}=.*$`,"gm")),/^\s*#\s*Claude Code environment variables\s*$/gm];for(const e of c)s=s.replace(e,"");if(s!==l)return n(t,s,"utf-8"),o.info(`Cleaned up ANTHROPIC_* environment variables from ${t}`),!0}catch(e){o.warn("Failed to cleanup shell environment variables:",e),o.logError("envCleanup.cleanupShellRcEnvVars",e)}return!1}export function cleanupAnthropicEnvVars(){const e=ANTHROPIC_ENV_VARS.filter(e=>!!process.env[e]);if(0===e.length)return[];cleanupShellRcEnvVars();for(const r of e)delete process.env[r];return[...e]}
1
+ import{execFileSync as e}from"child_process";import{existsSync as n,readFileSync as r,writeFileSync as o}from"fs";import{join as t}from"path";import{logger as i}from"../utils/logger.js";import{toolHome as s}from"./tools/types.js";export const ANTHROPIC_ENV_VARS=["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"];export function getShellRcFilePath(){if("win32"===process.platform)return null;const e=s();switch((process.env.SHELL||"").split("/").pop()||""){case"bash":return t(e,".bashrc");case"zsh":return t(e,".zshrc");case"fish":return t(e,".config","fish","config.fish");default:return t(e,".profile")}}export function cleanupShellRcEnvVars(){if("win32"===process.platform)return!1;try{const e=getShellRcFilePath();if(!e||!n(e))return!1;let t=r(e,"utf-8");const s=t,l=[...ANTHROPIC_ENV_VARS.map(e=>new RegExp(String.raw`^\s*export\s+${e}=.*$`,"gm")),/^\s*#\s*Claude Code environment variables\s*$/gm];for(const e of l)t=t.replace(e,"");if(t!==s)return o(e,t,"utf-8"),i.info(`Cleaned up ANTHROPIC_* environment variables from ${e}`),!0}catch(e){i.warn("Failed to cleanup shell environment variables:",e),i.logError("envCleanup.cleanupShellRcEnvVars",e)}return!1}let l=n=>e("powershell.exe",["-NoProfile","-NonInteractive","-Command",n],{encoding:"utf-8",windowsHide:!0,timeout:2e4});export function setPowerShellRunnerForTests(e){l=e}export function cleanupWindowsEnvVars(e){const c=[];if("win32"!==process.platform||0===e.length)return{machineLeft:c};try{const n=e.map(e=>`'${e}'`).join(","),r=l(`foreach($n in @(${n})){if($null -ne [Environment]::GetEnvironmentVariable($n,'User')){[Environment]::SetEnvironmentVariable($n,$null,'User');"user:$n"};if($null -ne [Environment]::GetEnvironmentVariable($n,'Machine')){try{[Environment]::SetEnvironmentVariable($n,$null,'Machine');"machine-removed:$n"}catch{"machine:$n"}}}`);for(const e of r.split(/\r?\n/)){const n=e.trim().match(/^machine:(\w+)$/);n&&c.push(n[1])}i.info(`Windows env cleanup: ${r.replace(/\s+/g," ").trim()}`)}catch(e){i.logError("envCleanup.cleanupWindowsEnvVars",e)}for(const l of function(){const e=s(),n=[];for(const r of[t(e,"Documents"),t(e,"OneDrive","Documents"),t(e,"OneDrive","文档")])for(const e of["PowerShell","WindowsPowerShell"])n.push(t(r,e,"Microsoft.PowerShell_profile.ps1"),t(r,e,"profile.ps1"));return n}())try{if(!n(l))continue;const t=r(l,"utf-8");let s=t;for(const n of e)s=s.replace(new RegExp(String.raw`^[ \t]*\$env:${n}[ \t]*=.*(\r?\n)?`,"gim"),"");s!==t&&(o(l,s,"utf-8"),i.info(`Removed ANTHROPIC_* assignments from ${l}`))}catch(e){i.logError("envCleanup.powerShellProfile",e)}return{machineLeft:c}}export function cleanupAnthropicEnvVars(){const e=ANTHROPIC_ENV_VARS.filter(e=>!!process.env[e]);if(0===e.length)return[];cleanupShellRcEnvVars(),c=cleanupWindowsEnvVars(e).machineLeft;for(const n of e)delete process.env[n];return[...e]}let c=[];export function machineEnvVarsLeft(){return c}
@@ -1,7 +1,7 @@
1
1
  import { type GatewayModelSpec } from './model-registry.js';
2
2
  import type { GatewayClientModel } from './api-validator.js';
3
3
  /**
4
- * Gateway models (Claude, domestic, Grok) vibecli adds to the coding tools'
4
+ * Gateway models (Claude, GPT, Gemini, domestic, Grok) vibecli adds to the coding tools'
5
5
  * model menus, and the shared context rules: each model compacts at
6
6
  * `autoCompactRatio` (80%) of its window unless the gateway sets a point.
7
7
  */
@@ -1 +1 @@
1
- import{getGatewayModelRules as t}from"./model-registry.js";export function autoCompactTokens(e,o){return o&&o>0?Math.min(o,e):Math.floor(e*t().autoCompactRatio)}export function gatewayModelSpecs(e,o){const n=t().models.filter(t=>function(t,e){return"codex"===t||!e.id.startsWith("claude-")&&!1!==e.claudeCode}(e,t));if(!o)return n;const r=new Map(n.map(t=>[t.id,t])),s="codex"===e?[...o.codex??[],...(o.claude??[]).filter(t=>t.id.startsWith("claude-")&&r.has(t.id))]:(o.claude??[]).filter(t=>!t.id.startsWith("claude-")&&(r.get(t.id)?.claudeCode??!0));if(0===s.length)return n;const a=new Set,i=[];for(const t of s)a.has(t.id)||(a.add(t.id),i.push({...r.get(t.id),...t}));return i}const e={gpt:"GPT",glm:"GLM",deepseek:"DeepSeek",minimax:"MiniMax",qwen:"Qwen"};export function prettyModelName(t){const o=t.replace(/^([a-z]+)(\d)/i,"$1 $2").split(/[-_\s]+/).filter(Boolean),n=[];for(const t of o){const o=n[n.length-1];if(/^\d+$/.test(t)&&o&&/^\d+$/.test(o)&&t.length<=2&&!/^\d{8}$/.test(t)){n[n.length-1]=`${o}.${t}`;continue}const r=t.toLowerCase();n.push(e[r]??(/^[a-z]\d/i.test(t)?t.toUpperCase():t[0].toUpperCase()+t.slice(1)))}return n.join(" ")}export function vendorNames(t){return[...new Set(t.map(t=>prettyModelName(t).split(" ")[0]))]}export function modelFamily(t){const e=t.toLowerCase();return e.startsWith("claude-")?"claude":e.startsWith("grok")?"grok":/^(gpt|o\d|codex)/.test(e)?"gpt":"other"}
1
+ import{getGatewayModelRules as e}from"./model-registry.js";export function autoCompactTokens(t,o){return o&&o>0?Math.min(o,t):Math.floor(t*e().autoCompactRatio)}export function gatewayModelSpecs(t,o){const i=e().models,n=i.filter(e=>function(e,t){return"codex"===e?!1!==t.codex:!t.id.startsWith("claude-")&&!1!==t.claudeCode}(t,e));if(!o)return n;const r=new Map(("claude-code"===t?i:n).map(e=>[e.id,e])),d="codex"===t?[...o.codex??[],...(o.claude??[]).filter(e=>e.id.startsWith("claude-")&&r.has(e.id))]:function(e,t){return[...(e.codex??[]).filter(e=>"gpt"===modelFamily(e.id)),...e.gemini??[],...e.grok??[],...(e.claude??[]).filter(e=>!e.id.startsWith("claude-"))].filter(e=>t.get(e.id)?.claudeCode??!0)}(o,r);if(0===d.length)return n;const a=new Set,s=[];for(const e of d)a.has(e.id)||(a.add(e.id),s.push({...r.get(e.id),...e}));return s}const t={gpt:"GPT",gemini:"Gemini",glm:"GLM",deepseek:"DeepSeek",minimax:"MiniMax",qwen:"Qwen"};export function prettyModelName(e){const o=e.replace(/^([a-z]+)(\d)/i,"$1 $2").split(/[-_\s]+/).filter(Boolean),i=[];for(const e of o){const o=i[i.length-1];if(/^\d+$/.test(e)&&o&&/^\d+$/.test(o)&&e.length<=2&&!/^\d{8}$/.test(e)){i[i.length-1]=`${o}.${e}`;continue}const n=e.toLowerCase();i.push(t[n]??(/^[a-z]\d/i.test(e)?e.toUpperCase():e[0].toUpperCase()+e.slice(1)))}return i.join(" ")}export function vendorNames(e){return[...new Set(e.map(e=>prettyModelName(e).split(" ")[0]))]}export function modelFamily(e){const t=e.toLowerCase();return t.startsWith("claude-")?"claude":t.startsWith("grok")?"grok":/^(gpt|o\d|codex)/.test(t)?"gpt":"other"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Any vibecli command also brings the coding tools' model menus up to date with
3
+ * the gateway's client config, so models the gateway adds or hides reach
4
+ * customers who never run `setup` again. Only menus vibecli wrote and that still
5
+ * use the key vibecli saved are touched; offline nothing changes.
6
+ */
7
+ export declare const MODEL_REFRESH_INTERVAL_MS: number;
8
+ export interface ModelMenuChange {
9
+ tool: string;
10
+ added: string[];
11
+ removed: string[];
12
+ }
13
+ export declare function shouldSkipModelRefresh(args: string[], env?: NodeJS.ProcessEnv): boolean;
14
+ /** Rewrites the menus that differ from the gateway's current list; returns what changed per tool. */
15
+ export declare function refreshModelMenus(): Promise<ModelMenuChange[]>;
16
+ /** Startup hook: at most once an hour, never fails the command it rides along with. */
17
+ export declare function runModelRefresh(args: string[], options?: {
18
+ env?: NodeJS.ProcessEnv;
19
+ now?: number;
20
+ output?: (line: string) => void;
21
+ }): Promise<ModelMenuChange[]>;
@@ -0,0 +1 @@
1
+ import{existsSync as e,mkdirSync as o,readFileSync as t,writeFileSync as r}from"fs";import{dirname as n,join as s}from"path";import i from"chalk";import{configManager as l}from"./config.js";import{i18n as d}from"./i18n.js";import{resolveModelCatalog as f}from"./model-catalog.js";import{listAdapters as c}from"./tools/registry.js";import{logger as a}from"../utils/logger.js";import{resolveUpdateCachePath as u}from"./auto-update.js";export const MODEL_REFRESH_INTERVAL_MS=36e5;const m=new Set(["setup","init","config","auth","enter","completion","update","lang","logs"]),h=["--json","--dry-run","--help","-h","--version","-v"];export function shouldSkipModelRefresh(e,o=process.env){if("1"===o.VIBECLI_DISABLE_MODEL_REFRESH)return!0;if(h.some(o=>e.includes(o)))return!0;const t=e.find(e=>!e.startsWith("-"));return void 0===t||m.has(t)}export async function refreshModelMenus(){const e=new Map,o=[];for(const t of c())if(t.refreshModelMenu&&t.listedModels)try{const r=l.getApiKeyForTool(t.name);if(!r||t.detectCurrentConfig().apiKey!==r)continue;const n=t.listedModels();if(null===n)continue;e.has(r)||e.set(r,await f(r));const s=e.get(r);if(!s)continue;t.refreshModelMenu({apiKey:r,baseUrl:l.getBaseUrl(),models:l.getModelsForTool(t.name),settings:l.getToolSettings(t.name),catalog:s});const i=t.listedModels()??[],d=i.filter(e=>!n.includes(e)),c=n.filter(e=>!i.includes(e));(d.length>0||c.length>0)&&o.push({tool:t.displayName,added:d,removed:c})}catch(e){a.logError(`modelRefresh(${t.name})`,e)}return o}export async function runModelRefresh(l,f={}){const c=f.env??process.env;if(shouldSkipModelRefresh(l,c))return[];const m=f.now??Date.now(),h=function(e){return s(n(u(e)),"model-refresh.json")}(c);if(m-function(e){try{const o=JSON.parse(t(e,"utf-8")).refreshed_at;return"number"==typeof o?o:0}catch{return 0}}(h)<36e5)return[];let p=[];try{p=await refreshModelMenus(),e(n(h))||o(n(h),{recursive:!0}),r(h,JSON.stringify({refreshed_at:m})+"\n")}catch(e){a.logError("runModelRefresh",e)}const g=f.output??(e=>console.log(i.gray(e)));for(const e of p){const o=[e.added.length>0?d.t("model_refresh.added",{models:e.added.join(", ")}):"",e.removed.length>0?d.t("model_refresh.removed",{models:e.removed.join(", ")}):""].filter(Boolean);g(d.t("model_refresh.changed",{tool:e.tool,changes:o.join(";")}))}return p}
@@ -47,11 +47,15 @@ export interface GatewayModelSpec {
47
47
  autoCompactTokens?: number;
48
48
  /** `false` keeps the model out of Claude Code's picker (its window differs from the rest). */
49
49
  claudeCode?: boolean;
50
+ /** `false` keeps the model out of Codex's catalog (GPT comes from Codex itself). */
51
+ codex?: boolean;
50
52
  }
51
53
  export interface GatewayModelRules {
52
54
  /** Auto-compact at this fraction of the context window unless a model sets its own limit. */
53
55
  autoCompactRatio: number;
54
56
  models: GatewayModelSpec[];
57
+ /** Ids vibecli used to add and has since taken off its lists; menus still holding them are vibecli's to update. */
58
+ retired: string[];
55
59
  }
56
60
  /** Codex-only catalog defaults. */
57
61
  export interface CodexRules {
@@ -1 +1 @@
1
- import{dirname as e,join as o}from"path";import{fileURLToPath as t}from"url";import{existsSync as n,readFileSync as r}from"fs";const l=e(t(import.meta.url));let s=null;function u(){return s||(s=function(){const e=[o(l,"..","..","models.json"),o(l,"..","models.json"),o(l,"models.json")];for(const o of e)if(n(o))return JSON.parse(r(o,"utf-8"));throw new Error(`models.json not found, searched: ${e.join(", ")}`)}()),s}export function getDefaultModels(){return{...u().defaultModels}}export function isRetiredDefaultModel(e,o){return u().retiredDefaultModels?.[e]?.includes(o)??!1}export function getDefaultToolModel(){return u().defaultToolModel?.trim()||"gpt-5.5"}export function getToolDefaultModel(e){const o=u().toolDefaults;return o&&Object.prototype.hasOwnProperty.call(o,e)?(o[e]??"").trim():getDefaultToolModel()}export function getPresets(){return[...u().presets]}export function resolvePreset(e){return e.models?{...e.models}:e.model?{haiku:e.model,sonnet:e.model,opus:e.model,reasoning:e.model}:getDefaultModels()}export function getClaudeCodeTierChains(){const e=u().claudeCode?.tierChains??{};return Object.fromEntries(Object.entries(e).map(([e,o])=>[e,[...o]]))}export function getClaudeDesktopRules(){const e=u().claudeDesktop??{};return{oneMillionFallback:[...e.oneMillionFallback??[]],oneMillionExclusions:(e.oneMillionExclusions??[]).map(e=>({...e,models:[...e.models]}))}}export function getGatewayModelRules(){const e=u().gatewayModels??{};return{autoCompactRatio:e.autoCompactRatio??.8,models:(e.models??[]).map(e=>({...e}))}}export function getCodexRules(){return{standardContextWindow:u().codex?.standardContextWindow??272e3}}export function compareVersions(e,o){const t=e.split(".").map(e=>parseInt(e,10)||0),n=o.split(".").map(e=>parseInt(e,10)||0);for(let e=0;e<Math.max(t.length,n.length);e++){const o=(t[e]??0)-(n[e]??0);if(0!==o)return o}return 0}export function getToolMinVersion(e){return u().toolMinVersions?.[e]?.trim()||null}export function reloadRegistry(){s=null}
1
+ import{dirname as e,join as o}from"path";import{fileURLToPath as t}from"url";import{existsSync as n,readFileSync as r}from"fs";const l=e(t(import.meta.url));let s=null;function i(){return s||(s=function(){const e=[o(l,"..","..","models.json"),o(l,"..","models.json"),o(l,"models.json")];for(const o of e)if(n(o))return JSON.parse(r(o,"utf-8"));throw new Error(`models.json not found, searched: ${e.join(", ")}`)}()),s}export function getDefaultModels(){return{...i().defaultModels}}export function isRetiredDefaultModel(e,o){return i().retiredDefaultModels?.[e]?.includes(o)??!1}export function getDefaultToolModel(){return i().defaultToolModel?.trim()||"gpt-5.5"}export function getToolDefaultModel(e){const o=i().toolDefaults;return o&&Object.prototype.hasOwnProperty.call(o,e)?(o[e]??"").trim():getDefaultToolModel()}export function getPresets(){return[...i().presets]}export function resolvePreset(e){return e.models?{...e.models}:e.model?{haiku:e.model,sonnet:e.model,opus:e.model,reasoning:e.model}:getDefaultModels()}export function getClaudeCodeTierChains(){const e=i().claudeCode?.tierChains??{};return Object.fromEntries(Object.entries(e).map(([e,o])=>[e,[...o]]))}export function getClaudeDesktopRules(){const e=i().claudeDesktop??{};return{oneMillionFallback:[...e.oneMillionFallback??[]],oneMillionExclusions:(e.oneMillionExclusions??[]).map(e=>({...e,models:[...e.models]}))}}export function getGatewayModelRules(){const e=i().gatewayModels??{};return{autoCompactRatio:e.autoCompactRatio??.8,models:(e.models??[]).map(e=>({...e})),retired:[...e.retired??[]]}}export function getCodexRules(){return{standardContextWindow:i().codex?.standardContextWindow??272e3}}export function compareVersions(e,o){const t=e.split(".").map(e=>parseInt(e,10)||0),n=o.split(".").map(e=>parseInt(e,10)||0);for(let e=0;e<Math.max(t.length,n.length);e++){const o=(t[e]??0)-(n[e]??0);if(0!==o)return o}return 0}export function getToolMinVersion(e){return i().toolMinVersions?.[e]?.trim()||null}export function reloadRegistry(){s=null}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * What setup and the wizard ask right after writing configs, in interactive
3
+ * terminals only: upgrade tools too old for the config (`vibecli upgrade`), and
4
+ * restart GUI apps that are running and would otherwise keep the old config.
5
+ */
6
+ export declare function postApplyPrompts(tools: string[]): Promise<void>;
@@ -0,0 +1 @@
1
+ import o from"chalk";import t from"inquirer";import{i18n as r}from"./i18n.js";import{isNonInteractive as e}from"./runtime-options.js";import{toolManager as i}from"./tool-manager.js";import{getAdapter as n}from"./tools/registry.js";import{upgradeTool as s}from"./tool-upgrade.js";async function a(o){const{ok:r}=await t.prompt([{type:"confirm",name:"ok",message:o,default:!0}]);return r}export async function postApplyPrompts(t){if(e()||!process.stdin.isTTY)return;const p=t.filter(o=>n(o)?.installSpec&&i.outdatedVersion(o));if(p.length>0){const o=p.map(o=>n(o)?.displayName??o).join("、");if(await a(r.t("setup.offer_upgrade",{tools:o})))for(const o of p)await s(o)}for(const e of t){const t=n(e);if(!t?.runningApp?.isRunning())continue;if(!await a(r.t("setup.offer_restart",{tool:t.displayName})))continue;const i=t.runningApp.restart();console.log(i?o.green(" √ "+r.t("setup.restarted",{tool:t.displayName})):o.yellow(" ! "+r.t("setup.restart_failed",{tool:t.displayName})))}}
@@ -1 +1 @@
1
- import{execSync as o,spawnSync as t}from"child_process";import{i18n as n}from"./i18n.js";import l from"inquirer";import e from"chalk";import s from"terminal-link";import i from"ora";import{logger as r}from"../utils/logger.js";import{configManager as a}from"./config.js";import{getAdapter as c,listAdapters as d,TOOL_ADAPTERS as u}from"./tools/registry.js";import{backupBeforeWrite as g,listBackups as m,restoreBackup as p}from"./tools/backup.js";import{existsSync as f,rmSync as w}from"fs";import{stringWidth as h}from"../utils/string-width.js";import{resolveModelCatalog as _}from"./model-catalog.js";import{compareVersions as y,getToolMinVersion as P}from"./model-registry.js";export{y as compareVersions};function C(o){const t=u[o];return{name:t.name,command:t.command,installCommand:t.installCommand,configPath:t.configPath,displayName:t.displayName,...t.hidden?{hidden:!0}:{}}}export const SUPPORTED_TOOLS=Object.fromEntries(Object.keys(u).map(o=>[o,C(o)]));export class ToolManager{static instance;constructor(){}static getInstance(){return ToolManager.instance||(ToolManager.instance=new ToolManager),ToolManager.instance}isToolInstalled(t){const n=SUPPORTED_TOOLS[t];if(!n)return!1;const l=c(t);if(l?.isInstalled)try{return l.isInstalled()}catch{return!1}try{const t="win32"===process.platform?`where ${n.command}`:`which ${n.command}`;return o(t,{stdio:"pipe"}),!0}catch{return!1}}async installTool(t){const a=SUPPORTED_TOOLS[t];if(!a)throw new Error(`Unknown tool: ${t}`);const c=i(n.t("wizard.installing_tool")).start();try{c.info(e.gray("$ ")+e.white(a.installCommand)),c.stop(),o(a.installCommand,{stdio:"inherit"}),c.succeed(n.t("wizard.tool_installed"))}catch(i){c.fail(n.t("wizard.install_failed"));const d=(i.message||"")+(i.stderr?.toString()||"")+(i.stdout?.toString()||"");if(!(d.includes("EACCES")||d.includes("permission denied")||d.includes("EPERM")||243===i.status))throw new Error(`Failed to install ${a.displayName}: ${i}`);if(console.log("\n⚠️ "+n.t("install.permission_detected")+"\n"),"win32"===process.platform)try{const t=a.installCommand.replace("npm install -g","npm install -g --force");return console.log("🔧 "+n.t("install.trying_solution",{num:"1",desc:n.t("install.using_force")})),console.log(e.gray("$ ")+e.white(t)),o(t,{stdio:"inherit"}),void console.log("\n✅ "+n.t("install.permission_fixed"))}catch(o){r.warn("Retry install failed:",o),console.log("\n❌ "+n.t("install.auto_fix_failed")),console.log(e.yellow("\n📌 "+n.t("install.windows_solutions"))),console.log(""),console.log(e.cyan.bold(n.t("install.windows_solution_1_title"))),console.log(e.gray(" "+n.t("install.windows_solution_1_step1"))),console.log(e.gray(" "+n.t("install.windows_solution_1_step2"))),console.log(e.gray(" "+n.t("install.windows_solution_1_step3"))),console.log(e.white(` ${a.installCommand}`)),console.log(""),console.log(e.cyan.bold(n.t("install.windows_solution_2_title"))),console.log(e.gray(" "+n.t("install.windows_solution_2_command"))),console.log(e.white(` ${a.installCommand.replace("npm install -g","npm install -g --prefix=%APPDATA%\\npm")}`)),console.log("");const{action:s}=await l.prompt([{type:"list",name:"action",message:n.t("install.what_next"),choices:[{name:n.t("install.installed_continue"),value:"continue"},{name:n.t("install.cancel_install"),value:"cancel"}]}]);if("cancel"===s)throw new Error(n.t("install.user_cancelled"));if(!this.isToolInstalled(t))throw console.log(e.red("\n❌ "+n.t("install.still_not_installed",{tool:a.displayName}))),new Error(`${a.displayName} is not installed`);return void console.log(e.green("\n✅ "+n.t("install.verified_success",{tool:a.displayName})))}console.log(e.yellow("\n📌 "+n.t("install.unix_solutions"))),console.log(""),console.log(e.cyan.bold(n.t("install.unix_solution_1_title"))),console.log(e.gray(" "+n.t("install.unix_solution_1_desc"))),console.log(e.white(` sudo ${a.installCommand}`)),console.log("");const u="https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally",g=s(u,u,{fallback:()=>u});console.log(e.cyan.bold(n.t("install.unix_solution_2_title"))),console.log(e.gray(" "+n.t("install.unix_solution_2_desc"))),console.log(e.blue(" 📖 "+n.t("install.npm_docs_link")+": ")+g),console.log("");const{action:m}=await l.prompt([{type:"list",name:"action",message:n.t("install.what_next"),choices:[{name:n.t("install.installed_continue"),value:"continue"},{name:n.t("install.cancel_install"),value:"cancel"}]}]);if("cancel"===m)throw new Error(n.t("install.user_cancelled"));if(!this.isToolInstalled(t))throw console.log(e.red("\n❌ "+n.t("install.still_not_installed",{tool:a.displayName}))),new Error(`${a.displayName} is not installed`);console.log(e.green("\n✅ "+n.t("install.verified_success",{tool:a.displayName})))}}getToolConfig(o){const t=c(o);if(!t)throw new Error(`Unknown tool: ${o}`);try{return t.getConfig()}catch(t){return r.warn(`Failed to read config for ${o}:`,t),r.logError(`ToolManager.getToolConfig(${o})`,t),null}}updateToolConfig(o,t){const n=c(o);if(!n)throw new Error(`Unknown tool: ${o}`);if(!n.updateConfig)throw new Error(`Tool ${o} does not support raw config updates`);n.updateConfig(t)}forceResetConfig(o){const t=c(o);if(!t)throw new Error(`Unknown tool: ${o}`);const n=t.forceResetPaths?t.forceResetPaths():this.backupPathsFor(t),l=[];for(const o of n)f(o)&&(g(o),w(o,{force:!0}),l.push(o));return l}loadPrivateConfig(o,t,n,l,e={}){const s=c(o);if(!s)throw new Error(`Unknown tool: ${o}`);e.force&&this.forceResetConfig(o),s.loadPrivateConfig({apiKey:t,baseUrl:a.getBaseUrl(),models:n,settings:a.getToolSettings(o),...l?{catalog:l}:{}})}async applyPrivateConfig(o,t,n,l={}){const e=c(o);if(!e)throw new Error(`Unknown tool: ${o}`);const s=e.wantsModelCatalog?await _(t):null;l.force?this.loadPrivateConfig(o,t,n,s??void 0,{force:!0}):s?this.loadPrivateConfig(o,t,n,s):this.loadPrivateConfig(o,t,n)}applyNotes(o){const t=c(o);if(!t)return[];const l=[];try{t.applyNotes&&l.push(...t.applyNotes())}catch(t){r.logError(`ToolManager.applyNotes(${o})`,t)}const e=this.outdatedVersion(o);if(e){l.push({kind:"action",text:n.t("tools.version_outdated",{current:e.current,min:e.min,upgrade:t.currentUpgradeCommand?.()||t.upgradeCommand||t.installCommand})});for(const o of t.upgradeHints?.(e.min)??[])l.push({kind:"action",text:o})}return l}printApplied(o,t=16){const n=SUPPORTED_TOOLS[o]?.displayName??o,l=this.applyNotes(o);for(const o of l.filter(o=>"detail"===o.kind))r.record(`${n}: ${o.text}`);const s=l.filter(o=>"summary"===o.kind).map(o=>o.text).join(";"),i=" ".repeat(Math.max(1,t-h(n)));console.log(e.green("√ "+n)+(s?i+s:""));for(const o of l.filter(o=>"action"===o.kind))console.log(e.yellow(" ! "+o.text))}installedVersion(o){const n=c(o);if(!n)return null;if(n.installedVersion)try{return n.installedVersion()}catch(t){return r.logError(`ToolManager.installedVersion(${o})`,t),null}if(n.isInstalled)return null;try{const o={encoding:"utf-8",timeout:1e4,windowsHide:!0,stdio:["ignore","pipe","pipe"]},l=`${("win32"===process.platform?t(`${n.command} --version`,{...o,shell:!0}):t(n.command,["--version"],o)).stdout??""}`.match(/(\d+)\.(\d+)\.(\d+)/);return l?l[0]:null}catch{return null}}outdatedVersion(o){const t=P(o);if(!t)return null;const n=this.installedVersion(o);return n&&y(n,t)<0?{current:n,min:t}:null}unloadPrivateConfig(o){const t=c(o);if(!t)throw new Error(`Unknown tool: ${o}`);t.unloadPrivateConfig()}async clearPinnedModels(o){const t=c(o);if(!t)throw new Error(`Unknown tool: ${o}`);if(a.setToolModel(o,null),!t.clearPinnedModels)return[];const n=a.getApiKeyForTool(o),l=t.wantsModelCatalog&&n?await _(n):null;return t.clearPinnedModels(l??void 0)}supportsClearPinnedModels(o){return Boolean(c(o)?.clearPinnedModels)}detectCurrentConfig(o){const t=c(o);if(!t)throw new Error(`Unknown tool: ${o}`);return t.detectCurrentConfig()}backupPathsFor(o){return o.backupPaths?o.backupPaths():[o.configPath]}listToolBackups(o){const t=c(o);if(!t)throw new Error(`Unknown tool: ${o}`);return this.backupPathsFor(t).map(o=>({file:o,backups:m(o)}))}restorePrivateConfig(o){const t=c(o);if(!t)throw new Error(`Unknown tool: ${o}`);return this.backupPathsFor(t).map(o=>({file:o,result:p(o)}))}getInstalledTools(){return Object.keys(SUPPORTED_TOOLS).filter(o=>this.isToolInstalled(o))}getSupportedTools(){return d().filter(o=>!o.hidden).map(o=>C(o.name))}isGitInstalled(){try{const t="win32"===process.platform?"where git":"which git";return o(t,{stdio:"pipe"}),!0}catch{return!1}}}export const toolManager=ToolManager.getInstance();
1
+ import{execSync as o,spawnSync as t}from"child_process";import{i18n as n}from"./i18n.js";import e from"inquirer";import r from"chalk";import{logger as i}from"../utils/logger.js";import{configManager as l}from"./config.js";import{getAdapter as s,listAdapters as a,TOOL_ADAPTERS as c}from"./tools/registry.js";import{backupBeforeWrite as d,listBackups as p,restoreBackup as u}from"./tools/backup.js";import{existsSync as f,rmSync as m}from"fs";import{stringWidth as g}from"../utils/string-width.js";import{resolveModelCatalog as h}from"./model-catalog.js";import{compareVersions as w,getToolMinVersion as P}from"./model-registry.js";import{findInstalls as y,forgetInstalls as T,installLabel as C,npmMirrorInstallCommand as k}from"./tools/tool-installs.js";export{w as compareVersions};function $(o){const t=c[o];return{name:t.name,command:t.command,installCommand:t.installCommand,configPath:t.configPath,displayName:t.displayName,...t.hidden?{hidden:!0}:{}}}export const SUPPORTED_TOOLS=Object.fromEntries(Object.keys(c).map(o=>[o,$(o)]));export class ToolManager{static instance;constructor(){}static getInstance(){return ToolManager.instance||(ToolManager.instance=new ToolManager),ToolManager.instance}isToolInstalled(t){const n=SUPPORTED_TOOLS[t];if(!n)return!1;const e=s(t);if(e?.isInstalled)try{return e.isInstalled()}catch{return!1}try{const t="win32"===process.platform?`where ${n.command}`:`which ${n.command}`;return o(t,{stdio:"pipe"}),!0}catch{return!1}}async installTool(t){const i=SUPPORTED_TOOLS[t];if(!i)throw new Error(`Unknown tool: ${t}`);const l=s(t)?.installSpec,a=l?k(l):i.installCommand;console.log(r.gray("$ ")+r.white(a));try{return o(a,{stdio:"inherit"}),T(),void console.log(r.green("√ "+n.t("wizard.tool_installed")))}catch(o){if(console.log(r.red("× "+n.t("wizard.install_failed"))),!/EACCES|EPERM|permission denied/i.test(`${o?.message??""}${o?.stderr??""}`)&&243!==o?.status&&!a.startsWith("npm "))throw new Error(`Failed to install ${i.displayName}: ${o}`);console.log(r.yellow(" ! "+("win32"===process.platform?n.t("install.permission_windows",{command:a}):n.t("install.permission_unix",{command:a}))))}const{action:c}=await e.prompt([{type:"list",name:"action",message:n.t("install.what_next"),choices:[{name:n.t("install.installed_continue"),value:"continue"},{name:n.t("install.cancel_install"),value:"cancel"}]}]);if("cancel"===c)throw new Error(n.t("install.user_cancelled"));if(T(),!this.isToolInstalled(t))throw console.log(r.red("× "+n.t("install.still_not_installed",{tool:i.displayName}))),new Error(`${i.displayName} is not installed`);console.log(r.green("√ "+n.t("install.verified_success",{tool:i.displayName})))}getToolConfig(o){const t=s(o);if(!t)throw new Error(`Unknown tool: ${o}`);try{return t.getConfig()}catch(t){return i.warn(`Failed to read config for ${o}:`,t),i.logError(`ToolManager.getToolConfig(${o})`,t),null}}updateToolConfig(o,t){const n=s(o);if(!n)throw new Error(`Unknown tool: ${o}`);if(!n.updateConfig)throw new Error(`Tool ${o} does not support raw config updates`);n.updateConfig(t)}forceResetConfig(o){const t=s(o);if(!t)throw new Error(`Unknown tool: ${o}`);const n=t.forceResetPaths?t.forceResetPaths():this.backupPathsFor(t),e=[];for(const o of n)f(o)&&(d(o),m(o,{force:!0}),e.push(o));return e}loadPrivateConfig(o,t,n,e,r={}){const i=s(o);if(!i)throw new Error(`Unknown tool: ${o}`);r.force&&this.forceResetConfig(o),i.loadPrivateConfig({apiKey:t,baseUrl:l.getBaseUrl(),models:n,settings:l.getToolSettings(o),...e?{catalog:e}:{}})}async applyPrivateConfig(o,t,n,e={}){const r=s(o);if(!r)throw new Error(`Unknown tool: ${o}`);const i=r.wantsModelCatalog?await h(t):null;e.force?this.loadPrivateConfig(o,t,n,i??void 0,{force:!0}):i?this.loadPrivateConfig(o,t,n,i):this.loadPrivateConfig(o,t,n)}applyNotes(o){const t=s(o);if(!t)return[];const e=[];try{t.applyNotes&&e.push(...t.applyNotes())}catch(t){i.logError(`ToolManager.applyNotes(${o})`,t)}const r=this.outdatedVersion(o);if(r){e.push({kind:"action",text:n.t("tools.version_outdated",{current:r.current,min:r.min,upgrade:t.installSpec?`vibecli upgrade ${o}`:t.upgradeCommand||t.installCommand})});for(const o of t.upgradeHints?.(r.min)??[])e.push({kind:"action",text:o})}const l=t.installSpec?y(t.installSpec):[];return l.length>1&&e.push({kind:"action",text:n.t("upgrade.duplicates_note",{count:String(l.length),active:C(l[0]),others:l.slice(1).map(C).join("、"),tool:o})}),e}printApplied(o,t=16){const n=SUPPORTED_TOOLS[o]?.displayName??o,e=this.applyNotes(o);for(const o of e.filter(o=>"detail"===o.kind))i.record(`${n}: ${o.text}`);const l=e.filter(o=>"summary"===o.kind).map(o=>o.text).join(";"),s=" ".repeat(Math.max(1,t-g(n)));console.log(r.green("√ "+n)+(l?s+l:""));for(const o of e.filter(o=>"action"===o.kind))console.log(r.yellow(" ! "+o.text))}installedVersion(o){const n=s(o);if(!n)return null;if(n.installSpec)return y(n.installSpec)[0]?.version??null;if(n.installedVersion)try{return n.installedVersion()}catch(t){return i.logError(`ToolManager.installedVersion(${o})`,t),null}if(n.isInstalled)return null;try{const o={encoding:"utf-8",timeout:1e4,windowsHide:!0,stdio:["ignore","pipe","pipe"]},e=`${("win32"===process.platform?t(`${n.command} --version`,{...o,shell:!0}):t(n.command,["--version"],o)).stdout??""}`.match(/(\d+)\.(\d+)\.(\d+)/);return e?e[0]:null}catch{return null}}outdatedVersion(o){const t=P(o);if(!t)return null;const n=this.installedVersion(o);return n&&w(n,t)<0?{current:n,min:t}:null}unloadPrivateConfig(o){const t=s(o);if(!t)throw new Error(`Unknown tool: ${o}`);t.unloadPrivateConfig()}async clearPinnedModels(o){const t=s(o);if(!t)throw new Error(`Unknown tool: ${o}`);if(l.setToolModel(o,null),!t.clearPinnedModels)return[];const n=l.getApiKeyForTool(o),e=t.wantsModelCatalog&&n?await h(n):null;return t.clearPinnedModels(e??void 0)}supportsClearPinnedModels(o){return Boolean(s(o)?.clearPinnedModels)}detectCurrentConfig(o){const t=s(o);if(!t)throw new Error(`Unknown tool: ${o}`);return t.detectCurrentConfig()}backupPathsFor(o){return o.backupPaths?o.backupPaths():[o.configPath]}listToolBackups(o){const t=s(o);if(!t)throw new Error(`Unknown tool: ${o}`);return this.backupPathsFor(t).map(o=>({file:o,backups:p(o)}))}restorePrivateConfig(o){const t=s(o);if(!t)throw new Error(`Unknown tool: ${o}`);return this.backupPathsFor(t).map(o=>({file:o,result:u(o)}))}getInstalledTools(){return Object.keys(SUPPORTED_TOOLS).filter(o=>this.isToolInstalled(o))}getSupportedTools(){return a().filter(o=>!o.hidden).map(o=>$(o.name))}isGitInstalled(){try{const t="win32"===process.platform?"where git":"which git";return o(t,{stdio:"pipe"}),!0}catch{return!1}}}export const toolManager=ToolManager.getInstance();
@@ -0,0 +1,10 @@
1
+ /** Tests replace the process runner. */
2
+ export declare function setUpgradeRunnerForTests(fake: (command: string, args: string[]) => boolean): void;
3
+ /**
4
+ * `vibecli upgrade <tool>`: upgrade the copy PATH runs the way it was installed
5
+ * (vendor updater / npm via npmmirror / Homebrew). When the vendor updater
6
+ * can't connect, a native install gets the same binary from the mirror in place;
7
+ * failing that, install from the npm mirror and leave a single copy on PATH.
8
+ * Returns whether the tool ends up at or above `toolMinVersions`.
9
+ */
10
+ export declare function upgradeTool(toolName: string): Promise<boolean>;
@@ -0,0 +1 @@
1
+ import{spawnSync as e}from"child_process";import{rmSync as r}from"fs";import{join as o}from"path";import t from"chalk";import n from"inquirer";import{i18n as s}from"./i18n.js";import{logger as i}from"../utils/logger.js";import{compareVersions as a,getToolMinVersion as l}from"./model-registry.js";import{getAdapter as p}from"./tools/registry.js";import{findInstalls as c,forgetInstalls as u,installLabel as g,npmMirrorInstallCommand as d,npmUninstallCommand as m}from"./tools/tool-installs.js";import{toolHome as f}from"./tools/types.js";import{isNonInteractive as w,shouldAutoConfirm as h}from"./runtime-options.js";let v=(r,o)=>{console.log(t.gray("$ ")+[r,...o].join(" "));const n="win32"!==process.platform||/\.exe$/i.test(r)?e(r,o,{stdio:"inherit"}):e([r,...o].map(e=>/\s/.test(e)?`"${e}"`:e).join(" "),{stdio:"inherit",shell:!0});return n.error&&i.logError(`upgrade: ${r}`,n.error),0===n.status};export function setUpgradeRunnerForTests(e){v=e}function y(e){const[r,...o]=d(e).split(" "),n=v(r,o);return n||"win32"===process.platform||console.log(t.yellow(" ! "+s.t("upgrade.npm_permission",{command:d(e)}))),n}function _(e,t){switch(t.kind){case"npm":v("npm",["uninstall","-g",e.npmPackage]);break;case"brew":e.brew&&v("brew",["uninstall",e.brew]);break;case"native":try{r(t.path,{force:!0});for(const t of e.native?.dataDirs??[])r(o(f(),...t),{recursive:!0,force:!0})}catch(e){i.logError("upgrade: remove native copy",e)}break;default:return!1}return u(),!c(e).some(e=>e.path===t.path)}function b(e,r){switch(r.kind){case"npm":return m(e);case"brew":return`brew uninstall ${e.brew}`;case"native":return s.t("upgrade.delete_paths",{paths:[r.path,...(e.native?.dataDirs??[]).map(e=>o(f(),...e))].join("、")});default:return s.t("upgrade.delete_paths",{paths:r.path})}}export async function upgradeTool(e){const r=p(e),o=r?.installSpec;if(!r||!o)throw new Error(s.t("upgrade.unsupported",{tool:e}));const d=r.displayName;u();const m=c(o);if(console.log(t.bold(`\n${d}`)),0===m.length)console.log(s.t("upgrade.not_installed_installing",{tool:d})),y(o);else{const e=m[0];console.log(t.gray(" "+s.t("upgrade.current",{copy:g(e)})));const r=function(e,r){switch(r.kind){case"native":return v(r.path,e.native?.updateArgs??["update"]);case"npm":return y(e);case"brew":return!!e.brew&&v("brew",["upgrade",e.brew]);default:return!1}}(o,e);r||"other"!==e.kind?r||"npm"===e.kind||"native"===e.kind&&await async function(e){const r=e.native?.mirrorUpdate;if(!r)return!1;console.log(t.yellow(" ! "+s.t("upgrade.native_mirror_trying")));try{const{version:e,manifestVerified:o}=await r();return console.log(t.green(" √ "+s.t(o?"upgrade.native_mirror_done_verified":"upgrade.native_mirror_done",{version:e}))),!0}catch(e){return i.logError("upgrade: native mirror",e),console.log(t.yellow(" ! "+s.t("upgrade.native_mirror_failed",{reason:e instanceof Error?e.message:String(e)}))),!1}}(o)||(console.log(t.yellow(" ! "+s.t("upgrade.fallback_mirror"))),y(o)):console.log(t.yellow(" ! "+s.t("upgrade.unknown_install",{path:e.path})))}u(),await async function(e,r){const o=c(e);if(o.length<2)return;const i=function(e){return e.reduce((e,r)=>r.version&&(!e.version||a(r.version,e.version)>0)?r:e)}(o),l=o.filter(e=>e!==i);console.log(t.yellow(" ! "+s.t("upgrade.duplicates",{tool:r,keep:g(i),others:l.map(g).join("、")})));let p=h();p||w()||!process.stdin.isTTY||(p=(await n.prompt([{type:"confirm",name:"remove",message:s.t("upgrade.confirm_remove"),default:!0}])).remove);for(const r of l)p&&_(e,r)?console.log(t.green(" √ "+s.t("upgrade.removed",{copy:g(r)}))):console.log(t.yellow(" ! "+s.t("upgrade.remove_by_hand",{how:b(e,r)})))}(o,d),u();const f=c(o)[0];if(!f)return console.log(t.red(" × "+s.t("upgrade.still_missing",{tool:d}))),!1;const k=l(e);if(k&&f.version&&a(f.version,k)<0){console.log(t.yellow(" ! "+s.t("upgrade.still_old",{copy:g(f),min:k})));for(const e of r.upgradeHints?.(k)??[])console.log(t.yellow(" ! "+e));return!1}return console.log(t.green(" √ "+s.t("upgrade.done",{copy:g(f)}))),!0}