@vibeapi/cli 0.0.68 → 0.0.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -98,8 +98,22 @@ vibecli doctor # 健康巡检(--deep 实发一条请求
98
98
  vibecli upgrade # 升级 Claude Code / Codex,官方渠道连不上自动改走国内镜像
99
99
  vibecli codex sessions # 换过登录方式后 Codex 旧会话不显示时,让它们重新出现(先退出 Codex)
100
100
  vibecli setup --desktop-registry on # Windows:桌面端不认本地配置时,强制写注册表(会请求一次管理员确认)
101
+ vibecli base # 查看并测试所有接入地址
102
+ vibecli base auto # 换到第一个能用的接入地址,已配置的工具一起换
101
103
  ```
102
104
 
105
+ ### 主域名连不上或证书报错
106
+
107
+ 校园网、酒店网络有时会把 `www.vibeapi.cn` 解析到别的服务器,表现为「连接不是私密连接」「Self-signed certificate」或一直超时。这时换一个接入地址即可,API Key 不用改:
108
+
109
+ ```bash
110
+ vibecli base auto # 自动测试并换到第一个能用的地址
111
+ vibecli base https://www.vibeapi.si # 或指定一个地址
112
+ vibecli base default # 网络恢复后回到主站
113
+ ```
114
+
115
+ 地址清单以网关控制台公布的为准(目前有 `www.vibeapi.cn`、`cf.vibeapi.cn`、`hk.vibeapi.cn`、`www.vibeapi.si`),vibecli 每次连上网关都会更新清单。切换后地址保存在当前 profile 里,Claude Code、Codex、Claude 桌面端等已用 vibecli 配置过的工具会一起改过去,以后再运行 `vibecli setup` 也不会改回主站。首次配置时就可以指定:`vibecli setup --key sk-xxx --base https://www.vibeapi.si`。向导主菜单里也有「切换接入地址」。
116
+
103
117
  ## 支持
104
118
 
105
119
  主页 <https://www.vibeapi.cn/> · 反馈 <https://www.vibeapi.cn/support> · [CNB 备用制品库](https://cnb.cool/vibeapi/npm/-/registries/@vibeapi/cli)
@@ -0,0 +1,17 @@
1
+ import { type GatewayAddress, type ProbeResult } from '../lib/gateway-addresses.js';
2
+ /**
3
+ * `vibecli base` list the gateway's addresses, test each one, mark the current one
4
+ * `vibecli base auto` switch to the first address (in the operator's order) that works here
5
+ * `vibecli base <n|url>` switch to address n from the list, or any address
6
+ * `vibecli base default` back to the main address
7
+ *
8
+ * Switching saves the address in the active profile and rewrites it into every
9
+ * tool vibecli configured with the saved key, so a later `vibecli setup` keeps it.
10
+ */
11
+ export declare function baseCommand(target?: string): Promise<void>;
12
+ /** The gateway's addresses plus the current one when it is not among them (a custom address). */
13
+ export declare function addressesWithCurrent(): GatewayAddress[];
14
+ export declare function probeAll(addresses: GatewayAddress[]): Promise<ProbeResult[]>;
15
+ export declare function reasonText(result: ProbeResult): string;
16
+ /** Saves `url` in the active profile and rewrites it into every tool vibecli configured. */
17
+ export declare function switchGatewayAddress(url: string): Promise<void>;
@@ -0,0 +1 @@
1
+ import e from"chalk";import{configManager as o,DEFAULT_BASE_URL as r}from"../lib/config.js";import{i18n as t}from"../lib/i18n.js";import{toolManager as s}from"../lib/tool-manager.js";import{listAdapters as n}from"../lib/tools/registry.js";import{postApplyPrompts as a}from"../lib/post-apply.js";import{emitJson as l,isJsonMode as i}from"../lib/runtime-options.js";import{logger as c}from"../utils/logger.js";import{gatewayAddresses as u,normalizeBaseUrl as d,probeGatewayAddress as f}from"../lib/gateway-addresses.js";export async function baseCommand(s){const n=o.getBaseUrl(),a=addressesWithCurrent(),c=s?.trim().toLowerCase();if(!c)return void await async function(o,r){const s=await probeAll(o);i()?l({ok:!0,current:r,addresses:o.map((e,o)=>({...e,...s[o],current:e.url===r}))}):(console.log(e.bold(t.t("base.current",{url:r}))+"\n"),b(o,s,r),console.log("\n"+e.gray(t.t("base.usage_hint"))))}(a,n);if("auto"===c){const e=await probeAll(a),o=e.find(e=>e.ok);return o?void await switchGatewayAddress(o.url):(p("no_reachable",t.t("base.none_reachable")),void b(a,e,n))}let u;if("default"===c||"reset"===c)u=r;else if(/^\d+$/.test(c)){if(u=a[Number(c)-1]?.url??null,!u)return void p("bad_index",t.t("base.bad_index",{index:c,count:String(a.length)}))}else if(u=d(s),!u)return void p("bad_url",t.t("base.bad_url",{value:s}));const g=await f(u);g.ok?await switchGatewayAddress(u):p("unreachable",t.t("base.unreachable",{url:u,reason:reasonText(g)}))}export function addressesWithCurrent(){const e=o.getBaseUrl(),r=u();return r.some(o=>o.url===e)||r.push({url:e,label:t.t("base.custom_label")}),r}export async function probeAll(e){return Promise.all(e.map(e=>f(e.url)))}export function reasonText(e){if(!e.error)return"";const o=t.t(`base.error_${e.error}`);return e.detail?`${o}(${e.detail})`:o}function p(o,r){process.exitCode=1,i()?l({ok:!1,error:o,message:r}):console.error(e.red(r))}function b(o,r,s){i()||o.forEach((o,n)=>{const a=r[n],l=a.ok?e.green(`√ ${a.ms} ms`):e.red(`× ${reasonText(a)}`),i=o.url===s?e.cyan(` ← ${t.t("base.current_mark")}`):"";console.log(`${n+1}. ${o.url} ${e.gray(o.label)}\n ${l}${i}`)})}export async function switchGatewayAddress(u){const d=o.getBaseUrl();u===r?o.resetBaseUrl():o.setBaseUrl(u,!0);const f=[],p=[];for(const e of function(){const e=[];for(const r of n()){const t=o.getApiKeyForTool(r.name);if(t)try{r.detectCurrentConfig().apiKey===t&&e.push(r.name)}catch(e){c.logError(`base.detectCurrentConfig(${r.name})`,e)}}return e}()){const r=o.getApiKeyForTool(e);try{await s.applyPrivateConfig(e,r,o.getModelsForTool(e)),f.push(e)}catch(o){p.push({tool:e,reason:o instanceof Error?o.message:String(o)})}}if(p.length>0&&(process.exitCode=1),i())l({ok:0===p.length,previous:d,base_url:u,applied:f,failed:p});else{console.log(e.green(t.t(u===d?"base.kept":"base.switched",{url:u}))),f.length>0&&console.log(t.t("base.tools_moved"));for(const e of f)s.printApplied(e);for(const{tool:o,reason:r}of p)console.error(e.red(`× ${t.t("setup.apply_failed",{tool:o,reason:r})}`));0===f.length&&0===p.length&&console.log(e.gray(t.t("base.no_tools"))),await a(f)}}
@@ -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","upgrade","codex","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","codex","bench","base","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 @@
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 u,installLabel as p}from"../lib/tools/tool-installs.js";import{detectManagedOverrides as m}from"../lib/tools/claude-managed.js";import{codexAuthProblem as _}from"../lib/tools/codex.js";import{claudeAccountLoginPresent as g}from"../lib/tools/claude-code.js";import{toolHome as f}from"../lib/tools/types.js";const h=["HTTPS_PROXY","https_proxy","HTTP_PROXY","http_proxy","ALL_PROXY","all_proxy"];function y(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 w=o(i.t("doctor.checking")).start(),$=[],j=Number(process.versions.node.split(".")[0]);$.push({name:"Node.js",status:j>=20?"pass":"fail",message:`v${process.versions.node}`}),w.text=i.t("doctor.check_proxy"),$.push(await async function(){const t=i.t("doctor.proxy"),o=new Map;for(const t of h){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 y(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(", ")})}}()),w.text=i.t("doctor.check_api_key_network");const x=r.getApiKey(),T=i.t("doctor.api_key_network");if(x){const t=await l(x);t.valid?$.push({name:T,status:"pass"}):"invalid_api_key"===t.error?$.push({name:T,status:"fail",message:i.t("doctor.api_key_invalid")}):$.push({name:T,status:"fail",message:`${i.t("doctor.network_error")}: ${t.message??t.error}`})}else $.push({name:T,status:"fail",message:i.t("doctor.api_key_missing")});w.text=i.t("doctor.check_tools");const k=c.getInstalledTools();$.push({name:i.t("doctor.installed_tools"),status:k.length>0?"pass":"warn",message:k.length>0?k.map(t=>d(t)?.displayName??t).join(", "):i.t("doctor.no_tools_installed")}),$.push(...function(){const t=[];for(const o of c.getInstalledTools()){const s=d(o);if(!s?.installSpec)continue;const e=u(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(p).join("、")})),t.push({name:s.displayName,status:a.length>0?"warn":"pass",message:[p(e[0]),...a,...a.length>0?[i.t("doctor.run_upgrade",{tool:o})]:[]].join("\n ")})}return t}());const b=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(f(),".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")}}();b&&$.push(b),k.includes("claude-code")&&g()&&$.push({name:i.t("doctor.claude_login"),status:"pass",message:i.t("tools.claude_code.account_login_overridden")});const v=k.includes("codex")?_():null;k.includes("codex")&&$.push({name:i.t("doctor.codex_auth"),status:v?"fail":"pass",message:v?i.t(`doctor.codex_${v}`):void 0}),"win32"===process.platform&&k.includes("claude-code")&&!process.env.CLAUDE_CODE_GIT_BASH_PATH&&$.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&&k.includes("claude-code")&&(w.text=i.t("doctor.check_claude_request"),$.push(function(){const t=i.t("doctor.claude_request"),o=u(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})}}())),w.stop(),console.log("\n"+t.bold(i.t("doctor.results_title"))+"\n");const A={pass:t.green("√"),warn:t.yellow("!"),fail:t.red("×")};for(const o of $)console.log(`${A[o.status]} ${o.name}`),o.message&&console.log(t.gray(` ${o.message}`));$.every(t=>"pass"===t.status)&&console.log("\n"+t.green.bold(i.t("doctor.all_good"))),!a.deep&&k.includes("claude-code")&&console.log(t.gray("\n"+i.t("doctor.deep_hint"))),$.some(t=>"fail"===t.status)&&(process.exitCode=1)}
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 r}from"path";import{configManager as n}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 u,installLabel as p}from"../lib/tools/tool-installs.js";import{detectManagedOverrides as m}from"../lib/tools/claude-managed.js";import{codexAuthProblem as _}from"../lib/tools/codex.js";import{claudeAccountLoginPresent as g}from"../lib/tools/claude-code.js";import{toolHome as f}from"../lib/tools/types.js";import{gatewayAddresses as h,probeGatewayAddress as y}from"../lib/gateway-addresses.js";const w=["HTTPS_PROXY","https_proxy","HTTP_PROXY","http_proxy","ALL_PROXY","all_proxy"];function j(t,o,s=3e3){return new Promise(e=>{const r=a({host:t,port:o}),n=t=>{r.destroy(),e(t)};r.setTimeout(s,()=>n(!1)),r.once("connect",()=>n(!0)),r.once("error",()=>n(!1))})}export async function doctorCommand(a={}){const x=o(i.t("doctor.checking")).start(),$=[],k=Number(process.versions.node.split(".")[0]);$.push({name:"Node.js",status:k>=20?"pass":"fail",message:`v${process.versions.node}`}),x.text=i.t("doctor.check_proxy"),$.push(await async function(){const t=i.t("doctor.proxy"),o=new Map;for(const t of w){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 j(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(", ")})}}()),x.text=i.t("doctor.check_api_key_network");const T=n.getApiKey(),b=i.t("doctor.api_key_network");if(T){const t=await l(T);t.valid?$.push({name:b,status:"pass"}):"invalid_api_key"===t.error?$.push({name:b,status:"fail",message:i.t("doctor.api_key_invalid")}):($.push({name:b,status:"fail",message:`${i.t("doctor.network_error")}: ${t.message??t.error}`}),x.text=i.t("doctor.check_other_addresses"),$.push(await async function(){const t=i.t("doctor.other_addresses"),o=n.getBaseUrl(),s=h().filter(t=>t.url!==o),e=await Promise.all(s.map(t=>y(t.url))),a=s.filter((t,o)=>e[o].ok).map(t=>t.url);return 0===a.length?{name:t,status:"fail",message:i.t("doctor.other_addresses_none")}:{name:t,status:"warn",message:i.t("doctor.other_addresses_found",{urls:a.join(", ")})}}()))}else $.push({name:b,status:"fail",message:i.t("doctor.api_key_missing")});x.text=i.t("doctor.check_tools");const v=c.getInstalledTools();$.push({name:i.t("doctor.installed_tools"),status:v.length>0?"pass":"warn",message:v.length>0?v.map(t=>d(t)?.displayName??t).join(", "):i.t("doctor.no_tools_installed")}),$.push(...function(){const t=[];for(const o of c.getInstalledTools()){const s=d(o);if(!s?.installSpec)continue;const e=u(s.installSpec);if(0===e.length)continue;const a=[],r=c.outdatedVersion(o);r&&a.push(i.t("doctor.tool_outdated",{min:r.min})),e.length>1&&a.push(i.t("doctor.tool_duplicates",{others:e.slice(1).map(p).join("、")})),t.push({name:s.displayName,status:a.length>0?"warn":"pass",message:[p(e[0]),...a,...a.length>0?[i.t("doctor.run_upgrade",{tool:o})]:[]].join("\n ")})}return t}());const A=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(r(f(),".claude","settings.json"),"utf-8")).env??{}}catch{}const a=n.getApiKeyForTool("claude-code");return s.ANTHROPIC_AUTH_TOKEN&&s.ANTHROPIC_BASE_URL?s.ANTHROPIC_BASE_URL!==n.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")}}();A&&$.push(A),v.includes("claude-code")&&g()&&$.push({name:i.t("doctor.claude_login"),status:"pass",message:i.t("tools.claude_code.account_login_overridden")});const N=v.includes("codex")?_():null;v.includes("codex")&&$.push({name:i.t("doctor.codex_auth"),status:N?"fail":"pass",message:N?i.t(`doctor.codex_${N}`):void 0}),"win32"===process.platform&&v.includes("claude-code")&&!process.env.CLAUDE_CODE_GIT_BASH_PATH&&$.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&&v.includes("claude-code")&&(x.text=i.t("doctor.check_claude_request"),$.push(function(){const t=i.t("doctor.claude_request"),o=u(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."],r=/\.cmd$/i.test(o.path)?s(`"${o.path}" ${a.map(t=>`"${t}"`).join(" ")}`,{...e,shell:!0}):s(o.path,a,e),n=`${r.stdout??""}`.trim();if(0===r.status&&n)return{name:t,status:"pass",message:n.slice(0,80)};const c=(`${r.stderr??""}`.trim()||n||r.error?.message||`exit ${r.status}`).slice(0,300);return{name:t,status:"fail",message:i.t("doctor.claude_request_failed",{reason:c})}}())),x.stop(),console.log("\n"+t.bold(i.t("doctor.results_title"))+"\n");const O={pass:t.green("√"),warn:t.yellow("!"),fail:t.red("×")};for(const o of $)console.log(`${O[o.status]} ${o.name}`),o.message&&console.log(t.gray(` ${o.message}`));$.every(t=>"pass"===t.status)&&console.log("\n"+t.green.bold(i.t("doctor.all_good"))),!a.deep&&v.includes("claude-code")&&console.log(t.gray("\n"+i.t("doctor.deep_hint"))),$.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{postApplyPrompts as t}from"../lib/post-apply.js";import{i18n as s}from"../lib/i18n.js";import{configManager as i}from"../lib/config.js";import{toolManager as l,SUPPORTED_TOOLS as a}from"../lib/tool-manager.js";import{validateApiKey as d,fetchModels as n}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_FORCE_REGISTRY_SETTING as u,CLAUDE_DESKTOP_HIDE_OFFICIAL_LOGIN_SETTING as f}from"../lib/tools/claude-desktop.js";async function g(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 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 k(o){console.log(e.green(o))}function y(o){console.log(e.yellow(o))}function _(o){console.error(e.red(o))}async function b(o,t){console.log(e.gray(s.t("setup.validating",{label:o})));const i=await d(t);return!!i.valid||(_(s.t("setup.invalid_key",{label:o,reason:i.message||i.error||"invalid"})),!1)}async function h(o,t,d={}){const{oneMillion:n,officialLogin:p,desktopRegistry:c,force:g}=d,m=a[o];try{"claude-desktop"===o&&void 0!==c&&i.setToolSetting("claude-desktop",u,c),"codex"===o&&void 0!==n&&i.setToolSetting("codex",r,n),"claude-desktop"===o&&void 0!==p&&i.setToolSetting("claude-desktop",f,!p),await l.applyPrivateConfig(o,t,i.getModelsForTool(o),{force:g})}catch(e){const o=(e instanceof Error?e.message:String(e)).replace(/\s+/g," ").trim();return _("× "+s.t("setup.apply_failed",{tool:m.displayName,reason:o})),!1}return l.printApplied(o),g&&console.log(e.gray(" "+s.t("config.force_done",{tool:m.displayName}))),"codex"===o&&void 0!==n&&console.log(e.gray(" "+s.t(n?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))),l.isToolInstalled(o)||console.log(e.yellow(" ! "+s.t("setup.install_hint",{tool:m.displayName,install:m.installCommand}))),!0}export async function setupCommand(e){const d=e.key?.trim(),n=e.claudekey?.trim(),u=e.codexkey?.trim(),f=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 _(s.t("setup.claude_official_invalid",{value:String(e.claudeOfficial)})),void(process.exitCode=1);const K=void 0!==e.desktopRegistry;let M;if(K&&(M=parseOnOff(e.desktopRegistry),void 0===M))return _(s.t("setup.desktop_registry_invalid",{value:String(e.desktopRegistry)})),void(process.exitCode=1);const S=C||K,j=Boolean(d||n||u||f||x),G={oneMillion:e.oneMillion,officialLogin:w,desktopRegistry:M,force:e.force};if(!j&&(v||S)){if(v){const o=i.getApiKeyForTool("codex");if(o)try{const t=!0===e.oneMillion;i.setToolSetting("codex",r,t);const l=await c(o);p(t,l??void 0),k(s.t(t?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))}catch(e){_(s.t("setup.apply_failed",{tool:a.codex.displayName,reason:e instanceof Error?e.message:String(e)})),process.exitCode=1}else _(s.t("setup.codex_1m_missing_key")),process.exitCode=1}if(S){const e=i.getApiKeyForTool("claude-desktop");e?await h("claude-desktop",e,{...G,oneMillion:void 0})?await t(["claude-desktop"]):process.exitCode=1:(_(s.t("setup.desktop_official_login_missing_key")),process.exitCode=1)}return}if(!j)return _(s.t("setup.usage")),void(process.exitCode=1);let T=0,O=0;const A=new Set,N=[];n&&N.push({tool:"claude-code",label:"--claudekey",apiKey:n,need:"claude"}),u&&N.push({tool:"codex",label:"--codexkey",apiKey:u,need:"gpt"}),f&&N.push({tool:"grok",label:"--grokkey",apiKey:f,need:"grok"}),x&&N.push({tool:"claude-desktop",label:"--desktopkey",apiKey:x,need:"claude"});for(const e of N){if(!await b(e.label,e.apiKey)){O+=1;continue}const o=await g(e.apiKey);!o.probed||m(o,e.need)?(o.probed||y(s.t("setup.probe_failed",{label:e.label})),i.setToolApiKey(e.tool,e.apiKey),await h(e.tool,e.apiKey,G)?(T+=1,A.add(e.tool)):O+=1):(_(s.t(`setup.key_no_${e.need}`,{label:e.label,tool:a[e.tool].displayName})),O+=1)}if(d){if(!await b("--key",d))return void(process.exitCode=1);i.setApiKey(d);const e=await g(d);if(e.probed||y(s.t("setup.probe_failed",{label:"--key"})),e.probed&&!e.hasClaude&&!e.hasGpt&&!e.hasGrok)return _(s.t("setup.key_no_family")),void(process.exitCode=1);const o=[{tool:"claude-code",capable:e.hasClaude,skipMsg:s.t("setup.skip_no_claude")},{tool:"codex",capable:e.hasGpt,skipMsg:s.t("setup.skip_no_gpt")},{tool:"grok",capable:e.hasGrok,skipMsg:s.t("setup.skip_no_grok")},{tool:"claude-desktop",capable:e.hasClaude&&l.isToolInstalled("claude-desktop"),skipMsg:s.t("setup.skip_desktop_not_installed"),quiet:!e.hasClaude}];for(const e of o)A.has(e.tool)||(e.capable?await h(e.tool,d,G)?(T+=1,A.add(e.tool)):O+=1:e.quiet||y(e.skipMsg))}if(T>0){const e=[...A].filter(e=>!o(e)?.isInstalled).map(e=>a[e]?.command).filter(Boolean).join(" / "),t=s.t("setup.done",{count:String(T)});k("\n"+(e?`${t}${s.t("setup.done_commands",{commands:e})}`:t))}O>0&&_(s.t("setup.failed_summary",{count:String(O)})),await t([...A]),(O>0||0===T)&&(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 i}from"../lib/config.js";import{toolManager as l,SUPPORTED_TOOLS as a}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_FORCE_REGISTRY_SETTING as u,CLAUDE_DESKTOP_HIDE_OFFICIAL_LOGIN_SETTING as f}from"../lib/tools/claude-desktop.js";async function g(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 k(o){console.log(e.green(o))}function y(o){console.log(e.yellow(o))}function _(o){console.error(e.red(o))}async function b(o,t){console.log(e.gray(s.t("setup.validating",{label:o})));const i=await n(t);return!!i.valid||(_(s.t("setup.invalid_key",{label:o,reason:i.message||i.error||"invalid"})),"network_error"===i.error&&y(s.t("base.network_hint")),!1)}async function h(o,t,n={}){const{oneMillion:d,officialLogin:p,desktopRegistry:c,force:g}=n,m=a[o];try{"claude-desktop"===o&&void 0!==c&&i.setToolSetting("claude-desktop",u,c),"codex"===o&&void 0!==d&&i.setToolSetting("codex",r,d),"claude-desktop"===o&&void 0!==p&&i.setToolSetting("claude-desktop",f,!p),await l.applyPrivateConfig(o,t,i.getModelsForTool(o),{force:g})}catch(e){const o=(e instanceof Error?e.message:String(e)).replace(/\s+/g," ").trim();return _("× "+s.t("setup.apply_failed",{tool:m.displayName,reason:o})),!1}return l.printApplied(o),g&&console.log(e.gray(" "+s.t("config.force_done",{tool:m.displayName}))),"codex"===o&&void 0!==d&&console.log(e.gray(" "+s.t(d?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))),l.isToolInstalled(o)||console.log(e.yellow(" ! "+s.t("setup.install_hint",{tool:m.displayName,install:m.installCommand}))),!0}export async function setupCommand(e){const n=e.key?.trim(),d=e.claudekey?.trim(),u=e.codexkey?.trim(),f=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 _(s.t("setup.claude_official_invalid",{value:String(e.claudeOfficial)})),void(process.exitCode=1);const K=void 0!==e.desktopRegistry;let M;if(K&&(M=parseOnOff(e.desktopRegistry),void 0===M))return _(s.t("setup.desktop_registry_invalid",{value:String(e.desktopRegistry)})),void(process.exitCode=1);const S=C||K,j=Boolean(n||d||u||f||x),G={oneMillion:e.oneMillion,officialLogin:w,desktopRegistry:M,force:e.force};if(!j&&(v||S)){if(v){const o=i.getApiKeyForTool("codex");if(o)try{const t=!0===e.oneMillion;i.setToolSetting("codex",r,t);const l=await c(o);p(t,l??void 0),k(s.t(t?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))}catch(e){_(s.t("setup.apply_failed",{tool:a.codex.displayName,reason:e instanceof Error?e.message:String(e)})),process.exitCode=1}else _(s.t("setup.codex_1m_missing_key")),process.exitCode=1}if(S){const e=i.getApiKeyForTool("claude-desktop");e?await h("claude-desktop",e,{...G,oneMillion:void 0})?await t(["claude-desktop"]):process.exitCode=1:(_(s.t("setup.desktop_official_login_missing_key")),process.exitCode=1)}return}if(!j)return _(s.t("setup.usage")),void(process.exitCode=1);let T=0,O=0;const A=new Set,N=[];d&&N.push({tool:"claude-code",label:"--claudekey",apiKey:d,need:"claude"}),u&&N.push({tool:"codex",label:"--codexkey",apiKey:u,need:"gpt"}),f&&N.push({tool:"grok",label:"--grokkey",apiKey:f,need:"grok"}),x&&N.push({tool:"claude-desktop",label:"--desktopkey",apiKey:x,need:"claude"});for(const e of N){if(!await b(e.label,e.apiKey)){O+=1;continue}const o=await g(e.apiKey);!o.probed||m(o,e.need)?(o.probed||y(s.t("setup.probe_failed",{label:e.label})),i.setToolApiKey(e.tool,e.apiKey),await h(e.tool,e.apiKey,G)?(T+=1,A.add(e.tool)):O+=1):(_(s.t(`setup.key_no_${e.need}`,{label:e.label,tool:a[e.tool].displayName})),O+=1)}if(n){if(!await b("--key",n))return void(process.exitCode=1);i.setApiKey(n);const e=await g(n);if(e.probed||y(s.t("setup.probe_failed",{label:"--key"})),e.probed&&!e.hasClaude&&!e.hasGpt&&!e.hasGrok)return _(s.t("setup.key_no_family")),void(process.exitCode=1);const o=[{tool:"claude-code",capable:e.hasClaude,skipMsg:s.t("setup.skip_no_claude")},{tool:"codex",capable:e.hasGpt,skipMsg:s.t("setup.skip_no_gpt")},{tool:"grok",capable:e.hasGrok,skipMsg:s.t("setup.skip_no_grok")},{tool:"claude-desktop",capable:e.hasClaude&&l.isToolInstalled("claude-desktop"),skipMsg:s.t("setup.skip_desktop_not_installed"),quiet:!e.hasClaude}];for(const e of o)A.has(e.tool)||(e.capable?await h(e.tool,n,G)?(T+=1,A.add(e.tool)):O+=1:e.quiet||y(e.skipMsg))}if(T>0){const e=[...A].filter(e=>!o(e)?.isInstalled).map(e=>a[e]?.command).filter(Boolean).join(" / "),t=s.t("setup.done",{count:String(T)});k("\n"+(e?`${t}${s.t("setup.done_commands",{commands:e})}`:t))}O>0&&_(s.t("setup.failed_summary",{count:String(O)})),await t([...A]),(O>0||0===T)&&(process.exitCode=1)}
@@ -1 +1 @@
1
- import{logger as e}from"../utils/logger.js";import{configManager as t}from"./config.js";import{i18n as r}from"./i18n.js";const o=e=>new Promise(t=>setTimeout(t,e));async function n(e,o,n){const i=`${t.getBaseUrl()}/v1/models`,s=new AbortController,l=setTimeout(()=>s.abort(),o);try{const t=await n(i,{method:"GET",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},signal:s.signal});if(401===t.status)return{valid:!1,error:"invalid_api_key",message:"API Key is invalid or expired"};if(t.ok){try{const e=await t.json();if(e&&"list"===e.object)return{valid:!0}}catch{return{valid:!0}}return{valid:!0}}return{valid:!1,error:"unknown_error",message:`HTTP ${t.status}: ${t.statusText}`}}catch(e){return e instanceof Error&&"AbortError"===e.name?{valid:!1,error:"network_error",message:"Request timeout"}:function(e){const t=e?.cause?.code;return"string"==typeof t&&a.has(t)}(e)?{valid:!1,error:"network_error",message:r.t("messages.certificate_intercepted",{url:i})}:{valid:!1,error:"network_error",message:e instanceof Error?e.message:"Network connection failed"}}finally{clearTimeout(l)}}const a=new Set(["DEPTH_ZERO_SELF_SIGNED_CERT","SELF_SIGNED_CERT_IN_CHAIN","UNABLE_TO_VERIFY_LEAF_SIGNATURE","UNABLE_TO_GET_ISSUER_CERT_LOCALLY","CERT_UNTRUSTED","ERR_TLS_CERT_ALTNAME_INVALID"]);export async function fetchModels(r,o={}){const{timeoutMs:n=3e4,fetchFn:a=fetch}=o,i=`${t.getBaseUrl()}/v1/models`,s=new AbortController,l=setTimeout(()=>s.abort(),n);try{const e=await a(i,{method:"GET",headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},signal:s.signal});if(!e.ok)return{ok:!1,models:[],error:`HTTP ${e.status}`};const t=await e.json();return{ok:!0,models:(Array.isArray(t?.data)?t.data:[]).map(e=>"string"==typeof e?e:e?.id).filter(e=>"string"==typeof e&&e.trim().length>0).map(e=>e.trim())}}catch(t){return e.logError("fetchModels",t),{ok:!1,models:[],error:t instanceof Error?t.message:"network error"}}finally{clearTimeout(l)}}function i(e){return e.filter(e=>!!e&&"object"==typeof e&&"string"==typeof e.name).map(e=>({name:e.name.trim(),..."string"==typeof e.labelOverride?{labelOverride:e.labelOverride}:{},..."boolean"==typeof e.supports1m?{supports1m:e.supports1m}:{}})).filter(e=>e.name.length>0)}export async function fetchGatewayStatus(r={}){const{timeoutMs:o=15e3,fetchFn:n=fetch}=r,a=`${t.getBaseUrl()}/api/status`,s=new AbortController,l=setTimeout(()=>s.abort(),o);try{const e=await n(a,{method:"GET",signal:s.signal});if(!e.ok)return{ok:!1,claudeDesktopGroupModels:{},error:`HTTP ${e.status}`};const t=await e.json();let r=t?.data?.claude_desktop_group_models;if("string"==typeof r)try{r=JSON.parse(r)}catch{r=void 0}const o={};if(r&&"object"==typeof r&&!Array.isArray(r))for(const[e,t]of Object.entries(r))Array.isArray(t)&&(o[e]=i(t));return{ok:!0,claudeDesktopGroupModels:o}}catch(t){return e.logError("fetchGatewayStatus",t),{ok:!1,claudeDesktopGroupModels:{},error:t instanceof Error?t.message:"network error"}}finally{clearTimeout(l)}}export async function fetchKeyGroupInfo(r,o={}){const{timeoutMs:n=1e4,fetchFn:a=fetch}=o,s=`${t.getBaseUrl()}/api/platform/vibecode/key`,l=new AbortController,c=setTimeout(()=>l.abort(),n);try{const e=await a(s,{method:"GET",headers:{Authorization:`Bearer ${r}`},signal:l.signal});if(!e.ok)return null;const t=await e.json(),o=t?.data;if(!o||"string"!=typeof o.group||!Array.isArray(o.claude_desktop_models))return null;const n=Array.isArray(o.groups)?o.groups.filter(e=>"string"==typeof e&&!!e):[];return{group:o.group,groups:n.length>0?n:[o.group],claudeDesktopModels:i(o.claude_desktop_models)}}catch(t){return e.logError("fetchKeyGroupInfo",t),null}finally{clearTimeout(c)}}function s(e){return"number"==typeof e&&Number.isFinite(e)&&e>0?Math.floor(e):void 0}export async function fetchClientModelConfig(r={}){const{timeoutMs:o=1e4,fetchFn:n=fetch}=r,a=`${t.getBaseUrl()}/api/platform/vibecode/config`,i=new AbortController,l=setTimeout(()=>i.abort(),o);try{const e=await n(a,{method:"GET",signal:i.signal});if(!e.ok)return null;const t=await e.json(),r=t?.data?.models;if(!r||"object"!=typeof r||Array.isArray(r))return null;const o={};for(const[e,t]of Object.entries(r))Array.isArray(t)&&(o[e]=t.filter(e=>!!e&&"object"==typeof e&&"string"==typeof e.id).filter(e=>!0!==e.hidden&&e.id.trim().length>0).map(e=>{const t={id:e.id.trim()};"string"==typeof e.name&&e.name.trim()&&(t.name=e.name.trim()),"string"==typeof e.description&&e.description.trim()&&(t.description=e.description.trim());const r=s(e.context_window);r&&(t.contextWindow=r);const o=s(e.auto_compact_tokens);return o&&(t.autoCompactTokens=o),t}));return o}catch(t){return e.logError("fetchClientModelConfig",t),null}finally{clearTimeout(l)}}export async function validateApiKey(t,r={}){const{timeoutMs:a=3e4,retries:i=2,backoffMs:s=500,fetchFn:l=fetch,sleepFn:c=o}=r;let u={valid:!1,error:"network_error",message:"Network connection failed"};for(let r=0;r<=i;r++){const o=await n(t,a,l);if(o.valid||"invalid_api_key"===o.error)return o.valid||e.logError("validateApiKey",new Error(o.message||"invalid_api_key")),o;u=o,r<i&&await c(s*Math.pow(2,r))}return e.logError("validateApiKey",new Error(u.message||"network_error")),u}
1
+ import{logger as e}from"../utils/logger.js";import{configManager as t}from"./config.js";import{i18n as r}from"./i18n.js";import{rememberPublishedAddresses as o}from"./gateway-addresses.js";const n=e=>new Promise(t=>setTimeout(t,e));async function a(e,o,n){const a=`${t.getBaseUrl()}/v1/models`,s=new AbortController,l=setTimeout(()=>s.abort(),o);try{const t=await n(a,{method:"GET",headers:{Authorization:`Bearer ${e}`,"Content-Type":"application/json"},signal:s.signal});if(401===t.status)return{valid:!1,error:"invalid_api_key",message:"API Key is invalid or expired"};if(t.ok){try{const e=await t.json();if(e&&"list"===e.object)return{valid:!0}}catch{return{valid:!0}}return{valid:!0}}return{valid:!1,error:"unknown_error",message:`HTTP ${t.status}: ${t.statusText}`}}catch(e){return e instanceof Error&&"AbortError"===e.name?{valid:!1,error:"network_error",message:"Request timeout"}:function(e){const t=e?.cause?.code;return"string"==typeof t&&i.has(t)}(e)?{valid:!1,error:"network_error",message:r.t("messages.certificate_intercepted",{url:a})}:{valid:!1,error:"network_error",message:e instanceof Error?e.message:"Network connection failed"}}finally{clearTimeout(l)}}const i=new Set(["DEPTH_ZERO_SELF_SIGNED_CERT","SELF_SIGNED_CERT_IN_CHAIN","UNABLE_TO_VERIFY_LEAF_SIGNATURE","UNABLE_TO_GET_ISSUER_CERT_LOCALLY","CERT_UNTRUSTED","ERR_TLS_CERT_ALTNAME_INVALID"]);export async function fetchModels(r,o={}){const{timeoutMs:n=3e4,fetchFn:a=fetch}=o,i=`${t.getBaseUrl()}/v1/models`,s=new AbortController,l=setTimeout(()=>s.abort(),n);try{const e=await a(i,{method:"GET",headers:{Authorization:`Bearer ${r}`,"Content-Type":"application/json"},signal:s.signal});if(!e.ok)return{ok:!1,models:[],error:`HTTP ${e.status}`};const t=await e.json();return{ok:!0,models:(Array.isArray(t?.data)?t.data:[]).map(e=>"string"==typeof e?e:e?.id).filter(e=>"string"==typeof e&&e.trim().length>0).map(e=>e.trim())}}catch(t){return e.logError("fetchModels",t),{ok:!1,models:[],error:t instanceof Error?t.message:"network error"}}finally{clearTimeout(l)}}function s(e){return e.filter(e=>!!e&&"object"==typeof e&&"string"==typeof e.name).map(e=>({name:e.name.trim(),..."string"==typeof e.labelOverride?{labelOverride:e.labelOverride}:{},..."boolean"==typeof e.supports1m?{supports1m:e.supports1m}:{}})).filter(e=>e.name.length>0)}export async function fetchGatewayStatus(r={}){const{timeoutMs:n=15e3,fetchFn:a=fetch}=r,i=`${t.getBaseUrl()}/api/status`,l=new AbortController,c=setTimeout(()=>l.abort(),n);try{const e=await a(i,{method:"GET",signal:l.signal});if(!e.ok)return{ok:!1,claudeDesktopGroupModels:{},error:`HTTP ${e.status}`};const r=await e.json();o(r?.data,t.getBaseUrl());let n=r?.data?.claude_desktop_group_models;if("string"==typeof n)try{n=JSON.parse(n)}catch{n=void 0}const c={};if(n&&"object"==typeof n&&!Array.isArray(n))for(const[e,t]of Object.entries(n))Array.isArray(t)&&(c[e]=s(t));return{ok:!0,claudeDesktopGroupModels:c}}catch(t){return e.logError("fetchGatewayStatus",t),{ok:!1,claudeDesktopGroupModels:{},error:t instanceof Error?t.message:"network error"}}finally{clearTimeout(c)}}export async function fetchKeyGroupInfo(r,o={}){const{timeoutMs:n=1e4,fetchFn:a=fetch}=o,i=`${t.getBaseUrl()}/api/platform/vibecode/key`,l=new AbortController,c=setTimeout(()=>l.abort(),n);try{const e=await a(i,{method:"GET",headers:{Authorization:`Bearer ${r}`},signal:l.signal});if(!e.ok)return null;const t=await e.json(),o=t?.data;if(!o||"string"!=typeof o.group||!Array.isArray(o.claude_desktop_models))return null;const n=Array.isArray(o.groups)?o.groups.filter(e=>"string"==typeof e&&!!e):[];return{group:o.group,groups:n.length>0?n:[o.group],claudeDesktopModels:s(o.claude_desktop_models)}}catch(t){return e.logError("fetchKeyGroupInfo",t),null}finally{clearTimeout(c)}}function l(e){return"number"==typeof e&&Number.isFinite(e)&&e>0?Math.floor(e):void 0}export async function fetchClientModelConfig(r={}){const{timeoutMs:o=1e4,fetchFn:n=fetch}=r,a=`${t.getBaseUrl()}/api/platform/vibecode/config`,i=new AbortController,s=setTimeout(()=>i.abort(),o);try{const e=await n(a,{method:"GET",signal:i.signal});if(!e.ok)return null;const t=await e.json(),r=t?.data?.models;if(!r||"object"!=typeof r||Array.isArray(r))return null;const o={};for(const[e,t]of Object.entries(r))Array.isArray(t)&&(o[e]=t.filter(e=>!!e&&"object"==typeof e&&"string"==typeof e.id).filter(e=>!0!==e.hidden&&e.id.trim().length>0).map(e=>{const t={id:e.id.trim()};"string"==typeof e.name&&e.name.trim()&&(t.name=e.name.trim()),"string"==typeof e.description&&e.description.trim()&&(t.description=e.description.trim());const r=l(e.context_window);r&&(t.contextWindow=r);const o=l(e.auto_compact_tokens);return o&&(t.autoCompactTokens=o),t}));return o}catch(t){return e.logError("fetchClientModelConfig",t),null}finally{clearTimeout(s)}}export async function validateApiKey(t,r={}){const{timeoutMs:o=3e4,retries:i=2,backoffMs:s=500,fetchFn:l=fetch,sleepFn:c=n}=r;let u={valid:!1,error:"network_error",message:"Network connection failed"};for(let r=0;r<=i;r++){const n=await a(t,o,l);if(n.valid||"invalid_api_key"===n.error)return n.valid||e.logError("validateApiKey",new Error(n.message||"invalid_api_key")),n;u=n,r<i&&await c(s*Math.pow(2,r))}return e.logError("validateApiKey",new Error(u.message||"network_error")),u}
@@ -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 d}from"../commands/logs.js";import{completionCommand as p}from"../commands/completion.js";import{benchCommand as l}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{codexSessionsCommand as h}from"../commands/codex-sessions.js";import{setupCommand as y}from"../commands/setup.js";import k from"chalk";import{readFileSync as b}from"fs";import{join as v,dirname as w}from"path";import{fileURLToPath as $}from"url";import{setRuntimeOptions as x,detectColorPreferenceFromEnv as C,isColorEnabled as j,isJsonMode as A}from"./runtime-options.js";import{cleanupAnthropicEnvVars as I,machineEnvVarsLeft as P}from"./env-cleanup.js";import{runStartupUpdate as D}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=$(import.meta.url),o=w(e),a=v(o,"../../package.json");return JSON.parse(b(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("--desktop-registry <on|off>","Windows Claude Desktop: always write the config to the registry policy (on, asks for admin once if needed) or decide automatically (off)").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 y({key:e.key,claudekey:e.claudekey,codexkey:e.codexkey,grokkey:e.grokkey,desktopkey:e.desktopkey,all:e.all,oneMillion:e["1m"],claudeOfficial:e.claudeOfficial,desktopRegistry:e.desktopRegistry,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 d(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("codex").description(o.t("commands.codex")).command("sessions").description(o.t("commands.codex_sessions")).option("--to <provider>","Provider to move the sessions under (default: the one Codex uses now)").option("--undo <dir>","Put the sessions back as recorded in this backup directory").action(async e=>{await h(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 l(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${k.bold(o.t("cli.examples"))}:\n ${k.gray("$ vibecli # Interactive main menu")}\n ${k.gray("$ vibecli init # Run first-time setup wizard")}\n ${k.gray("$ vibecli setup --key sk-xxx # One-shot setup (auto-detect capable tools)")}\n ${k.gray("$ vibecli setup --claudekey sk-a --codexkey sk-b --grokkey sk-c")}\n ${k.gray("$ vibecli setup --desktopkey sk-d --claude-official off # Claude Desktop, hide Claude.ai login")}\n ${k.gray("$ vibecli setup --key sk-xxx --force # Replace broken tool configs with the recommended ones")}\n ${k.gray("$ vibecli config codex --force # Same, for one tool")}\n ${k.gray("$ vibecli status --json # Machine-readable status")}\n ${k.gray("$ vibecli config profile list")}\n ${k.gray("$ vibecli config profile create work")}\n ${k.gray("$ vibecli config profile use work")}\n ${k.gray("$ vibecli config preset save fast")}\n ${k.gray("$ vibecli config backup create snapshot")}\n ${k.gray("$ vibecli auth <token> --non-interactive")}\n ${k.gray("$ vibecli auth revoke --yes")}\n ${k.gray("$ vibecli logs path")}\n ${k.gray("$ vibecli completion bash")}\n ${k.gray("$ vibecli bench --samples 3")}\n ${k.gray("$ vibecli update")}\n ${k.gray("$ vibecli upgrade claude-code")}\n ${k.gray('$ vibecli --base "https://custom.api.com" init # Custom base URL')}\n`)}async handleInitCommand(){await i.runFirstTimeSetup()}cleanupLeakedEnvVars(){const e=I();if(0===e.length||A())return;const a=o.t("messages.env_vars_cleared",{vars:e.join(", ")});console.log(j()?k.yellow(a):a);const t=P();if(t.length>0){const e=o.t("messages.env_vars_machine_left",{vars:t.join(", ")});console.log(j()?k.yellow(e):e)}}preflightArgs(e){const o=o=>e.includes(o),a=!o("--no-color")&&C();let i;x({nonInteractive:o("--non-interactive"),json:o("--json"),yes:o("--yes")||o("-y"),dryRun:o("--dry-run"),color:a}),a||(k.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 D(e)).handled)return;await u(e),await this.program.parseAsync(e,{from:"user"})}catch(e){if(e instanceof Error){const a=j()?k.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 d}from"../commands/logs.js";import{completionCommand as p}from"../commands/completion.js";import{benchCommand as l}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{codexSessionsCommand as h}from"../commands/codex-sessions.js";import{setupCommand as y}from"../commands/setup.js";import{baseCommand as k}from"../commands/base.js";import{normalizeBaseUrl as b}from"./gateway-addresses.js";import w from"chalk";import{readFileSync as v}from"fs";import{join as $,dirname as x}from"path";import{fileURLToPath as C}from"url";import{setRuntimeOptions as j,detectColorPreferenceFromEnv as A,isColorEnabled as I,isJsonMode as P}from"./runtime-options.js";import{cleanupAnthropicEnvVars as D,machineEnvVarsLeft as R}from"./env-cleanup.js";import{runStartupUpdate as S}from"./auto-update.js";const _=new Set(["setup","init","auth","enter","config"]);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=C(import.meta.url),o=x(e),a=$(o,"../../package.json");return JSON.parse(v(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("--desktop-registry <on|off>","Windows Claude Desktop: always write the config to the registry policy (on, asks for admin once if needed) or decide automatically (off)").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 y({key:e.key,claudekey:e.claudekey,codexkey:e.codexkey,grokkey:e.grokkey,desktopkey:e.desktopkey,all:e.all,oneMillion:e["1m"],claudeOfficial:e.claudeOfficial,desktopRegistry:e.desktopRegistry,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("base [target]").description(o.t("commands.base")).action(async e=>{await k(e)}),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 d(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("codex").description(o.t("commands.codex")).command("sessions").description(o.t("commands.codex_sessions")).option("--to <provider>","Provider to move the sessions under (default: the one Codex uses now)").option("--undo <dir>","Put the sessions back as recorded in this backup directory").action(async e=>{await h(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 l(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${w.bold(o.t("cli.examples"))}:\n ${w.gray("$ vibecli # Interactive main menu")}\n ${w.gray("$ vibecli init # Run first-time setup wizard")}\n ${w.gray("$ vibecli setup --key sk-xxx # One-shot setup (auto-detect capable tools)")}\n ${w.gray("$ vibecli setup --claudekey sk-a --codexkey sk-b --grokkey sk-c")}\n ${w.gray("$ vibecli setup --desktopkey sk-d --claude-official off # Claude Desktop, hide Claude.ai login")}\n ${w.gray("$ vibecli setup --key sk-xxx --force # Replace broken tool configs with the recommended ones")}\n ${w.gray("$ vibecli config codex --force # Same, for one tool")}\n ${w.gray("$ vibecli status --json # Machine-readable status")}\n ${w.gray("$ vibecli config profile list")}\n ${w.gray("$ vibecli config profile create work")}\n ${w.gray("$ vibecli config profile use work")}\n ${w.gray("$ vibecli config preset save fast")}\n ${w.gray("$ vibecli config backup create snapshot")}\n ${w.gray("$ vibecli auth <token> --non-interactive")}\n ${w.gray("$ vibecli auth revoke --yes")}\n ${w.gray("$ vibecli logs path")}\n ${w.gray("$ vibecli completion bash")}\n ${w.gray("$ vibecli bench --samples 3")}\n ${w.gray("$ vibecli update")}\n ${w.gray("$ vibecli upgrade claude-code")}\n ${w.gray("$ vibecli base # List gateway addresses and test each one")}\n ${w.gray("$ vibecli base auto # Switch every tool to the first address that works")}\n ${w.gray("$ vibecli setup --key sk-xxx --base https://www.vibeapi.si # Configure on another address")}\n`)}async handleInitCommand(){await i.runFirstTimeSetup()}cleanupLeakedEnvVars(){const e=D();if(0===e.length||P())return;const a=o.t("messages.env_vars_cleared",{vars:e.join(", ")});console.log(I()?w.yellow(a):a);const t=R();if(t.length>0){const e=o.t("messages.env_vars_machine_left",{vars:t.join(", ")});console.log(I()?w.yellow(e):e)}}preflightArgs(e){const o=o=>e.includes(o),a=!o("--no-color")&&A();let i,n;j({nonInteractive:o("--non-interactive"),json:o("--json"),yes:o("--yes")||o("-y"),dryRun:o("--dry-run"),color:a}),a||(w.level=0);for(let o=0;o<e.length;o+=1){const a=e[o];"--base"===a&&e[o+1]?(i=e[o+1],o+=1):a.startsWith("--base=")?i=a.slice(7):n||a.startsWith("-")||(n=a)}if(i){const e=void 0===n&&!["--help","-h","--version","-v"].some(o)||void 0!==n&&_.has(n);t.setBaseUrl(b(i)??i,e)}}async execute(e){try{if(this.preflightArgs(e),this.cleanupLeakedEnvVars(),(await S(e)).handled)return;await u(e),await this.program.parseAsync(e,{from:"user"})}catch(e){if(e instanceof Error){const a=I()?w.red:e=>e;console.error(a(o.t("cli.error_general")),e.message)}process.exit(1)}}getProgram(){return this.program}}
@@ -0,0 +1,36 @@
1
+ import { type GatewayAddress } from './model-registry.js';
2
+ export type { GatewayAddress };
3
+ export type ProbeError = 'certificate' | 'timeout' | 'network' | 'not_gateway';
4
+ export interface ProbeResult {
5
+ url: string;
6
+ ok: boolean;
7
+ ms: number;
8
+ error?: ProbeError;
9
+ detail?: string;
10
+ }
11
+ /**
12
+ * `www.vibeapi.si`, `https://www.vibeapi.si/`, `https://www.vibeapi.si/v1` all
13
+ * mean the same base URL; null when the input is not an http(s) address.
14
+ */
15
+ export declare function normalizeBaseUrl(input: string): string | null;
16
+ /** Valid, de-duplicated entries of the gateway's `api_info` list; empty when it has none. */
17
+ export declare function parsePublishedAddresses(apiInfo: unknown): GatewayAddress[];
18
+ /**
19
+ * Caches the list from the `/api/status` payload `source` answered with. Only an
20
+ * address already trusted (built in, or on the cached list) may publish one: a
21
+ * profile pointed at some other gateway must not get to steer `vibecli base auto`,
22
+ * and with it the user's key, to addresses of its choosing. A gateway that
23
+ * publishes no list leaves the cache alone.
24
+ */
25
+ export declare function rememberPublishedAddresses(statusData: unknown, source: string): void;
26
+ /** Addresses to offer, in the operator's order: the gateway's published list, else the built-in one. */
27
+ export declare function gatewayAddresses(): GatewayAddress[];
28
+ /**
29
+ * One unauthenticated `GET /api/status`: reachable, valid certificate, and the
30
+ * answer looks like this gateway (a JSON `data` object). A successful answer
31
+ * also refreshes the cached address list.
32
+ */
33
+ export declare function probeGatewayAddress(url: string, options?: {
34
+ timeoutMs?: number;
35
+ fetchFn?: typeof fetch;
36
+ }): Promise<ProbeResult>;
@@ -0,0 +1 @@
1
+ import{existsSync as r,mkdirSync as e,readFileSync as t,writeFileSync as o}from"fs";import{join as s}from"path";import{configManager as n}from"./config.js";import{getBuiltinGatewayAddresses as i}from"./model-registry.js";import{logger as a}from"../utils/logger.js";const l=new Set(["SELF_SIGNED_CERT_IN_CHAIN","DEPTH_ZERO_SELF_SIGNED_CERT","UNABLE_TO_VERIFY_LEAF_SIGNATURE","UNABLE_TO_GET_ISSUER_CERT_LOCALLY","CERT_HAS_EXPIRED","CERT_NOT_YET_VALID","ERR_TLS_CERT_ALTNAME_INVALID"]);function u(){return s(n.getConfigDir(),"gateway-addresses.json")}export function normalizeBaseUrl(r){let e,t=r.trim();if(!t)return null;/^[a-z][a-z0-9+.-]*:\/\//i.test(t)||(t=`https://${t}`);try{e=new URL(t)}catch{return null}if("https:"!==e.protocol&&"http:"!==e.protocol)return null;const o=e.pathname.replace(/\/+$/,"").replace(/\/v1$/i,"");return`${e.protocol}//${e.host}${o}`}export function parsePublishedAddresses(r){if(!Array.isArray(r))return[];const e=new Set,t=[];for(const o of r){if(!o||"object"!=typeof o)continue;const{url:r,route:s,description:n}=o,i="string"==typeof r?normalizeBaseUrl(r):null;if(!i?.startsWith("https://")||e.has(i))continue;e.add(i);const a="string"==typeof s?s.trim():"",l="string"==typeof n?n.trim():"";t.push({url:i,label:a&&l?`${a}(${l})`:a||l})}return t}export function rememberPublishedAddresses(r,t){if(!r||"object"!=typeof r)return;if(!gatewayAddresses().some(r=>r.url===t))return;const s=r;if(!1===s.api_info_enabled)return;const i=parsePublishedAddresses(s.api_info);if(0!==i.length)try{e(n.getConfigDir(),{recursive:!0}),o(u(),JSON.stringify({addresses:i},null,2)+"\n")}catch(r){a.logError("rememberPublishedAddresses",r)}}export function gatewayAddresses(){const e=function(){const e=u();if(!r(e))return[];try{const r=JSON.parse(t(e,"utf-8"));return(Array.isArray(r.addresses)?r.addresses:[]).flatMap(r=>{const e="string"==typeof r?.url?normalizeBaseUrl(r.url):null;return e?[{url:e,label:"string"==typeof r.label?r.label:""}]:[]})}catch{return[]}}();return e.length>0?e:i().map(r=>({url:normalizeBaseUrl(r.url)??r.url,label:r.label})).filter((r,e,t)=>t.findIndex(e=>e.url===r.url)===e)}function c(r){const e=r?.cause,t="string"==typeof e?.code?e.code:"",o="string"==typeof e?.message?e.message:r instanceof Error?r.message:String(r);return r instanceof Error&&"AbortError"===r.name?{error:"timeout",detail:o}:l.has(t)||/certificate/i.test(o)?{error:"certificate",detail:t||o}:{error:"network",detail:t||o}}export async function probeGatewayAddress(r,e={}){const{timeoutMs:t=8e3,fetchFn:o=fetch}=e,s=new AbortController,n=setTimeout(()=>s.abort(),t),i=Date.now();try{const e=await o(`${r}/api/status`,{method:"GET",signal:s.signal}),t=Date.now()-i;let n;try{n=(await e.json())?.data}catch{n=void 0}return e.ok&&n&&"object"==typeof n?(rememberPublishedAddresses(n,r),{url:r,ok:!0,ms:t}):{url:r,ok:!1,ms:t,error:"not_gateway",detail:`HTTP ${e.status}`}}catch(e){return{url:r,ok:!1,ms:Date.now()-i,...c(e)}}finally{clearTimeout(n)}}
@@ -77,6 +77,13 @@ export interface ModelRegistryData {
77
77
  codex?: Partial<CodexRules>;
78
78
  /** Oldest tool release vibecli's written config works with (tool id → semver). */
79
79
  toolMinVersions?: Record<string, string>;
80
+ /** Addresses the gateway answers on, in the operator's order; used until the gateway's own list is cached. */
81
+ gatewayAddresses?: GatewayAddress[];
82
+ }
83
+ /** One address the gateway answers on (another domain or route to the same site). */
84
+ export interface GatewayAddress {
85
+ url: string;
86
+ label: string;
80
87
  }
81
88
  export declare function getDefaultModels(): Models;
82
89
  /** Earlier default for this slot, saved into a profile by the wizard's prefilled inputs. */
@@ -94,6 +101,7 @@ export declare function resolvePreset(preset: ModelPreset): Models;
94
101
  export declare function getClaudeCodeTierChains(): Record<string, string[]>;
95
102
  export declare function getClaudeDesktopRules(): ClaudeDesktopRules;
96
103
  export declare function getGatewayModelRules(): GatewayModelRules;
104
+ export declare function getBuiltinGatewayAddresses(): GatewayAddress[];
97
105
  export declare function getCodexRules(): CodexRules;
98
106
  /** Compare dotted numeric versions (`2.1.9` < `2.1.10`); missing parts count as 0. */
99
107
  export declare function compareVersions(a: string, b: string): number;
@@ -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 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}
1
+ import{dirname as e,join as t}from"path";import{fileURLToPath as o}from"url";import{existsSync as n,readFileSync as r}from"fs";const l=e(o(import.meta.url));let s=null;function i(){return s||(s=function(){const e=[t(l,"..","..","models.json"),t(l,"..","models.json"),t(l,"models.json")];for(const t of e)if(n(t))return JSON.parse(r(t,"utf-8"));throw new Error(`models.json not found, searched: ${e.join(", ")}`)}()),s}export function getDefaultModels(){return{...i().defaultModels}}export function isRetiredDefaultModel(e,t){return i().retiredDefaultModels?.[e]?.includes(t)??!1}export function getDefaultToolModel(){return i().defaultToolModel?.trim()||"gpt-5.5"}export function getToolDefaultModel(e){const t=i().toolDefaults;return t&&Object.prototype.hasOwnProperty.call(t,e)?(t[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,t])=>[e,[...t]]))}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 getBuiltinGatewayAddresses(){return(i().gatewayAddresses??[]).map(e=>({...e}))}export function getCodexRules(){return{standardContextWindow:i().codex?.standardContextWindow??272e3}}export function compareVersions(e,t){const o=e.split(".").map(e=>parseInt(e,10)||0),n=t.split(".").map(e=>parseInt(e,10)||0);for(let e=0;e<Math.max(o.length,n.length);e++){const t=(o[e]??0)-(n[e]??0);if(0!==t)return t}return 0}export function getToolMinVersion(e){return i().toolMinVersions?.[e]?.trim()||null}export function reloadRegistry(){s=null}
@@ -23,6 +23,8 @@ export declare class Wizard {
23
23
  skipLoadPrompt?: boolean;
24
24
  }): Promise<void>;
25
25
  promptLoadAfterModelConfig(): Promise<void>;
26
+ /** Pick another gateway address (each one tested first); every configured tool follows. */
27
+ configBaseUrl(): Promise<void>;
26
28
  configApiKey(): Promise<boolean>;
27
29
  selectAndConfigureTool(firstRun?: boolean): Promise<void>;
28
30
  configureTool(toolName: string, firstRun?: boolean): Promise<void>;
@@ -1 +1 @@
1
- import e from"inquirer";import t from"chalk";import o from"ora";import{configManager as a,DEFAULT_MODELS as i}from"./config.js";import{getPresets as n,resolvePreset as r,getToolDefaultModel as s}from"./model-registry.js";import{toolManager as l,SUPPORTED_TOOLS as c}from"./tool-manager.js";import{postApplyPrompts as d}from"./post-apply.js";import{i18n as p,detectSystemLang as m}from"./i18n.js";import{createBorderLine as u,createContentLine as g}from"../utils/string-width.js";import{validateApiKey as w,fetchModels as _}from"./api-validator.js";import{CODEX_ONE_MILLION_CONTEXT_SETTING as f}from"./tools/codex.js";import{CLAUDE_DESKTOP_HIDE_OFFICIAL_LOGIN_SETTING as y}from"./tools/claude-desktop.js";import{readFileSync as h}from"fs";import{dirname as z,join as v}from"path";import{fileURLToPath as k}from"url";const x=z(k(import.meta.url)),C=t.hex("#FF8700");export class Wizard{static instance;BOX_WIDTH=63;validatedApiKeys=new Set;constructor(){}static getInstance(){return Wizard.instance||(Wizard.instance=new Wizard),Wizard.instance}createBox(e){console.log(C.bold("\n"+u("╔","╗","═",this.BOX_WIDTH))),console.log(C.bold(g(e,"║","║",this.BOX_WIDTH,"center"))),console.log(C.bold(u("╚","╝","═",this.BOX_WIDTH))),console.log("")}showOperationHints(){const e=[t.gray(p.t("wizard.hint_navigate")),t.gray(p.t("wizard.hint_confirm"))];console.log(t.gray("💡 ")+e.join(t.gray(" | "))+"\n")}async promptWithHints(t){this.showOperationHints();const o=t.map(e=>"list"!==e.type||e.pageSize?e:{...e,pageSize:20});return e.prompt(o)}printBanner(){const e=65,t=p.t("wizard.banner_subtitle"),o=g(t,"║","║",e,"center"),a=g("","║","║",e,"center"),i=g(`API Helper v${function(){try{return JSON.parse(h(v(x,"..","..","package.json"),"utf-8")).version}catch{return"0.0.0"}}()}`,"║","║",e,"center"),n=[" ▄▀▄ █▀▄ █ █▄█ ██▀ █ █▀▄ ██▀ █▀▄ "," █▀█ █▀ █ █ █ █▄▄ █▄▄ █▀ █▄▄ █▀▄ "].map(t=>g(t,"║","║",e,"center")),r=[u("╔","╗","═",e),a,...n,a,i,o,u("╚","╝","═",e)];console.log(C.bold("\n"+r.join("\n")))}resetScreen(){console.clear(),this.printBanner()}printGoodbye(){const e=l.getSupportedTools().filter(e=>{try{return Boolean(l.detectCurrentConfig(e.name).apiKey)}catch{return!1}});let o;if(e.length>0){const t=e.map(e=>e.command).join(" / ");o=p.t("wizard.goodbye_message_tools",{commands:t}),e.some(e=>"claude-code"===e.name)&&(o+=" "+p.t("wizard.goodbye_claude_hint"))}else o=p.t("wizard.goodbye_message");console.log(t.green("\n👋 "+o))}async runFirstTimeSetup(){const e="zh_CN"===m();e&&(a.setLang("zh_CN"),p.setLocale("zh_CN")),this.resetScreen(),console.log(C.bold("\n"+p.t("wizard.welcome"))),console.log(t.gray(p.t("wizard.privacy_note")+"\n")),e?console.log(t.gray(p.t("wizard.lang_auto_zh")+"\n")):await this.configLanguage(),await this.configApiKey()&&await this.selectAndConfigureTool(!0)}async configLanguage(){for(;;){this.resetScreen(),this.createBox(p.t("wizard.select_language"));const o=p.getLocale(),{language:i}=await this.promptWithHints([{type:"list",name:"language",message:"✨ "+p.t("wizard.select_language"),choices:[{name:"[EN] English"+("en_US"===o?t.green(" ✓ ("+p.t("wizard.current_active")+")"):""),value:"en_US"},{name:"[CN] 中文"+("zh_CN"===o?t.green(" ✓ ("+p.t("wizard.current_active")+")"):""),value:"zh_CN"},new e.Separator,{name:"<- "+p.t("wizard.nav_return"),value:"back"},{name:"x "+p.t("wizard.nav_exit"),value:"exit"}],default:"zh_CN"}]);if("exit"===i)this.printGoodbye(),process.exit(0);else if("back"===i)return;return a.setLang(i),void p.setLocale(i)}}buildPresetChoices(){const o=n(),a=o.filter(e=>e.recommended),i=o.filter(e=>!e.recommended),r=e=>({name:(p.t(e.name)!==e.name?p.t(e.name):e.name)+(e.models?t.gray(` [H:${e.models.haiku} S:${e.models.sonnet} O:${e.models.opus}]`):""),value:{type:"preset",preset:e}}),s=[];return a.length>0&&(s.push(new e.Separator(t.hex("#FF8700")("── "+p.t("wizard.recommended")+" ──"))),s.push(...a.map(r))),i.length>0&&(s.push(new e.Separator(t.gray("── "+p.t("wizard.other_models")+" ──"))),s.push(...i.map(r))),s}async handleCustomModelInput(){const o=a.getModels(),{customMode:n}=await e.prompt([{type:"list",name:"customMode",message:p.t("wizard.custom_model_mode"),choices:[{name:p.t("wizard.custom_model_single"),value:"single"},{name:p.t("wizard.custom_model_per_role"),value:"per_role"}]}]);if("single"===n){const{customModel:t}=await e.prompt([{type:"input",name:"customModel",message:p.t("wizard.input_custom_model"),default:i.sonnet}]);a.setModels({haiku:t,sonnet:t,opus:t,reasoning:t})}else{const t=await e.prompt([{type:"input",name:"haiku",message:p.t("wizard.model_haiku"),default:o.haiku},{type:"input",name:"sonnet",message:p.t("wizard.model_sonnet"),default:o.sonnet},{type:"input",name:"opus",message:p.t("wizard.model_opus"),default:o.opus},{type:"input",name:"reasoning",message:p.t("wizard.model_reasoning"),default:o.reasoning}]);a.setModels(t)}console.log(t.green("\n✓ "+p.t("wizard.models_saved")))}async configModels(o){const i=a.getApiKey();if(await this.validatedKeyForConfiguration(i))for(;;){this.resetScreen(),this.createBox(p.t("wizard.config_models"));const i=[...this.buildPresetChoices()];i.push(new e.Separator),i.push({name:"✏️ "+p.t("wizard.custom_model_input"),value:{type:"custom_input"}}),i.push(new e.Separator),i.push({name:"<- "+p.t("wizard.nav_return"),value:{type:"back"}}),i.push({name:"x "+p.t("wizard.nav_exit"),value:{type:"exit"}});const{modelChoice:n}=await this.promptWithHints([{type:"list",name:"modelChoice",message:p.t("wizard.select_model_config"),choices:i}]);if("exit"===n.type)this.printGoodbye(),process.exit(0);else{if("back"===n.type)return;if("custom_input"===n.type)return await this.handleCustomModelInput(),void(o?.skipLoadPrompt||await this.promptLoadAfterModelConfig());if("preset"===n.type){const e=r(n.preset);return a.setModels(e),console.log(t.green("\n✓ "+p.t("wizard.models_saved"))),void(o?.skipLoadPrompt||await this.promptLoadAfterModelConfig())}}}else await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}])}async promptLoadAfterModelConfig(){const{shouldLoad:t}=await e.prompt([{type:"list",name:"shouldLoad",message:p.t("wizard.load_config_now"),choices:[{name:p.t("wizard.load_config_yes"),value:!0},{name:p.t("wizard.load_config_later"),value:!1}]}]);t&&await this.selectAndConfigureTool()}async configApiKey(){for(;;){this.resetScreen(),this.createBox(p.t("wizard.input_api_key")),console.log(t.gray("\n💡 "+p.t("wizard.api_key_input_hint"))),console.log(t.gray(" "+p.t("wizard.api_key_get_hint",{url:"https://www.vibeapi.cn/console/token"}))),console.log();const{apiKey:i}=await e.prompt([{type:"password",name:"apiKey",message:p.t("wizard.input_your_api_key"),mask:"*"}]);if(!i||""===i.trim()){const{action:t}=await e.prompt([{type:"list",name:"action",message:p.t("wizard.select_action"),choices:[{name:p.t("wizard.retry_input"),value:"retry"},{name:"<- "+p.t("wizard.nav_return"),value:"back"}]}]);if("back"===t)return!1;continue}const n=o(p.t("wizard.validating_api_key")).start(),r=await w(i.trim());if(n.stop(),r.valid)return a.setApiKey(i.trim()),this.validatedApiKeys.add(i.trim()),console.log(t.green("\n✓ "+p.t("wizard.api_key_valid"))),!0;{this.validatedApiKeys.delete(i.trim()),"invalid_api_key"===r.error?console.log(t.red("\n✗ "+p.t("wizard.api_key_invalid"))):console.log(t.red("\n✗ "+p.t("wizard.api_key_network_error")));const{action:o}=await e.prompt([{type:"list",name:"action",message:p.t("wizard.select_action"),choices:[{name:p.t("wizard.retry_input"),value:"retry"},{name:"<- "+p.t("wizard.nav_return"),value:"back"}]}]);if("back"===o)return!1}}}async selectAndConfigureTool(t=!1){for(;;){this.resetScreen(),this.createBox(p.t("wizard.select_tool"));const o=l.getSupportedTools().map(e=>({name:e.displayName,value:e.name})),{tool:a}=await this.promptWithHints([{type:"list",name:"tool",message:"🛠️ "+p.t("wizard.select_tool"),choices:[...o,new e.Separator,{name:"<- "+p.t("wizard.nav_return"),value:"back"},{name:"x "+p.t("wizard.nav_exit"),value:"exit"}]}]);if("exit"===a)this.printGoodbye(),process.exit(0);else if("back"===a)return;await this.configureTool(a,t)}}async configureTool(o,a=!1){const i=c[o];if(i){if(!l.isToolInstalled(o)){console.log(t.yellow("\n"+p.t("wizard.tool_not_installed",{tool:i.displayName})));const{install:a}=await e.prompt([{type:"confirm",name:"install",message:p.t("wizard.install_tool_confirm"),default:!0}]);if(!a)return;try{await l.installTool(o)}catch{return void console.log(t.red(p.t("wizard.install_failed")))}}a?await this.configureToolModel(o)&&await this.loadConfig(o):await this.showToolMenu(o)}}async showToolMenu(t){for(;;){this.resetScreen();const o=c[t];if(!o)return;this.createBox(o.displayName+" "+p.t("wizard.menu_title"));const a=[{name:"📥 "+p.t("wizard.action_load_config",{tool:o.displayName}),value:"load"},{name:"📤 "+p.t("wizard.action_unload_config",{tool:o.displayName}),value:"unload"}];l.supportsClearPinnedModels(t)&&a.push({name:"🧹 "+p.t("wizard.action_clear_model",{tool:o.displayName}),value:"clear-model"}),a.push({name:"🧨 "+p.t("wizard.action_force_config",{tool:o.displayName}),value:"force"},{name:"↩️ "+p.t("wizard.action_restore_config",{tool:o.displayName}),value:"restore"},new e.Separator,{name:"<- "+p.t("wizard.nav_return"),value:"back"},{name:"x "+p.t("wizard.nav_exit"),value:"exit"});const{action:i}=await this.promptWithHints([{type:"list",name:"action",message:"⚙️ "+p.t("wizard.select_action"),choices:a}]);if("exit"===i)this.printGoodbye(),process.exit(0);else{if("back"===i)return;"load"===i?await this.configureToolModel(t)&&await this.loadConfig(t):"force"===i?await this.forceConfig(t):"unload"===i?await this.unloadConfig(t):"clear-model"===i?await this.clearPinnedModels(t):"restore"===i&&await this.restoreConfig(t)}}}singleModelOverride(e){const t=e.trim();return/claude/i.test(t)?{opus:t}:{haiku:t,sonnet:t,opus:t,reasoning:t}}hasConfiguredApiKey(){return!!a.getApiKey()?.trim()||a.listToolApiKeys().some(e=>Boolean(a.getApiKeyForTool(e)?.trim()))}async validatedKeyForConfiguration(e){const a=e?.trim();if(!a)return console.log(t.red("\n"+p.t("wizard.api_key_required"))),console.log(t.yellow(p.t("wizard.please_config_api_key_first"))),null;if(this.validatedApiKeys.has(a))return a;const i=o(p.t("wizard.validating_api_key")).start(),n=await w(a);if(n.valid)return this.validatedApiKeys.add(a),i.succeed(p.t("wizard.api_key_valid")),a;this.validatedApiKeys.delete(a);const r="invalid_api_key"===n.error?p.t("wizard.api_key_invalid"):p.t("wizard.api_key_network_error");return i.fail(r),console.log(t.red("\n"+p.t("wizard.please_reconfig_api_key"))),null}async configureToolModel(i){const n=c[i];if(!n)return!1;const l=await this.validatedKeyForConfiguration(a.getApiKeyForTool(i));if(!l)return await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}]),!1;const d="claude-code"===i,m=s(i),u=d||!m?p.t("wizard.model_builtin_default"):m;this.resetScreen(),this.createBox(n.displayName+" · "+p.t("wizard.config_models"));const g=[{name:"✅ "+p.t("wizard.use_default_model",{model:u}),value:{type:"default"}},{name:"🌐 "+p.t("wizard.fetch_models_from_api"),value:{type:"fetch"}},...this.buildPresetChoices(),new e.Separator,{name:"✏️ "+p.t("wizard.input_custom_model"),value:{type:"custom"}},new e.Separator,{name:"<- "+p.t("wizard.nav_return"),value:{type:"back"}}],{choice:w}=await this.promptWithHints([{type:"list",name:"choice",message:p.t("wizard.select_tool_model",{tool:n.displayName}),choices:g}]);if("back"===w.type)return!1;if("default"===w.type)a.setToolModel(i,null);else if("preset"===w.type)a.setToolModel(i,r(w.preset));else if("custom"===w.type){const{model:t}=await e.prompt([{type:"input",name:"model",message:p.t("wizard.input_custom_model"),default:!d&&m?m:void 0}]),o=(t||"").trim();a.setToolModel(i,o?this.singleModelOverride(o):null)}else if("fetch"===w.type){const n=o(p.t("wizard.fetching_models")).start(),r=await _(l);let s;if(n.stop(),r.ok&&r.models.length>0){const{model:t}=await e.prompt([{type:"list",name:"model",message:p.t("wizard.select_fetched_model"),pageSize:20,choices:[...r.models,new e.Separator,{name:"<- "+p.t("wizard.nav_return"),value:"__back__"}]}]);if("__back__"===t)return!1;s=t}else{console.log(t.yellow("\n"+p.t("wizard.fetch_models_failed")));const{model:o}=await e.prompt([{type:"input",name:"model",message:p.t("wizard.input_custom_model"),default:!d&&m?m:void 0}]);s=(o||"").trim()}a.setToolModel(i,s?this.singleModelOverride(s):null)}if("codex"===i){const t=a.getToolSetting(i,f),{enableOneMillionContext:o}=await e.prompt([{type:"confirm",name:"enableOneMillionContext",message:p.t("wizard.codex_enable_1m_context"),default:!1!==t}]);a.setToolSetting(i,f,Boolean(o))}if("claude-desktop"===i){const t=a.getToolSetting(i,y),{hideOfficialLogin:o}=await e.prompt([{type:"confirm",name:"hideOfficialLogin",message:p.t("wizard.desktop_hide_official_login"),default:!1!==t}]);a.setToolSetting(i,y,Boolean(o))}return console.log(t.green("\n✓ "+p.t("wizard.models_saved"))),d&&console.log(t.gray("💡 "+p.t("wizard.claude_model_hint"))),!0}async forceConfig(t){const o=c[t]?.displayName||t,{confirm:a}=await e.prompt([{type:"confirm",name:"confirm",message:p.t("config.confirm_force",{tool:o}),default:!1}]);a&&await this.loadConfig(t,{force:!0})}async loadConfig(i,n={}){const r=a.getModelsForTool(i),s=await this.validatedKeyForConfiguration(a.getApiKeyForTool(i));if(!s)return void await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}]);const m=o(p.t("wizard.loading_config")).start();try{await l.applyPrivateConfig(i,s,r,n),m.stop(),l.printApplied(i),await d([i]),n.force&&console.log(t.yellow(p.t("config.force_done",{tool:c[i]?.displayName||i})));const o=c[i]?.command;o&&console.log(t.green("\n"+p.t("wizard.run_tool_hint",{command:o})));const{nextAction:a}=await e.prompt([{type:"list",name:"nextAction",message:p.t("wizard.what_next"),choices:[{name:p.t("wizard.exit_and_start"),value:"exit"},{name:p.t("wizard.continue_config"),value:"continue"}]}]);"exit"===a&&(this.printGoodbye(),process.exit(0))}catch(o){m.fail(p.t("wizard.config_failed")),console.log(t.red(o instanceof Error?o.message:String(o))),await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}])}}async clearPinnedModels(o){const a=c[o]?.displayName||o,{confirm:i}=await e.prompt([{type:"confirm",name:"confirm",message:p.t("wizard.confirm_clear_model",{tool:a}),default:!1}]);if(i){try{const e=await l.clearPinnedModels(o);0===e.length?console.log(t.yellow("\n"+p.t("config.clear_model_none",{tool:a}))):(console.log(t.green("\n✓ "+p.t("config.clear_model_done",{tool:a}))),e.forEach(e=>console.log(t.gray(` - ${e}`))),console.log(t.gray(p.t("config.clear_model_restart_hint",{tool:a}))))}catch(e){console.log(t.red("\n"+(e instanceof Error?e.message:String(e))))}await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}])}}async unloadConfig(a){const{confirm:i}=await e.prompt([{type:"confirm",name:"confirm",message:p.t("wizard.confirm_unload_config",{tool:c[a]?.displayName||a}),default:!1}]);if(!i)return;const n=o(p.t("wizard.unloading_config")).start();try{l.unloadPrivateConfig(a),n.succeed(p.t("wizard.config_unloaded"));const{nextAction:t}=await e.prompt([{type:"list",name:"nextAction",message:p.t("wizard.what_next"),choices:[{name:p.t("wizard.exit_and_start"),value:"exit"},{name:p.t("wizard.continue_config"),value:"continue"}]}]);"exit"===t&&(this.printGoodbye(),process.exit(0))}catch(o){n.fail(p.t("wizard.config_unload_failed")),console.log(t.red(o instanceof Error?o.message:String(o))),await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}])}}async restoreConfig(a){const i=c[a]?.displayName||a,{confirm:n}=await e.prompt([{type:"confirm",name:"confirm",message:p.t("wizard.confirm_restore_config",{tool:i}),default:!1}]);if(!n)return;const r=o(p.t("wizard.restoring_config")).start();try{const e=l.restorePrivateConfig(a).filter(e=>e.result.restored);0===e.length?r.warn(p.t("wizard.restore_no_backup")):(r.succeed(p.t("wizard.config_restored",{tool:i})),e.forEach(e=>console.log(t.gray(` ${e.file} ← ${e.result.from}`))))}catch(e){r.fail(p.t("wizard.config_restore_failed")),console.log(t.red(e instanceof Error?e.message:String(e)))}await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}])}async run(){await this.showMainMenu()}async showMainMenu(){if(this.hasConfiguredApiKey())for(;;){this.resetScreen();const o=a.getApiKey()||"",i=o.length>4?"sk-****"+o.slice(-4):"****";console.log(t.gray(` 🔑 API Key: ${i}`)+t.green(" ✓")),console.log("");const n=l.getSupportedTools().map(e=>({name:"🛠️ "+e.displayName,value:{type:"tool",tool:e.name}})),r=[new e.Separator(C("── "+p.t("wizard.select_tool")+" ──")),...n,new e.Separator(t.gray("── "+p.t("wizard.settings")+" ──")),{name:"🔑 "+p.t("wizard.menu_change_api_key"),value:{type:"apikey"}},{name:"🌐 "+p.t("wizard.menu_config_language"),value:{type:"language"}},new e.Separator,{name:"x "+p.t("wizard.nav_exit"),value:{type:"exit"}}],{action:s}=await this.promptWithHints([{type:"list",name:"action",message:"🛠️ "+p.t("wizard.select_tool"),choices:r}]);"exit"===s.type?(this.printGoodbye(),process.exit(0)):"tool"===s.type?await this.configureTool(s.tool):"apikey"===s.type?await this.configApiKey():"language"===s.type&&await this.configLanguage()}else await this.runFirstTimeSetup()}}export const wizard=Wizard.getInstance();
1
+ import e from"inquirer";import t from"chalk";import a from"ora";import{configManager as o,DEFAULT_MODELS as i}from"./config.js";import{getPresets as n,resolvePreset as r,getToolDefaultModel as s}from"./model-registry.js";import{toolManager as l,SUPPORTED_TOOLS as c}from"./tool-manager.js";import{postApplyPrompts as d}from"./post-apply.js";import{i18n as p,detectSystemLang as m}from"./i18n.js";import{createBorderLine as u,createContentLine as g}from"../utils/string-width.js";import{validateApiKey as _,fetchModels as w}from"./api-validator.js";import{CODEX_ONE_MILLION_CONTEXT_SETTING as f}from"./tools/codex.js";import{CLAUDE_DESKTOP_HIDE_OFFICIAL_LOGIN_SETTING as y}from"./tools/claude-desktop.js";import{addressesWithCurrent as h,probeAll as z,reasonText as v,switchGatewayAddress as k}from"../commands/base.js";import{readFileSync as x}from"fs";import{dirname as b,join as C}from"path";import{fileURLToPath as S}from"url";const M=b(S(import.meta.url)),T=t.hex("#FF8700");export class Wizard{static instance;BOX_WIDTH=63;validatedApiKeys=new Set;constructor(){}static getInstance(){return Wizard.instance||(Wizard.instance=new Wizard),Wizard.instance}createBox(e){console.log(T.bold("\n"+u("╔","╗","═",this.BOX_WIDTH))),console.log(T.bold(g(e,"║","║",this.BOX_WIDTH,"center"))),console.log(T.bold(u("╚","╝","═",this.BOX_WIDTH))),console.log("")}showOperationHints(){const e=[t.gray(p.t("wizard.hint_navigate")),t.gray(p.t("wizard.hint_confirm"))];console.log(t.gray("💡 ")+e.join(t.gray(" | "))+"\n")}async promptWithHints(t){this.showOperationHints();const a=t.map(e=>"list"!==e.type||e.pageSize?e:{...e,pageSize:20});return e.prompt(a)}printBanner(){const e=65,t=p.t("wizard.banner_subtitle"),a=g(t,"║","║",e,"center"),o=g("","║","║",e,"center"),i=g(`API Helper v${function(){try{return JSON.parse(x(C(M,"..","..","package.json"),"utf-8")).version}catch{return"0.0.0"}}()}`,"║","║",e,"center"),n=[" ▄▀▄ █▀▄ █ █▄█ ██▀ █ █▀▄ ██▀ █▀▄ "," █▀█ █▀ █ █ █ █▄▄ █▄▄ █▀ █▄▄ █▀▄ "].map(t=>g(t,"║","║",e,"center")),r=[u("╔","╗","═",e),o,...n,o,i,a,u("╚","╝","═",e)];console.log(T.bold("\n"+r.join("\n")))}resetScreen(){console.clear(),this.printBanner()}printGoodbye(){const e=l.getSupportedTools().filter(e=>{try{return Boolean(l.detectCurrentConfig(e.name).apiKey)}catch{return!1}});let a;if(e.length>0){const t=e.map(e=>e.command).join(" / ");a=p.t("wizard.goodbye_message_tools",{commands:t}),e.some(e=>"claude-code"===e.name)&&(a+=" "+p.t("wizard.goodbye_claude_hint"))}else a=p.t("wizard.goodbye_message");console.log(t.green("\n👋 "+a))}async runFirstTimeSetup(){const e="zh_CN"===m();e&&(o.setLang("zh_CN"),p.setLocale("zh_CN")),this.resetScreen(),console.log(T.bold("\n"+p.t("wizard.welcome"))),console.log(t.gray(p.t("wizard.privacy_note")+"\n")),e?console.log(t.gray(p.t("wizard.lang_auto_zh")+"\n")):await this.configLanguage(),await this.configApiKey()&&await this.selectAndConfigureTool(!0)}async configLanguage(){for(;;){this.resetScreen(),this.createBox(p.t("wizard.select_language"));const a=p.getLocale(),{language:i}=await this.promptWithHints([{type:"list",name:"language",message:"✨ "+p.t("wizard.select_language"),choices:[{name:"[EN] English"+("en_US"===a?t.green(" ✓ ("+p.t("wizard.current_active")+")"):""),value:"en_US"},{name:"[CN] 中文"+("zh_CN"===a?t.green(" ✓ ("+p.t("wizard.current_active")+")"):""),value:"zh_CN"},new e.Separator,{name:"<- "+p.t("wizard.nav_return"),value:"back"},{name:"x "+p.t("wizard.nav_exit"),value:"exit"}],default:"zh_CN"}]);if("exit"===i)this.printGoodbye(),process.exit(0);else if("back"===i)return;return o.setLang(i),void p.setLocale(i)}}buildPresetChoices(){const a=n(),o=a.filter(e=>e.recommended),i=a.filter(e=>!e.recommended),r=e=>({name:(p.t(e.name)!==e.name?p.t(e.name):e.name)+(e.models?t.gray(` [H:${e.models.haiku} S:${e.models.sonnet} O:${e.models.opus}]`):""),value:{type:"preset",preset:e}}),s=[];return o.length>0&&(s.push(new e.Separator(t.hex("#FF8700")("── "+p.t("wizard.recommended")+" ──"))),s.push(...o.map(r))),i.length>0&&(s.push(new e.Separator(t.gray("── "+p.t("wizard.other_models")+" ──"))),s.push(...i.map(r))),s}async handleCustomModelInput(){const a=o.getModels(),{customMode:n}=await e.prompt([{type:"list",name:"customMode",message:p.t("wizard.custom_model_mode"),choices:[{name:p.t("wizard.custom_model_single"),value:"single"},{name:p.t("wizard.custom_model_per_role"),value:"per_role"}]}]);if("single"===n){const{customModel:t}=await e.prompt([{type:"input",name:"customModel",message:p.t("wizard.input_custom_model"),default:i.sonnet}]);o.setModels({haiku:t,sonnet:t,opus:t,reasoning:t})}else{const t=await e.prompt([{type:"input",name:"haiku",message:p.t("wizard.model_haiku"),default:a.haiku},{type:"input",name:"sonnet",message:p.t("wizard.model_sonnet"),default:a.sonnet},{type:"input",name:"opus",message:p.t("wizard.model_opus"),default:a.opus},{type:"input",name:"reasoning",message:p.t("wizard.model_reasoning"),default:a.reasoning}]);o.setModels(t)}console.log(t.green("\n✓ "+p.t("wizard.models_saved")))}async configModels(a){const i=o.getApiKey();if(await this.validatedKeyForConfiguration(i))for(;;){this.resetScreen(),this.createBox(p.t("wizard.config_models"));const i=[...this.buildPresetChoices()];i.push(new e.Separator),i.push({name:"✏️ "+p.t("wizard.custom_model_input"),value:{type:"custom_input"}}),i.push(new e.Separator),i.push({name:"<- "+p.t("wizard.nav_return"),value:{type:"back"}}),i.push({name:"x "+p.t("wizard.nav_exit"),value:{type:"exit"}});const{modelChoice:n}=await this.promptWithHints([{type:"list",name:"modelChoice",message:p.t("wizard.select_model_config"),choices:i}]);if("exit"===n.type)this.printGoodbye(),process.exit(0);else{if("back"===n.type)return;if("custom_input"===n.type)return await this.handleCustomModelInput(),void(a?.skipLoadPrompt||await this.promptLoadAfterModelConfig());if("preset"===n.type){const e=r(n.preset);return o.setModels(e),console.log(t.green("\n✓ "+p.t("wizard.models_saved"))),void(a?.skipLoadPrompt||await this.promptLoadAfterModelConfig())}}}else await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}])}async promptLoadAfterModelConfig(){const{shouldLoad:t}=await e.prompt([{type:"list",name:"shouldLoad",message:p.t("wizard.load_config_now"),choices:[{name:p.t("wizard.load_config_yes"),value:!0},{name:p.t("wizard.load_config_later"),value:!1}]}]);t&&await this.selectAndConfigureTool()}async configBaseUrl(){const i=o.getBaseUrl(),n=h(),r=a(p.t("wizard.base_testing")).start(),s=await z(n);r.stop();const l=n.map((e,a)=>{const o=s[a],n=o.ok?t.green(`√ ${o.ms} ms`):t.red(`× ${v(o)}`),r=e.url===i?t.cyan(` ← ${p.t("base.current_mark")}`):"";return{name:`${e.url} ${t.gray(e.label)} ${n}${r}`,value:e.url}});l.push(new e.Separator,{name:"<- "+p.t("wizard.nav_return"),value:"__back__"});const{url:c}=await this.promptWithHints([{type:"list",name:"url",message:p.t("wizard.base_select"),choices:l,default:i}]);if("__back__"===c)return;const d=s[n.findIndex(e=>e.url===c)];d.ok?await k(c):console.log(t.red(p.t("base.unreachable",{url:c,reason:v(d)}))),await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}])}async configApiKey(){for(;;){this.resetScreen(),this.createBox(p.t("wizard.input_api_key")),console.log(t.gray("\n💡 "+p.t("wizard.api_key_input_hint"))),console.log(t.gray(" "+p.t("wizard.api_key_get_hint",{url:"https://www.vibeapi.cn/console/token"}))),console.log();const{apiKey:i}=await e.prompt([{type:"password",name:"apiKey",message:p.t("wizard.input_your_api_key"),mask:"*"}]);if(!i||""===i.trim()){const{action:t}=await e.prompt([{type:"list",name:"action",message:p.t("wizard.select_action"),choices:[{name:p.t("wizard.retry_input"),value:"retry"},{name:"<- "+p.t("wizard.nav_return"),value:"back"}]}]);if("back"===t)return!1;continue}const n=a(p.t("wizard.validating_api_key")).start(),r=await _(i.trim());if(n.stop(),r.valid)return o.setApiKey(i.trim()),this.validatedApiKeys.add(i.trim()),console.log(t.green("\n✓ "+p.t("wizard.api_key_valid"))),!0;{this.validatedApiKeys.delete(i.trim()),"invalid_api_key"===r.error?console.log(t.red("\n✗ "+p.t("wizard.api_key_invalid"))):console.log(t.red("\n✗ "+p.t("wizard.api_key_network_error")));const{action:a}=await e.prompt([{type:"list",name:"action",message:p.t("wizard.select_action"),choices:[{name:p.t("wizard.retry_input"),value:"retry"},{name:"<- "+p.t("wizard.nav_return"),value:"back"}]}]);if("back"===a)return!1}}}async selectAndConfigureTool(t=!1){for(;;){this.resetScreen(),this.createBox(p.t("wizard.select_tool"));const a=l.getSupportedTools().map(e=>({name:e.displayName,value:e.name})),{tool:o}=await this.promptWithHints([{type:"list",name:"tool",message:"🛠️ "+p.t("wizard.select_tool"),choices:[...a,new e.Separator,{name:"<- "+p.t("wizard.nav_return"),value:"back"},{name:"x "+p.t("wizard.nav_exit"),value:"exit"}]}]);if("exit"===o)this.printGoodbye(),process.exit(0);else if("back"===o)return;await this.configureTool(o,t)}}async configureTool(a,o=!1){const i=c[a];if(i){if(!l.isToolInstalled(a)){console.log(t.yellow("\n"+p.t("wizard.tool_not_installed",{tool:i.displayName})));const{install:o}=await e.prompt([{type:"confirm",name:"install",message:p.t("wizard.install_tool_confirm"),default:!0}]);if(!o)return;try{await l.installTool(a)}catch{return void console.log(t.red(p.t("wizard.install_failed")))}}o?await this.configureToolModel(a)&&await this.loadConfig(a):await this.showToolMenu(a)}}async showToolMenu(t){for(;;){this.resetScreen();const a=c[t];if(!a)return;this.createBox(a.displayName+" "+p.t("wizard.menu_title"));const o=[{name:"📥 "+p.t("wizard.action_load_config",{tool:a.displayName}),value:"load"},{name:"📤 "+p.t("wizard.action_unload_config",{tool:a.displayName}),value:"unload"}];l.supportsClearPinnedModels(t)&&o.push({name:"🧹 "+p.t("wizard.action_clear_model",{tool:a.displayName}),value:"clear-model"}),o.push({name:"🧨 "+p.t("wizard.action_force_config",{tool:a.displayName}),value:"force"},{name:"↩️ "+p.t("wizard.action_restore_config",{tool:a.displayName}),value:"restore"},new e.Separator,{name:"<- "+p.t("wizard.nav_return"),value:"back"},{name:"x "+p.t("wizard.nav_exit"),value:"exit"});const{action:i}=await this.promptWithHints([{type:"list",name:"action",message:"⚙️ "+p.t("wizard.select_action"),choices:o}]);if("exit"===i)this.printGoodbye(),process.exit(0);else{if("back"===i)return;"load"===i?await this.configureToolModel(t)&&await this.loadConfig(t):"force"===i?await this.forceConfig(t):"unload"===i?await this.unloadConfig(t):"clear-model"===i?await this.clearPinnedModels(t):"restore"===i&&await this.restoreConfig(t)}}}singleModelOverride(e){const t=e.trim();return/claude/i.test(t)?{opus:t}:{haiku:t,sonnet:t,opus:t,reasoning:t}}hasConfiguredApiKey(){return!!o.getApiKey()?.trim()||o.listToolApiKeys().some(e=>Boolean(o.getApiKeyForTool(e)?.trim()))}async validatedKeyForConfiguration(e){const o=e?.trim();if(!o)return console.log(t.red("\n"+p.t("wizard.api_key_required"))),console.log(t.yellow(p.t("wizard.please_config_api_key_first"))),null;if(this.validatedApiKeys.has(o))return o;const i=a(p.t("wizard.validating_api_key")).start(),n=await _(o);if(n.valid)return this.validatedApiKeys.add(o),i.succeed(p.t("wizard.api_key_valid")),o;this.validatedApiKeys.delete(o);const r="invalid_api_key"===n.error?p.t("wizard.api_key_invalid"):p.t("wizard.api_key_network_error");return i.fail(r),console.log(t.red("\n"+p.t("wizard.please_reconfig_api_key"))),null}async configureToolModel(i){const n=c[i];if(!n)return!1;const l=await this.validatedKeyForConfiguration(o.getApiKeyForTool(i));if(!l)return await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}]),!1;const d="claude-code"===i,m=s(i),u=d||!m?p.t("wizard.model_builtin_default"):m;this.resetScreen(),this.createBox(n.displayName+" · "+p.t("wizard.config_models"));const g=[{name:"✅ "+p.t("wizard.use_default_model",{model:u}),value:{type:"default"}},{name:"🌐 "+p.t("wizard.fetch_models_from_api"),value:{type:"fetch"}},...this.buildPresetChoices(),new e.Separator,{name:"✏️ "+p.t("wizard.input_custom_model"),value:{type:"custom"}},new e.Separator,{name:"<- "+p.t("wizard.nav_return"),value:{type:"back"}}],{choice:_}=await this.promptWithHints([{type:"list",name:"choice",message:p.t("wizard.select_tool_model",{tool:n.displayName}),choices:g}]);if("back"===_.type)return!1;if("default"===_.type)o.setToolModel(i,null);else if("preset"===_.type)o.setToolModel(i,r(_.preset));else if("custom"===_.type){const{model:t}=await e.prompt([{type:"input",name:"model",message:p.t("wizard.input_custom_model"),default:!d&&m?m:void 0}]),a=(t||"").trim();o.setToolModel(i,a?this.singleModelOverride(a):null)}else if("fetch"===_.type){const n=a(p.t("wizard.fetching_models")).start(),r=await w(l);let s;if(n.stop(),r.ok&&r.models.length>0){const{model:t}=await e.prompt([{type:"list",name:"model",message:p.t("wizard.select_fetched_model"),pageSize:20,choices:[...r.models,new e.Separator,{name:"<- "+p.t("wizard.nav_return"),value:"__back__"}]}]);if("__back__"===t)return!1;s=t}else{console.log(t.yellow("\n"+p.t("wizard.fetch_models_failed")));const{model:a}=await e.prompt([{type:"input",name:"model",message:p.t("wizard.input_custom_model"),default:!d&&m?m:void 0}]);s=(a||"").trim()}o.setToolModel(i,s?this.singleModelOverride(s):null)}if("codex"===i){const t=o.getToolSetting(i,f),{enableOneMillionContext:a}=await e.prompt([{type:"confirm",name:"enableOneMillionContext",message:p.t("wizard.codex_enable_1m_context"),default:!1!==t}]);o.setToolSetting(i,f,Boolean(a))}if("claude-desktop"===i){const t=o.getToolSetting(i,y),{hideOfficialLogin:a}=await e.prompt([{type:"confirm",name:"hideOfficialLogin",message:p.t("wizard.desktop_hide_official_login"),default:!1!==t}]);o.setToolSetting(i,y,Boolean(a))}return console.log(t.green("\n✓ "+p.t("wizard.models_saved"))),d&&console.log(t.gray("💡 "+p.t("wizard.claude_model_hint"))),!0}async forceConfig(t){const a=c[t]?.displayName||t,{confirm:o}=await e.prompt([{type:"confirm",name:"confirm",message:p.t("config.confirm_force",{tool:a}),default:!1}]);o&&await this.loadConfig(t,{force:!0})}async loadConfig(i,n={}){const r=o.getModelsForTool(i),s=await this.validatedKeyForConfiguration(o.getApiKeyForTool(i));if(!s)return void await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}]);const m=a(p.t("wizard.loading_config")).start();try{await l.applyPrivateConfig(i,s,r,n),m.stop(),l.printApplied(i),await d([i]),n.force&&console.log(t.yellow(p.t("config.force_done",{tool:c[i]?.displayName||i})));const a=c[i]?.command;a&&console.log(t.green("\n"+p.t("wizard.run_tool_hint",{command:a})));const{nextAction:o}=await e.prompt([{type:"list",name:"nextAction",message:p.t("wizard.what_next"),choices:[{name:p.t("wizard.exit_and_start"),value:"exit"},{name:p.t("wizard.continue_config"),value:"continue"}]}]);"exit"===o&&(this.printGoodbye(),process.exit(0))}catch(a){m.fail(p.t("wizard.config_failed")),console.log(t.red(a instanceof Error?a.message:String(a))),await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}])}}async clearPinnedModels(a){const o=c[a]?.displayName||a,{confirm:i}=await e.prompt([{type:"confirm",name:"confirm",message:p.t("wizard.confirm_clear_model",{tool:o}),default:!1}]);if(i){try{const e=await l.clearPinnedModels(a);0===e.length?console.log(t.yellow("\n"+p.t("config.clear_model_none",{tool:o}))):(console.log(t.green("\n✓ "+p.t("config.clear_model_done",{tool:o}))),e.forEach(e=>console.log(t.gray(` - ${e}`))),console.log(t.gray(p.t("config.clear_model_restart_hint",{tool:o}))))}catch(e){console.log(t.red("\n"+(e instanceof Error?e.message:String(e))))}await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}])}}async unloadConfig(o){const{confirm:i}=await e.prompt([{type:"confirm",name:"confirm",message:p.t("wizard.confirm_unload_config",{tool:c[o]?.displayName||o}),default:!1}]);if(!i)return;const n=a(p.t("wizard.unloading_config")).start();try{l.unloadPrivateConfig(o),n.succeed(p.t("wizard.config_unloaded"));const{nextAction:t}=await e.prompt([{type:"list",name:"nextAction",message:p.t("wizard.what_next"),choices:[{name:p.t("wizard.exit_and_start"),value:"exit"},{name:p.t("wizard.continue_config"),value:"continue"}]}]);"exit"===t&&(this.printGoodbye(),process.exit(0))}catch(a){n.fail(p.t("wizard.config_unload_failed")),console.log(t.red(a instanceof Error?a.message:String(a))),await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}])}}async restoreConfig(o){const i=c[o]?.displayName||o,{confirm:n}=await e.prompt([{type:"confirm",name:"confirm",message:p.t("wizard.confirm_restore_config",{tool:i}),default:!1}]);if(!n)return;const r=a(p.t("wizard.restoring_config")).start();try{const e=l.restorePrivateConfig(o).filter(e=>e.result.restored);0===e.length?r.warn(p.t("wizard.restore_no_backup")):(r.succeed(p.t("wizard.config_restored",{tool:i})),e.forEach(e=>console.log(t.gray(` ${e.file} ← ${e.result.from}`))))}catch(e){r.fail(p.t("wizard.config_restore_failed")),console.log(t.red(e instanceof Error?e.message:String(e)))}await e.prompt([{type:"input",name:"continue",message:p.t("wizard.press_enter")}])}async run(){await this.showMainMenu()}async showMainMenu(){if(this.hasConfiguredApiKey())for(;;){this.resetScreen();const a=o.getApiKey()||"",i=a.length>4?"sk-****"+a.slice(-4):"****";console.log(t.gray(` 🔑 API Key: ${i}`)+t.green(" ✓")),console.log("");const n=l.getSupportedTools().map(e=>({name:"🛠️ "+e.displayName,value:{type:"tool",tool:e.name}})),r=[new e.Separator(T("── "+p.t("wizard.select_tool")+" ──")),...n,new e.Separator(t.gray("── "+p.t("wizard.settings")+" ──")),{name:"🔑 "+p.t("wizard.menu_change_api_key"),value:{type:"apikey"}},{name:"🔗 "+p.t("wizard.menu_base_url",{url:o.getBaseUrl()}),value:{type:"base"}},{name:"🌐 "+p.t("wizard.menu_config_language"),value:{type:"language"}},new e.Separator,{name:"x "+p.t("wizard.nav_exit"),value:{type:"exit"}}],{action:s}=await this.promptWithHints([{type:"list",name:"action",message:"🛠️ "+p.t("wizard.select_tool"),choices:r}]);"exit"===s.type?(this.printGoodbye(),process.exit(0)):"tool"===s.type?await this.configureTool(s.tool):"apikey"===s.type?await this.configApiKey():"base"===s.type?await this.configBaseUrl():"language"===s.type&&await this.configLanguage()}else await this.runFirstTimeSetup()}}export const wizard=Wizard.getInstance();
@@ -20,7 +20,8 @@
20
20
  "setup": "One-shot setup (--key auto-detects capable tools / --claudekey --codexkey for per-tool keys)",
21
21
  "upgrade": "Upgrade Claude Code, Codex and other coding tools (falls back to the npm mirror)",
22
22
  "codex": "Codex helpers",
23
- "codex_sessions": "Show every local Codex session again (sessions from another login or provider are hidden)"
23
+ "codex_sessions": "Show every local Codex session again (sessions from another login or provider are hidden)",
24
+ "base": "List, test and switch the gateway address (when the main domain is unreachable or shows a certificate error)"
24
25
  },
25
26
  "lang": {
26
27
  "current": "Current language",
@@ -129,7 +130,7 @@
129
130
  "api_key_valid": "API Key is valid",
130
131
  "api_key_success_hint": "Entering main menu...",
131
132
  "api_key_invalid": "API Key is invalid or expired",
132
- "api_key_network_error": "Network error, please check your connection",
133
+ "api_key_network_error": "Network error, please check your connection; you can also run vibecli base to switch to another gateway address",
133
134
  "please_config_api_key_first": "Please configure a valid API Key first",
134
135
  "please_reconfig_api_key": "Please reconfigure your API Key",
135
136
  "select_model_config": "Select model",
@@ -181,7 +182,10 @@
181
182
  "custom_model_single": "Single model for all roles",
182
183
  "custom_model_per_role": "Configure each role separately",
183
184
  "desktop_hide_official_login": "Hide the official Claude.ai sign-in entry in Claude Desktop (gateway only)?",
184
- "action_force_config": "Reset to recommended - (delete {{tool}}'s config files and rewrite them; repairs a broken config)"
185
+ "action_force_config": "Reset to recommended - (delete {{tool}}'s config files and rewrite them; repairs a broken config)",
186
+ "menu_base_url": "Switch gateway address (now {{url}})",
187
+ "base_testing": "Testing the gateway addresses...",
188
+ "base_select": "Pick a gateway address (every configured tool follows)"
185
189
  },
186
190
  "doctor": {
187
191
  "checking": "Running health check...",
@@ -214,7 +218,11 @@
214
218
  "codex_auth": "Codex token",
215
219
  "codex_stale_bearer": "~/.codex/config.toml has experimental_bearer_token under [model_providers.custom] (usually PROXY_MANAGED left by cc-switch's proxy mode); Codex sends it as the token and gets 401 Invalid token. vibecli setup removes it",
216
220
  "codex_chatgpt_login": "Codex is signed in with a ChatGPT account and sends that credential to VibeAPI (401 Invalid token); run vibecli setup to switch back to the key",
217
- "claude_login": "Claude account login"
221
+ "claude_login": "Claude account login",
222
+ "check_other_addresses": "Testing other gateway addresses",
223
+ "other_addresses": "Other gateway addresses",
224
+ "other_addresses_found": "These addresses work: {{urls}}. Run vibecli base auto to move every tool to one of them",
225
+ "other_addresses_none": "None of the other gateway addresses can be reached either; check your network or proxy"
218
226
  },
219
227
  "preset": {
220
228
  "claude_mix": "Claude Mix",
@@ -460,5 +468,24 @@
460
468
  "db_skipped": "Codex's database was not updated ({{reason}}); session files were. Upgrade Node.js to 22.13+ and run again",
461
469
  "backup": "Backup and undo record in {{dir}}; undo with vibecli codex sessions --undo <that dir>",
462
470
  "undone": "Restored {{files}} session files and {{rows}} database rows"
471
+ },
472
+ "base": {
473
+ "current": "Current gateway address: {{url}}",
474
+ "current_mark": "current",
475
+ "custom_label": "Custom address",
476
+ "usage_hint": "Switch: vibecli base <number or address>; first one that works: vibecli base auto; back to the main site: vibecli base default",
477
+ "switched": "Switched to {{url}}",
478
+ "kept": "{{url}} works and is saved",
479
+ "no_tools": "No tool has been configured by vibecli yet; vibecli setup --key <your key> will use this address",
480
+ "none_reachable": "None of the gateway addresses can be reached; check your network or proxy",
481
+ "bad_index": "There is no address {{index}} ({{count}} in total); run vibecli base to see the list",
482
+ "bad_url": "{{value}} is not a valid address, e.g. https://www.vibeapi.si",
483
+ "unreachable": "{{url}} cannot be reached: {{reason}}. Nothing was changed",
484
+ "error_certificate": "wrong certificate; this network may resolve the domain to another server",
485
+ "error_timeout": "timed out",
486
+ "error_network": "connection failed",
487
+ "error_not_gateway": "the answer is not from VibeAPI",
488
+ "network_hint": "If the current address cannot be reached, run vibecli base to see the others, or vibecli base auto to switch to one that works",
489
+ "tools_moved": "These tools now use it:"
463
490
  }
464
491
  }
@@ -20,7 +20,8 @@
20
20
  "setup": "一条命令完成配置(--key 按能力自动选工具 / --claudekey --codexkey 双令牌分别配置)",
21
21
  "upgrade": "升级 Claude Code、Codex 等编码工具(官方渠道连不上时改走国内镜像)",
22
22
  "codex": "Codex 辅助功能",
23
- "codex_sessions": "让 Codex 本地所有会话都显示在列表里(换过登录方式或服务商后旧会话会被隐藏)"
23
+ "codex_sessions": "让 Codex 本地所有会话都显示在列表里(换过登录方式或服务商后旧会话会被隐藏)",
24
+ "base": "查看、测速并切换接入地址(主域名连不上或证书报错时换一个)"
24
25
  },
25
26
  "lang": {
26
27
  "current": "当前语言",
@@ -129,7 +130,7 @@
129
130
  "api_key_valid": "API Key 验证通过",
130
131
  "api_key_success_hint": "正在进入主菜单...",
131
132
  "api_key_invalid": "API Key 无效或已过期",
132
- "api_key_network_error": "网络错误,请检查网络连接",
133
+ "api_key_network_error": "网络错误,请检查网络连接;也可以运行 vibecli base 换一个接入地址",
133
134
  "please_config_api_key_first": "请先配置有效的 API Key",
134
135
  "please_reconfig_api_key": "请重新配置 API Key",
135
136
  "select_model_config": "请选择模型",
@@ -181,7 +182,10 @@
181
182
  "custom_model_single": "所有角色使用同一模型",
182
183
  "custom_model_per_role": "分别配置每个角色",
183
184
  "desktop_hide_official_login": "隐藏 Claude Desktop 里的官方登录(Claude.ai 账号)入口,只走网关?",
184
- "action_force_config": "重置为推荐配置 - (删掉 {{tool}} 原配置文件后整份重写,修复改坏的配置)"
185
+ "action_force_config": "重置为推荐配置 - (删掉 {{tool}} 原配置文件后整份重写,修复改坏的配置)",
186
+ "menu_base_url": "切换接入地址(当前 {{url}})",
187
+ "base_testing": "正在测试各个接入地址...",
188
+ "base_select": "选择接入地址(所有已配置的工具会一起换过去)"
185
189
  },
186
190
  "doctor": {
187
191
  "checking": "正在进行健康检查...",
@@ -214,7 +218,11 @@
214
218
  "codex_auth": "Codex 令牌",
215
219
  "codex_stale_bearer": "~/.codex/config.toml 的 [model_providers.custom] 里有 experimental_bearer_token(多半是 cc-switch 等工具代理模式留下的 PROXY_MANAGED),Codex 会把它当令牌发出去,导致 401 Invalid token;运行 vibecli setup 会删掉它",
216
220
  "codex_chatgpt_login": "Codex 当前是 ChatGPT 账号登录状态,会把 ChatGPT 的凭据发给 VibeAPI,导致 401 Invalid token;运行 vibecli setup 改回令牌登录",
217
- "claude_login": "Claude 官方登录"
221
+ "claude_login": "Claude 官方登录",
222
+ "check_other_addresses": "测试其他接入地址",
223
+ "other_addresses": "其他接入地址",
224
+ "other_addresses_found": "这些地址能用:{{urls}}。运行 vibecli base auto,所有工具会一起换过去",
225
+ "other_addresses_none": "其他接入地址也都连不上,请检查网络或代理"
218
226
  },
219
227
  "preset": {
220
228
  "claude_mix": "Claude 混合",
@@ -460,5 +468,24 @@
460
468
  "db_skipped": "Codex 的数据库没有更新({{reason}}),会话文件已改好;升级 Node.js 到 22.13 以上后再运行一次",
461
469
  "backup": "备份与还原记录在 {{dir}},还原用 vibecli codex sessions --undo <这个目录>",
462
470
  "undone": "已还原 {{files}} 个会话文件、{{rows}} 条数据库记录"
471
+ },
472
+ "base": {
473
+ "current": "当前接入地址:{{url}}",
474
+ "current_mark": "当前",
475
+ "custom_label": "自定义地址",
476
+ "usage_hint": "切换:vibecli base <序号或地址>;自动选第一个能用的:vibecli base auto;回到主站:vibecli base default",
477
+ "switched": "已切换到 {{url}}",
478
+ "kept": "当前地址 {{url}} 可用,已保存",
479
+ "no_tools": "还没有用 vibecli 配置过的工具,运行 vibecli setup --key <你的 Key> 时会直接用这个地址",
480
+ "none_reachable": "所有接入地址都连不上,请检查网络或代理",
481
+ "bad_index": "没有第 {{index}} 个地址(共 {{count}} 个),先运行 vibecli base 查看列表",
482
+ "bad_url": "{{value}} 不是有效的地址,示例:https://www.vibeapi.si",
483
+ "unreachable": "{{url}} 连不上:{{reason}}。没有切换",
484
+ "error_certificate": "证书不对,当前网络可能把这个域名解析到了别的地址",
485
+ "error_timeout": "超时",
486
+ "error_network": "网络连接失败",
487
+ "error_not_gateway": "返回的不是 VibeAPI",
488
+ "network_hint": "连不上当前接入地址时,运行 vibecli base 查看其他地址,vibecli base auto 自动换到能用的那个",
489
+ "tools_moved": "以下工具已改用这个地址:"
463
490
  }
464
491
  }
package/dist/models.json CHANGED
@@ -281,5 +281,23 @@
281
281
  "codex": "0.122.0",
282
282
  "claude-code": "2.1.291",
283
283
  "claude-desktop": "2.2553.0"
284
- }
284
+ },
285
+ "gatewayAddresses": [
286
+ {
287
+ "url": "https://www.vibeapi.cn",
288
+ "label": "主站"
289
+ },
290
+ {
291
+ "url": "https://cf.vibeapi.cn",
292
+ "label": "Cloudflare 线路"
293
+ },
294
+ {
295
+ "url": "https://hk.vibeapi.cn",
296
+ "label": "香港线路"
297
+ },
298
+ {
299
+ "url": "https://www.vibeapi.si",
300
+ "label": "备用域名(证书报错时用)"
301
+ }
302
+ ]
285
303
  }
package/models.json CHANGED
@@ -281,5 +281,23 @@
281
281
  "codex": "0.122.0",
282
282
  "claude-code": "2.1.291",
283
283
  "claude-desktop": "2.2553.0"
284
- }
284
+ },
285
+ "gatewayAddresses": [
286
+ {
287
+ "url": "https://www.vibeapi.cn",
288
+ "label": "主站"
289
+ },
290
+ {
291
+ "url": "https://cf.vibeapi.cn",
292
+ "label": "Cloudflare 线路"
293
+ },
294
+ {
295
+ "url": "https://hk.vibeapi.cn",
296
+ "label": "香港线路"
297
+ },
298
+ {
299
+ "url": "https://www.vibeapi.si",
300
+ "label": "备用域名(证书报错时用)"
301
+ }
302
+ ]
285
303
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeapi/cli",
3
- "version": "0.0.68",
3
+ "version": "0.0.70",
4
4
  "description": "CLI tool for managing AI coding assistants (Claude Code, Codex, OpenClaw, Hermes, AtomCode, Grok Build) with one unified config",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",