@vibeapi/cli 0.0.72 → 0.0.74

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
@@ -112,7 +112,7 @@ vibecli base https://www.vibeapi.si # 或指定一个地址
112
112
  vibecli base default # 网络恢复后回到主站
113
113
  ```
114
114
 
115
- `auto` 按这个优先级尝试,取第一个能用的:`www.vibeapi.cn` → `www.vibeapi.si` → `hk.vibeapi.cn` → `cf.vibeapi.cn` → `token.nfeyre.top`。网关控制台以后新增的地址会排在这些后面。切换后地址保存在当前 profile 里,Claude Code、Codex、Claude 桌面端等已用 vibecli 配置过的工具会一起改过去,以后再运行 `vibecli setup` 也不会改回主站。首次配置时就可以指定:`vibecli setup --key sk-xxx --base https://www.vibeapi.si`。向导主菜单里也有「切换接入地址」。
115
+ `auto` 按这个优先级尝试,取第一个能用的:`www.vibeapi.cn` → `www.vibeapi.si` → `hk.vibeapi.cn` → `cf.vibeapi.cn` → `token.nfeyre.top` → `us13-www.vibeapi.cn`。网关控制台以后新增的地址会排在这些后面。保存了令牌时,每个地址都用它实际拉一次模型列表,拉得到才算能用;`vibecli base` 每个地址测 3 次取中位数,并标出最快的一个。第一次 `vibecli setup` / `vibecli init` 时主站连不上,也会按同样的顺序自动换到能用的地址。切换后地址保存在当前 profile 里,Claude Code、Codex、Claude 桌面端等已用 vibecli 配置过的工具会一起改过去,以后再运行 `vibecli setup` 也不会改回主站。首次配置时就可以指定:`vibecli setup --key sk-xxx --base https://www.vibeapi.si`。向导主菜单里也有「切换接入地址」。
116
116
 
117
117
  ## 支持
118
118
 
@@ -1,4 +1,4 @@
1
- import { type GatewayAddress, type ProbeResult } from '../lib/gateway-addresses.js';
1
+ import { type GatewayAddress, type ProbeOptions, type ProbeResult } from '../lib/gateway-addresses.js';
2
2
  /**
3
3
  * `vibecli base` list the gateway's addresses, test each one, mark the current one
4
4
  * `vibecli base auto` switch to the first address (in the operator's order) that works here
@@ -9,9 +9,24 @@ import { type GatewayAddress, type ProbeResult } from '../lib/gateway-addresses.
9
9
  * tool vibecli configured with the saved key, so a later `vibecli setup` keeps it.
10
10
  */
11
11
  export declare function baseCommand(target?: string): Promise<void>;
12
+ /**
13
+ * Before a setup writes anything: when the current address cannot be reached,
14
+ * move to the first address in priority order that can, and save it, so the
15
+ * tools are configured on an address that works from here. An address named
16
+ * with `--base` on this run is left alone. Returns the new address, or null
17
+ * when nothing changed.
18
+ */
19
+ export declare function ensureReachableGatewayAddress(apiKey?: string | undefined): Promise<string | null>;
12
20
  /** The gateway's addresses plus the current one when it is not among them (a custom address). */
13
21
  export declare function addressesWithCurrent(): GatewayAddress[];
14
- export declare function probeAll(addresses: GatewayAddress[]): Promise<ProbeResult[]>;
22
+ /** The key vibecli would use: the profile's, else any tool's dedicated one. */
23
+ export declare function savedApiKey(): string | undefined;
24
+ /** Every address probed at once (each address's samples run one after another). */
25
+ export declare function probeAll(addresses: GatewayAddress[], options?: ProbeOptions): Promise<ProbeResult[]>;
26
+ /** `√ 650 ms · 74 个模型 · 最快` / `× 证书不对(…)` */
27
+ export declare function probeSummary(result: ProbeResult, fastest: boolean): string;
28
+ /** Index of the quickest working address, or -1. */
29
+ export declare function fastestIndex(results: ProbeResult[]): number;
15
30
  export declare function reasonText(result: ProbeResult): string;
16
31
  /** Saves `url` in the active profile and rewrites it into every tool vibecli configured. */
17
32
  export declare function switchGatewayAddress(url: string): Promise<void>;
@@ -1 +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
+ import e from"chalk";import{configManager as r,DEFAULT_BASE_URL as o}from"../lib/config.js";import{i18n as t}from"../lib/i18n.js";import{toolManager as s}from"../lib/tool-manager.js";import{listAdapters as a}from"../lib/tools/registry.js";import{postApplyPrompts as n}from"../lib/post-apply.js";import{emitJson as l,isJsonMode as i}from"../lib/runtime-options.js";import{logger as u}from"../utils/logger.js";import{gatewayAddresses as c,normalizeBaseUrl as p,probeGatewayAddress as d}from"../lib/gateway-addresses.js";export async function baseCommand(s){const a=r.getBaseUrl(),n=addressesWithCurrent(),u=s?.trim().toLowerCase();if(!u)return void await async function(r,o){const s=savedApiKey(),a=await probeAll(r,{apiKey:s,samples:3});i()?l({ok:!0,current:o,addresses:r.map((e,r)=>({...e,...a[r],current:e.url===o}))}):(console.log(e.bold(t.t("base.current",{url:o}))),console.log(e.gray(t.t(s?"base.probe_with_key":"base.probe_without_key"))+"\n"),b(r,a,o),console.log("\n"+e.gray(t.t("base.usage_hint"))))}(n,a);if("auto"===u){const e=await probeAll(n,{apiKey:savedApiKey()}),r=e.find(e=>e.ok);if(!r){const r=e.every(e=>"key_rejected"===e.error);return f("no_reachable",t.t(r?"base.key_rejected_everywhere":"base.none_reachable")),void b(n,e,a)}return void await switchGatewayAddress(r.url)}let c;if("default"===u||"reset"===u)c=o;else if(/^\d+$/.test(u)){if(c=n[Number(u)-1]?.url??null,!c)return void f("bad_index",t.t("base.bad_index",{index:u,count:String(n.length)}))}else if(c=p(s),!c)return void f("bad_url",t.t("base.bad_url",{value:s}));const m=await d(c,{apiKey:savedApiKey()});m.ok?await switchGatewayAddress(c):f("unreachable",t.t("base.unreachable",{url:c,reason:reasonText(m)}))}export async function ensureReachableGatewayAddress(s=savedApiKey()){if(r.hasRuntimeBaseUrl())return null;const a=r.getBaseUrl(),n=await d(a,{apiKey:s});if(n.ok||"key_rejected"===n.error)return null;const l=c().filter(e=>e.url!==a),u=(await probeAll(l,{apiKey:s})).find(e=>e.ok);return u?(u.url===o?r.resetBaseUrl():r.setBaseUrl(u.url,!0),i()||console.log(e.yellow(t.t("base.setup_fallback",{from:a,reason:reasonText(n),url:u.url}))),u.url):null}export function addressesWithCurrent(){const e=r.getBaseUrl(),o=c();return o.some(r=>r.url===e)||o.push({url:e,label:t.t("base.custom_label")}),o}export function savedApiKey(){return r.getApiKey()||a().map(e=>r.getApiKeyForTool(e.name)).find(Boolean)}export async function probeAll(e,r={}){return Promise.all(e.map(e=>d(e.url,r)))}export function probeSummary(r,o){if(!r.ok)return e.red(`× ${reasonText(r)}`);const s=[`${r.ms} ms`];return void 0!==r.models&&s.push(t.t("base.models_count",{count:String(r.models)})),o&&s.push(t.t("base.fastest")),e.green(`√ ${s.join(" · ")}`)}export function fastestIndex(e){let r=-1;return e.forEach((o,t)=>{o.ok&&(r<0||o.ms<e[r].ms)&&(r=t)}),r}export function reasonText(e){if(!e.error)return"";const r=t.t(`base.error_${e.error}`);return e.detail?`${r}(${e.detail})`:r}function f(r,o){process.exitCode=1,i()?l({ok:!1,error:r,message:o}):console.error(e.red(o))}function b(r,o,s){if(i())return;const a=o.length>1?fastestIndex(o):-1;r.forEach((r,n)=>{const l=r.url===s?e.cyan(` ← ${t.t("base.current_mark")}`):"";console.log(`${n+1}. ${r.url} ${e.gray(r.label)}\n ${probeSummary(o[n],n===a)}${l}`)})}export async function switchGatewayAddress(c){const p=r.getBaseUrl();c===o?r.resetBaseUrl():r.setBaseUrl(c,!0);const d=[],f=[];for(const e of function(){const e=[];for(const o of a()){const t=r.getApiKeyForTool(o.name);if(t)try{o.detectCurrentConfig().apiKey===t&&e.push(o.name)}catch(e){u.logError(`base.detectCurrentConfig(${o.name})`,e)}}return e}()){const o=r.getApiKeyForTool(e);try{await s.applyPrivateConfig(e,o,r.getModelsForTool(e)),d.push(e)}catch(r){f.push({tool:e,reason:r instanceof Error?r.message:String(r)})}}if(f.length>0&&(process.exitCode=1),i())l({ok:0===f.length,previous:p,base_url:c,applied:d,failed:f});else{console.log(e.green(t.t(c===p?"base.kept":"base.switched",{url:c}))),d.length>0&&console.log(t.t("base.tools_moved"));for(const e of d)s.printApplied(e);for(const{tool:r,reason:o}of f)console.error(e.red(`× ${t.t("setup.apply_failed",{tool:r,reason:o})}`));0===d.length&&0===f.length&&console.log(e.yellow(t.t("base.no_tools",{url:c}))),await n(d)}}
@@ -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 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
+ 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=n.getApiKey(),a=await Promise.all(s.map(t=>y(t.url,{apiKey:e}))),r=s.filter((t,o)=>a[o].ok).map(t=>t.url);return 0===r.length?{name:t,status:"fail",message:i.t("doctor.other_addresses_none")}:{name:t,status:"warn",message:i.t("doctor.other_addresses_found",{urls:r.join(", ")})}}()))}else $.push({name:b,status:"fail",message:i.t("doctor.api_key_missing")});x.text=i.t("doctor.check_tools");const A=c.getInstalledTools();$.push({name:i.t("doctor.installed_tools"),status:A.length>0?"pass":"warn",message:A.length>0?A.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 v=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")}}();v&&$.push(v),A.includes("claude-code")&&g()&&$.push({name:i.t("doctor.claude_login"),status:"pass",message:i.t("tools.claude_code.account_login_overridden")});const N=A.includes("codex")?_():null;A.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&&A.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&&A.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&&A.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 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
+ import e from"chalk";import{getAdapter as o}from"../lib/tools/registry.js";import{postApplyPrompts as t}from"../lib/post-apply.js";import{ensureReachableGatewayAddress as s}from"./base.js";import{i18n as i}from"../lib/i18n.js";import{configManager as a}from"../lib/config.js";import{toolManager as l,SUPPORTED_TOOLS as n}from"../lib/tool-manager.js";import{validateApiKey as d,fetchModels as r}from"../lib/api-validator.js";import{CODEX_ONE_MILLION_CONTEXT_SETTING as p,setCodexOneMillionContext as c}from"../lib/tools/codex.js";import{resolveModelCatalog as u}from"../lib/model-catalog.js";import{CLAUDE_DESKTOP_FORCE_REGISTRY_SETTING as f,CLAUDE_DESKTOP_HIDE_OFFICIAL_LOGIN_SETTING as m}from"../lib/tools/claude-desktop.js";async function g(e){const o=await r(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 k(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 y(o){console.log(e.green(o))}function _(o){console.log(e.yellow(o))}function b(o){console.error(e.red(o))}async function h(o,t){console.log(e.gray(i.t("setup.validating",{label:o})));const s=await d(t);return!!s.valid||(b(i.t("setup.invalid_key",{label:o,reason:s.message||s.error||"invalid"})),"network_error"===s.error&&_(i.t("base.network_hint")),!1)}async function x(o,t,s={}){const{oneMillion:d,officialLogin:r,desktopRegistry:c,force:u}=s,g=n[o];try{"claude-desktop"===o&&void 0!==c&&a.setToolSetting("claude-desktop",f,c),"codex"===o&&void 0!==d&&a.setToolSetting("codex",p,d),"claude-desktop"===o&&void 0!==r&&a.setToolSetting("claude-desktop",m,!r),await l.applyPrivateConfig(o,t,a.getModelsForTool(o),{force:u})}catch(e){const o=(e instanceof Error?e.message:String(e)).replace(/\s+/g," ").trim();return b("× "+i.t("setup.apply_failed",{tool:g.displayName,reason:o})),!1}return l.printApplied(o),u&&console.log(e.gray(" "+i.t("config.force_done",{tool:g.displayName}))),"codex"===o&&void 0!==d&&console.log(e.gray(" "+i.t(d?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))),l.isToolInstalled(o)||console.log(e.yellow(" ! "+i.t("setup.install_hint",{tool:g.displayName,install:g.installCommand}))),!0}export async function setupCommand(e){const d=e.key?.trim(),r=e.claudekey?.trim(),f=e.codexkey?.trim(),m=e.grokkey?.trim(),v=e.desktopkey?.trim(),w=void 0!==e.oneMillion,C=void 0!==e.claudeOfficial;let K;if(C&&(K=parseOnOff(e.claudeOfficial),void 0===K))return b(i.t("setup.claude_official_invalid",{value:String(e.claudeOfficial)})),void(process.exitCode=1);const M=void 0!==e.desktopRegistry;let j;if(M&&(j=parseOnOff(e.desktopRegistry),void 0===j))return b(i.t("setup.desktop_registry_invalid",{value:String(e.desktopRegistry)})),void(process.exitCode=1);const S=C||M,G=Boolean(d||r||f||m||v),T={oneMillion:e.oneMillion,officialLogin:K,desktopRegistry:j,force:e.force};if(!G&&(w||S)){if(w){const o=a.getApiKeyForTool("codex");if(o)try{const t=!0===e.oneMillion;a.setToolSetting("codex",p,t);const s=await u(o);c(t,s??void 0),y(i.t(t?"setup.codex_1m_enabled":"setup.codex_1m_disabled"))}catch(e){b(i.t("setup.apply_failed",{tool:n.codex.displayName,reason:e instanceof Error?e.message:String(e)})),process.exitCode=1}else b(i.t("setup.codex_1m_missing_key")),process.exitCode=1}if(S){const e=a.getApiKeyForTool("claude-desktop");e?await x("claude-desktop",e,{...T,oneMillion:void 0})?await t(["claude-desktop"]):process.exitCode=1:(b(i.t("setup.desktop_official_login_missing_key")),process.exitCode=1)}return}if(!G)return b(i.t("setup.usage")),void(process.exitCode=1);await s(d||r||f||m||v);let O=0,A=0;const N=new Set,R=[];r&&R.push({tool:"claude-code",label:"--claudekey",apiKey:r,need:"claude"}),f&&R.push({tool:"codex",label:"--codexkey",apiKey:f,need:"gpt"}),m&&R.push({tool:"grok",label:"--grokkey",apiKey:m,need:"grok"}),v&&R.push({tool:"claude-desktop",label:"--desktopkey",apiKey:v,need:"claude"});for(const e of R){if(!await h(e.label,e.apiKey)){A+=1;continue}const o=await g(e.apiKey);!o.probed||k(o,e.need)?(o.probed||_(i.t("setup.probe_failed",{label:e.label})),a.setToolApiKey(e.tool,e.apiKey),await x(e.tool,e.apiKey,T)?(O+=1,N.add(e.tool)):A+=1):(b(i.t(`setup.key_no_${e.need}`,{label:e.label,tool:n[e.tool].displayName})),A+=1)}if(d){if(!await h("--key",d))return void(process.exitCode=1);a.setApiKey(d);const e=await g(d);if(e.probed||_(i.t("setup.probe_failed",{label:"--key"})),e.probed&&!e.hasClaude&&!e.hasGpt&&!e.hasGrok)return b(i.t("setup.key_no_family")),void(process.exitCode=1);const o=[{tool:"claude-code",capable:e.hasClaude,skipMsg:i.t("setup.skip_no_claude")},{tool:"codex",capable:e.hasGpt,skipMsg:i.t("setup.skip_no_gpt")},{tool:"grok",capable:e.hasGrok,skipMsg:i.t("setup.skip_no_grok")},{tool:"claude-desktop",capable:e.hasClaude&&l.isToolInstalled("claude-desktop"),skipMsg:i.t("setup.skip_desktop_not_installed"),quiet:!e.hasClaude}];for(const e of o)N.has(e.tool)||(e.capable?await x(e.tool,d,T)?(O+=1,N.add(e.tool)):A+=1:e.quiet||_(e.skipMsg))}if(O>0){const e=[...N].filter(e=>!o(e)?.isInstalled).map(e=>n[e]?.command).filter(Boolean).join(" / "),t=i.t("setup.done",{count:String(O)});y("\n"+(e?`${t}${i.t("setup.done_commands",{commands:e})}`:t))}A>0&&b(i.t("setup.failed_summary",{count:String(A)})),await t([...N]),(A>0||0===O)&&(process.exitCode=1)}
@@ -1,13 +1,15 @@
1
1
  import { Command as Commander } from 'commander';
2
2
  export declare class Command {
3
3
  private program;
4
+ /** Set by `preflightArgs`: this run configures tools (see CONFIGURING_COMMANDS). */
5
+ private configuring;
4
6
  constructor();
5
7
  private getVersion;
6
8
  private setupProgram;
7
9
  private handleInitCommand;
8
10
  /**
9
- * 启动即检测 shell 环境里泄漏的 ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY 并清除,
10
- * 否则它们会覆盖 vibecli 写进各工具配置文件的凭据。
11
+ * 配置类命令(CONFIGURING_COMMANDS 与向导)启动时检测 shell 环境里泄漏的
12
+ * ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY 并清除,否则它们会覆盖 vibecli 写进各工具配置文件的凭据。
11
13
  */
12
14
  private cleanupLeakedEnvVars;
13
15
  private preflightArgs;
@@ -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{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}}
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;configuring=!1;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)}const s=void 0===n&&!["--help","-h","--version","-v"].some(o);this.configuring=s||void 0!==n&&_.has(n),i&&t.setBaseUrl(b(i)??i,this.configuring)}async execute(e){try{if(this.preflightArgs(e),this.configuring&&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}}
@@ -133,6 +133,8 @@ export declare class ConfigManager {
133
133
  /** vibecli's own directory (`~/.vibecli`, or the test / VIBECLI_CONFIG_DIR override). */
134
134
  getConfigDir(): string;
135
135
  getBaseUrl(): string;
136
+ /** True when `--base` was given on this run (the user named the address). */
137
+ hasRuntimeBaseUrl(): boolean;
136
138
  setBaseUrl(url: string, persist?: boolean): void;
137
139
  resetBaseUrl(): void;
138
140
  }
@@ -1 +1 @@
1
- import{existsSync as e,mkdirSync as t,readFileSync as i,writeFileSync as o,renameSync as s,unlinkSync as r,readdirSync as n,rmdirSync as a,statSync as c,chmodSync as l}from"fs";import{join as f,dirname as g}from"path";import{homedir as h}from"os";import{createHash as p}from"crypto";import*as m from"js-yaml";import{logger as u}from"../utils/logger.js";import{getDefaultModels as y,isRetiredDefaultModel as d}from"./model-registry.js";import{readLegacySecretsFile as _}from"./secret-store.js";export const DEFAULT_BASE_URL="https://www.vibeapi.cn";export const DEFAULT_MODELS=y();export const DEFAULT_PROFILE_NAME="default";const k="plaintext";export class ConfigManager{static instance;configDir;configPath;config;runtimeBaseUrl;constructor(){this.configDir=this.resolveConfigDir(),this.migrateLegacyConfigDir(),this.configPath=f(this.configDir,"config.yaml"),this.config=this.loadConfig()}migrateLegacyConfigDir(){try{const t=f(g(this.configDir),".chelper");if(t===this.configDir||!e(t))return;if(["config.yaml","secrets.enc"].some(t=>e(f(this.configDir,t))))return;if(e(this.configDir)){for(const i of n(t)){const o=f(this.configDir,i);e(o)||s(f(t,i),o)}0===n(t).length&&a(t)}else s(t,this.configDir);u.record(`Migrated config dir: ${t} -> ${this.configDir}`)}catch(e){u.warn("Failed to migrate legacy .chelper config dir:",e)}}resolveConfigDir(){const e=process.env.VIBECLI_CONFIG_DIR;if(e&&e.trim())return e.trim();const t=process.env.VIBECLI_TEST_HOME;return t&&t.trim()?f(t.trim(),".vibecli"):f(h(),".vibecli")}createDefaultConfig(){return this.syncLegacyFields({lang:"en_US",active_profile:"default",profiles:{[DEFAULT_PROFILE_NAME]:{}},presets:{}})}normalizeModels(e){const t=t=>{const i=e?.[t]?.trim();return i&&!d(t,i)?i:DEFAULT_MODELS[t]},i={haiku:t("haiku"),sonnet:t("sonnet"),opus:t("opus"),reasoning:t("reasoning")};if(e&&"object"==typeof e)for(const[t,o]of Object.entries(e))t in i||"string"==typeof o&&o.trim()&&(i[t]=o.trim());return i}normalizeToolModels(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||"string"!=typeof e||!t||"object"!=typeof t)return[];const i={};for(const[e,o]of Object.entries(t))"string"==typeof o&&o.trim()&&(i[e]=o.trim());return Object.keys(i).length>0?[[e,i]]:[]});return Object.fromEntries(t)}normalizeToolSettings(e){if(!e||"object"!=typeof e||Array.isArray(e))return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||!t||"object"!=typeof t||Array.isArray(t))return[];const i=Object.fromEntries(Object.entries(t).filter(([,e])=>"string"==typeof e||"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e)));return Object.keys(i).length>0?[[e,i]]:[]});return Object.fromEntries(t)}normalizeStringRecord(e){return!e||"object"!=typeof e||Array.isArray(e)?{}:Object.fromEntries(Object.entries(e).filter(e=>"string"==typeof e[1]&&Boolean(e[1].trim())).map(([e,t])=>[e,t.trim()]))}normalizeProfile(e){if(!e)return{};const t=this.normalizeToolModels(e.tool_models),i=this.normalizeToolSettings(e.tool_settings),o=this.normalizeStringRecord(e.tool_api_keys);return{..."string"==typeof e.api_key&&e.api_key?{api_key:e.api_key}:{},...Object.keys(o).length>0?{tool_api_keys:o}:{},..."string"==typeof e.base_url&&e.base_url?{base_url:e.base_url}:{},...e.models?{models:this.normalizeModels(e.models)}:{},...Object.keys(t).length>0?{tool_models:t}:{},...Object.keys(i).length>0?{tool_settings:i}:{}}}normalizeProfiles(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>e&&"string"==typeof e&&t&&"object"==typeof t?[[e,this.normalizeProfile(t)]]:[]);return Object.fromEntries(t)}normalizePresets(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||"string"!=typeof e||!t||"object"!=typeof t)return[];const i=t;return i.models?[[e,{models:this.normalizeModels(i.models),updated_at:i.updated_at||new Date(0).toISOString()}]]:[]});return Object.fromEntries(t)}getResolvedActiveProfile(e){const t=e.active_profile||"default";return e.profiles?.[t]||{}}syncLegacyFields(e){const t=this.normalizeProfiles(e.profiles),i=e.active_profile?.trim()||Object.keys(t)[0]||"default",o=t[i]?t:{...t,[i]:{}},s=this.getResolvedActiveProfile({...e,profiles:o,active_profile:i});return{...e,lang:e.lang||"en_US",active_profile:i,profiles:o,presets:this.normalizePresets(e.presets),api_key:s.api_key,base_url:s.base_url,models:s.models?this.normalizeModels(s.models):DEFAULT_MODELS}}sanitizeBackupLabel(e){return(e||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)}getBackupDir(){return f(this.configDir,"backups")}getLegacySecretsPath(){return f(this.configDir,"secrets.enc")}getSecretsBackupPath(e){return e.replace(/\.yaml$/,".secrets.enc")}static getInstance(){return ConfigManager.instance||(ConfigManager.instance=new ConfigManager),ConfigManager.instance}ensureConfigDir(){e(this.configDir)||t(this.configDir,{recursive:!0})}loadConfig(){try{if(e(this.configPath)){const t=i(this.configPath,"utf-8"),o=m.load(t);let s=this.migrateOldConfig(o||{lang:"en_US"}),n=!1;const a=this.getLegacySecretsPath();if(e(a)&&o?.credential_store!==k)try{s=this.mergeLegacySecrets(s,_(a)),n=!0}catch(e){u.warn("Legacy secrets.enc is unreadable on this host; it was kept for recovery and will not block new credentials."),u.logError("ConfigManager.migrateLegacySecrets",e)}if((this.needsMigration(o)||n)&&(this.backupConfig(),this.atomicWrite(this.configPath,m.dump(this.prepareForPersistence(s))),n))try{r(a)}catch(e){u.logError("ConfigManager.removeLegacySecrets",e)}try{l(this.configPath,384)}catch{}return s}const t=this.getLegacySecretsPath();if(e(t))try{const e=this.mergeLegacySecrets(this.createDefaultConfig(),_(t));this.ensureConfigDir(),this.atomicWrite(this.configPath,m.dump(this.prepareForPersistence(e)));try{r(t)}catch(e){u.logError("ConfigManager.removeLegacySecrets",e)}return e}catch(e){u.warn("Legacy secrets.enc is unreadable on this host; it was kept for recovery and will not block new credentials."),u.logError("ConfigManager.migrateLegacySecretsWithoutConfig",e)}}catch(e){u.warn("Failed to load config, using defaults:",e),u.logError("ConfigManager.loadConfig",e)}return this.createDefaultConfig()}needsMigration(e){return!(!e||"object"!=typeof e)&&("plan"in e||!("profiles"in e)||!("active_profile"in e)||!("presets"in e))}migrateOldConfig(e){if(!e||"object"!=typeof e)return this.createDefaultConfig();const t=e,{plan:i,...o}=t,s=this.normalizeProfiles(o.profiles),r=this.normalizeProfile({api_key:"string"==typeof o.api_key?o.api_key:void 0,base_url:"string"==typeof o.base_url?o.base_url:void 0,models:o.models}),n=Object.keys(s).length>0?s:{[DEFAULT_PROFILE_NAME]:r},a="string"==typeof o.active_profile&&o.active_profile.trim()?o.active_profile.trim():Object.keys(n)[0]||"default";return this.syncLegacyFields({lang:"string"==typeof o.lang&&o.lang?o.lang:"en_US",...!0===o.lang_chosen?{lang_chosen:!0}:{},...o.credential_store===k?{credential_store:k}:{},active_profile:a,profiles:n,presets:this.normalizePresets(o.presets)})}atomicWrite(t,i){const n=`${t}.tmp`;try{o(n,i,{encoding:"utf-8",mode:384}),s(n,t);try{l(t,384)}catch{}}catch(t){if(e(n))try{r(n)}catch{}throw t}}backupConfig(){if(!e(this.configPath))return;const s=this.getBackupDir();e(s)||t(s,{recursive:!0});const r=(new Date).toISOString().replace(/[:.]/g,"-"),n=f(s,`config-${r}.yaml`);try{const e=i(this.configPath,"utf-8");o(n,e,{encoding:"utf-8",mode:384}),this.rotateBackups(s,10,30)}catch(e){u.logError("ConfigManager.backupConfig",e)}}rotateBackups(t,i,o){try{const s=Date.now()-24*o*60*60*1e3,a=n(t).filter(e=>e.startsWith("config-")&&e.endsWith(".yaml")).map(e=>({name:e,path:f(t,e),mtime:c(f(t,e)).mtime})).sort((e,t)=>t.mtime.getTime()-e.mtime.getTime()),l=new Set;a.slice(i).forEach(e=>l.add(e.path)),a.forEach(e=>{e.mtime.getTime()<s&&l.add(e.path)});for(const t of l){let i=!1;try{r(t),i=!0}catch(e){u.logError("ConfigManager.rotateBackups",e)}if(i){const i=this.getSecretsBackupPath(t);if(e(i))try{r(i)}catch(e){u.logError("ConfigManager.rotateBackups.secrets",e)}}}}catch(e){u.logError("ConfigManager.rotateBackups",e)}}saveConfig(e){try{this.ensureConfigDir(),this.backupConfig();const t=this.syncLegacyFields(this.migrateOldConfig(e||this.config)),i=this.prepareForPersistence(t),o=m.dump(i);this.atomicWrite(this.configPath,o),this.config=t}catch(e){throw u.error("Failed to save config:",e),u.logError("ConfigManager.saveConfig",e),e}}prepareForPersistence(e){const t={...e,credential_store:k};return delete t.api_key,delete t.base_url,delete t.models,t}mergeLegacySecrets(e,t){const i={...e.profiles||{}},o=new Set([...Object.keys(t.api_keys||{}),...Object.keys(t.tool_api_keys||{})]);for(const e of o){const o=i[e]||{},s=o.api_key||t.api_keys?.[e],r={...t.tool_api_keys?.[e]||{},...o.tool_api_keys||{}};i[e]={...o,...s?{api_key:s}:{},...Object.keys(r).length>0?{tool_api_keys:r}:{}}}return this.syncLegacyFields({...e,profiles:i})}getConfig(){return this.syncLegacyFields({...this.config})}updateConfig(e){this.saveConfig({...this.config,...e})}isFirstRun(){return!e(this.configPath)}getLang(){return this.config.lang||"en_US"}isLangChosen(){return!0===this.config.lang_chosen}setLang(e){this.updateConfig({lang:e,lang_chosen:!0})}getActiveProfileName(){return this.config.active_profile||"default"}listProfiles(){return Object.keys(this.config.profiles||{}).sort()}getProfile(e=this.getActiveProfileName()){const t=this.config.profiles?.[e]||{};return{...t.api_key?{api_key:t.api_key}:{},...t.tool_api_keys&&Object.keys(t.tool_api_keys).length>0?{tool_api_keys:this.normalizeStringRecord(t.tool_api_keys)}:{},...t.base_url?{base_url:t.base_url}:{},models:t.models?this.normalizeModels(t.models):DEFAULT_MODELS,...t.tool_models&&Object.keys(t.tool_models).length>0?{tool_models:this.normalizeToolModels(t.tool_models)}:{},...t.tool_settings&&Object.keys(t.tool_settings).length>0?{tool_settings:this.normalizeToolSettings(t.tool_settings)}:{}}}createProfile(e,t=this.getActiveProfileName()){const i=this.getProfile(t),o={...this.config.profiles||{},[e]:{...i.api_key?{api_key:i.api_key}:{},...i.base_url?{base_url:i.base_url}:{},models:this.normalizeModels(i.models)}};this.saveConfig({...this.config,profiles:o})}setActiveProfile(e){if(!this.config.profiles?.[e])throw new Error(`Profile not found: ${e}`);this.saveConfig({...this.config,active_profile:e})}updateProfile(e,t){const i=this.config.profiles?.[e]||{},o=t.models?this.normalizeModels({...i.models,...t.models}):i.models?this.normalizeModels(i.models):void 0,s={...i,...void 0!==t.api_key?{api_key:t.api_key}:{},...void 0!==t.base_url?{base_url:t.base_url}:{},...o?{models:o}:{}},r={...this.config.profiles||{},[e]:s};this.saveConfig({...this.config,profiles:r})}deleteProfile(e){const t={...this.config.profiles||{}};if(!t[e])return;const i=Object.keys(t).filter(t=>t!==e);if(0===i.length)throw new Error("At least one profile must remain");delete t[e];const o=this.getActiveProfileName()===e?i[0]:this.getActiveProfileName();this.saveConfig({...this.config,profiles:t,active_profile:o})}getApiKey(){return this.config.profiles?.[this.getActiveProfileName()]?.api_key}setApiKey(e){this.updateProfile(this.getActiveProfileName(),{api_key:e})}revokeApiKey(){const e=this.config.profiles?.[this.getActiveProfileName()]||{},{api_key:t,tool_api_keys:i,...o}=e,s={...this.config.profiles||{},[this.getActiveProfileName()]:o};this.saveConfig({...this.config,profiles:s})}getApiKeyFingerprint(){const e=this.getApiKey();return e?p("sha256").update(e).digest("hex").slice(0,12):""}getApiKeyForTool(e){const t=this.config.profiles?.[this.getActiveProfileName()];return t?.tool_api_keys?.[e]||t?.api_key||void 0}setToolApiKey(e,t){const i=this.getActiveProfileName(),o=this.config.profiles?.[i]||{},s={...this.config.profiles||{},[i]:{...o,tool_api_keys:{...o.tool_api_keys||{},[e]:t}}};this.saveConfig({...this.config,profiles:s})}clearToolApiKey(e){const t=this.getActiveProfileName(),i=this.config.profiles?.[t]||{},{[e]:o,...s}=i.tool_api_keys||{},r={...i};Object.keys(s).length>0?r.tool_api_keys=s:delete r.tool_api_keys,this.saveConfig({...this.config,profiles:{...this.config.profiles||{},[t]:r}})}listToolApiKeys(){return Object.keys(this.config.profiles?.[this.getActiveProfileName()]?.tool_api_keys||{}).sort()}getToolApiKeyFingerprint(e){const t=this.config.profiles?.[this.getActiveProfileName()]?.tool_api_keys?.[e];return t?p("sha256").update(t).digest("hex").slice(0,12):""}getModels(){const e=this.config.profiles?.[this.getActiveProfileName()]?.models;return e?this.normalizeModels(e):DEFAULT_MODELS}setModels(e){this.updateProfile(this.getActiveProfileName(),{models:this.normalizeModels(e)})}updateModels(e){this.setModels(this.normalizeModels({...this.getModels(),...e}))}getModelsForTool(e){const t=this.getModels(),i=this.config.profiles?.[this.getActiveProfileName()]?.tool_models?.[e];return i?this.normalizeModels({...t,...i}):t}setToolModel(e,t){const i=this.getActiveProfileName(),o=this.config.profiles?.[i]||{},s={...o.tool_models||{}};t&&0!==Object.keys(t).length?s[e]=t:delete s[e];const r={...o};Object.keys(s).length>0?r.tool_models=s:delete r.tool_models;const n={...this.config.profiles||{},[i]:r};this.saveConfig({...this.config,profiles:n})}getToolSettings(e){const t=this.config.profiles?.[this.getActiveProfileName()]?.tool_settings?.[e];return t?{...t}:{}}getToolSetting(e,t){return this.config.profiles?.[this.getActiveProfileName()]?.tool_settings?.[e]?.[t]}setToolSetting(e,t,i){if(this.getToolSetting(e,t)===i)return;const o=this.getActiveProfileName(),s=this.config.profiles?.[o]||{},r={...s.tool_settings||{}};r[e]={...r[e]||{},[t]:i};const n={...this.config.profiles||{},[o]:{...s,tool_settings:r}};this.saveConfig({...this.config,profiles:n})}getCustomPresets(){return{...this.config.presets||{}}}saveCustomPreset(e,t){const i={...this.config.presets||{},[e]:{models:this.normalizeModels(t),updated_at:(new Date).toISOString()}};this.saveConfig({...this.config,presets:i})}deleteCustomPreset(e){const t={...this.config.presets||{}};t[e]&&(delete t[e],this.saveConfig({...this.config,presets:t}))}listBackups(){const t=this.getBackupDir();return e(t)?n(t).filter(e=>e.startsWith("config-")&&e.endsWith(".yaml")).map(e=>{const i=f(t,e);return{name:e,path:i,mtimeMs:c(i).mtimeMs}}).sort((e,t)=>t.mtimeMs-e.mtimeMs):[]}createBackup(s){this.ensureConfigDir();const r=this.getBackupDir();e(r)||t(r,{recursive:!0});const n=(new Date).toISOString().replace(/[:.]/g,"-"),a=this.sanitizeBackupLabel(s),c=f(r,a?`config-${n}-${a}.yaml`:`config-${n}.yaml`),l=e(this.configPath)?i(this.configPath,"utf-8"):m.dump(this.prepareForPersistence(this.getConfig()));return o(c,l,{encoding:"utf-8",mode:384}),this.rotateBackups(r,10,30),c}restoreBackup(t){if(!t||"."===t||".."===t||/[/\\]|\.\./.test(t))throw new Error(`Invalid backup name: ${t}`);const o=this.getBackupDir(),s=f(o,t);if(!e(s))throw new Error(`Backup not found: ${t}`);const r=i(s,"utf-8"),n=m.load(r),a=this.getSecretsBackupPath(s);let c=this.migrateOldConfig(n||this.createDefaultConfig());if(n?.credential_store!==k&&e(a))try{c=this.mergeLegacySecrets(c,_(a))}catch(e){throw new Error(`Legacy backup credentials could not be decrypted: ${a}`,{cause:e})}this.saveConfig(c)}getConfigDir(){return this.configDir}getBaseUrl(){return this.runtimeBaseUrl||this.config.profiles?.[this.getActiveProfileName()]?.base_url||DEFAULT_BASE_URL}setBaseUrl(e,t=!1){this.runtimeBaseUrl=e,t&&this.updateProfile(this.getActiveProfileName(),{base_url:e})}resetBaseUrl(){this.runtimeBaseUrl=void 0;const e=this.config.profiles?.[this.getActiveProfileName()]||{},{base_url:t,...i}=e,o={...this.config.profiles||{},[this.getActiveProfileName()]:i};this.saveConfig({...this.config,profiles:o})}}export const configManager=ConfigManager.getInstance();
1
+ import{existsSync as e,mkdirSync as t,readFileSync as i,writeFileSync as o,renameSync as s,unlinkSync as r,readdirSync as n,rmdirSync as a,statSync as c,chmodSync as l}from"fs";import{join as f,dirname as g}from"path";import{homedir as h}from"os";import{createHash as p}from"crypto";import*as m from"js-yaml";import{logger as u}from"../utils/logger.js";import{getDefaultModels as y,isRetiredDefaultModel as d}from"./model-registry.js";import{readLegacySecretsFile as _}from"./secret-store.js";export const DEFAULT_BASE_URL="https://www.vibeapi.cn";export const DEFAULT_MODELS=y();export const DEFAULT_PROFILE_NAME="default";const k="plaintext";export class ConfigManager{static instance;configDir;configPath;config;runtimeBaseUrl;constructor(){this.configDir=this.resolveConfigDir(),this.migrateLegacyConfigDir(),this.configPath=f(this.configDir,"config.yaml"),this.config=this.loadConfig()}migrateLegacyConfigDir(){try{const t=f(g(this.configDir),".chelper");if(t===this.configDir||!e(t))return;if(["config.yaml","secrets.enc"].some(t=>e(f(this.configDir,t))))return;if(e(this.configDir)){for(const i of n(t)){const o=f(this.configDir,i);e(o)||s(f(t,i),o)}0===n(t).length&&a(t)}else s(t,this.configDir);u.record(`Migrated config dir: ${t} -> ${this.configDir}`)}catch(e){u.warn("Failed to migrate legacy .chelper config dir:",e)}}resolveConfigDir(){const e=process.env.VIBECLI_CONFIG_DIR;if(e&&e.trim())return e.trim();const t=process.env.VIBECLI_TEST_HOME;return t&&t.trim()?f(t.trim(),".vibecli"):f(h(),".vibecli")}createDefaultConfig(){return this.syncLegacyFields({lang:"en_US",active_profile:"default",profiles:{[DEFAULT_PROFILE_NAME]:{}},presets:{}})}normalizeModels(e){const t=t=>{const i=e?.[t]?.trim();return i&&!d(t,i)?i:DEFAULT_MODELS[t]},i={haiku:t("haiku"),sonnet:t("sonnet"),opus:t("opus"),reasoning:t("reasoning")};if(e&&"object"==typeof e)for(const[t,o]of Object.entries(e))t in i||"string"==typeof o&&o.trim()&&(i[t]=o.trim());return i}normalizeToolModels(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||"string"!=typeof e||!t||"object"!=typeof t)return[];const i={};for(const[e,o]of Object.entries(t))"string"==typeof o&&o.trim()&&(i[e]=o.trim());return Object.keys(i).length>0?[[e,i]]:[]});return Object.fromEntries(t)}normalizeToolSettings(e){if(!e||"object"!=typeof e||Array.isArray(e))return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||!t||"object"!=typeof t||Array.isArray(t))return[];const i=Object.fromEntries(Object.entries(t).filter(([,e])=>"string"==typeof e||"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e)));return Object.keys(i).length>0?[[e,i]]:[]});return Object.fromEntries(t)}normalizeStringRecord(e){return!e||"object"!=typeof e||Array.isArray(e)?{}:Object.fromEntries(Object.entries(e).filter(e=>"string"==typeof e[1]&&Boolean(e[1].trim())).map(([e,t])=>[e,t.trim()]))}normalizeProfile(e){if(!e)return{};const t=this.normalizeToolModels(e.tool_models),i=this.normalizeToolSettings(e.tool_settings),o=this.normalizeStringRecord(e.tool_api_keys);return{..."string"==typeof e.api_key&&e.api_key?{api_key:e.api_key}:{},...Object.keys(o).length>0?{tool_api_keys:o}:{},..."string"==typeof e.base_url&&e.base_url?{base_url:e.base_url}:{},...e.models?{models:this.normalizeModels(e.models)}:{},...Object.keys(t).length>0?{tool_models:t}:{},...Object.keys(i).length>0?{tool_settings:i}:{}}}normalizeProfiles(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>e&&"string"==typeof e&&t&&"object"==typeof t?[[e,this.normalizeProfile(t)]]:[]);return Object.fromEntries(t)}normalizePresets(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||"string"!=typeof e||!t||"object"!=typeof t)return[];const i=t;return i.models?[[e,{models:this.normalizeModels(i.models),updated_at:i.updated_at||new Date(0).toISOString()}]]:[]});return Object.fromEntries(t)}getResolvedActiveProfile(e){const t=e.active_profile||"default";return e.profiles?.[t]||{}}syncLegacyFields(e){const t=this.normalizeProfiles(e.profiles),i=e.active_profile?.trim()||Object.keys(t)[0]||"default",o=t[i]?t:{...t,[i]:{}},s=this.getResolvedActiveProfile({...e,profiles:o,active_profile:i});return{...e,lang:e.lang||"en_US",active_profile:i,profiles:o,presets:this.normalizePresets(e.presets),api_key:s.api_key,base_url:s.base_url,models:s.models?this.normalizeModels(s.models):DEFAULT_MODELS}}sanitizeBackupLabel(e){return(e||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)}getBackupDir(){return f(this.configDir,"backups")}getLegacySecretsPath(){return f(this.configDir,"secrets.enc")}getSecretsBackupPath(e){return e.replace(/\.yaml$/,".secrets.enc")}static getInstance(){return ConfigManager.instance||(ConfigManager.instance=new ConfigManager),ConfigManager.instance}ensureConfigDir(){e(this.configDir)||t(this.configDir,{recursive:!0})}loadConfig(){try{if(e(this.configPath)){const t=i(this.configPath,"utf-8"),o=m.load(t);let s=this.migrateOldConfig(o||{lang:"en_US"}),n=!1;const a=this.getLegacySecretsPath();if(e(a)&&o?.credential_store!==k)try{s=this.mergeLegacySecrets(s,_(a)),n=!0}catch(e){u.warn("Legacy secrets.enc is unreadable on this host; it was kept for recovery and will not block new credentials."),u.logError("ConfigManager.migrateLegacySecrets",e)}if((this.needsMigration(o)||n)&&(this.backupConfig(),this.atomicWrite(this.configPath,m.dump(this.prepareForPersistence(s))),n))try{r(a)}catch(e){u.logError("ConfigManager.removeLegacySecrets",e)}try{l(this.configPath,384)}catch{}return s}const t=this.getLegacySecretsPath();if(e(t))try{const e=this.mergeLegacySecrets(this.createDefaultConfig(),_(t));this.ensureConfigDir(),this.atomicWrite(this.configPath,m.dump(this.prepareForPersistence(e)));try{r(t)}catch(e){u.logError("ConfigManager.removeLegacySecrets",e)}return e}catch(e){u.warn("Legacy secrets.enc is unreadable on this host; it was kept for recovery and will not block new credentials."),u.logError("ConfigManager.migrateLegacySecretsWithoutConfig",e)}}catch(e){u.warn("Failed to load config, using defaults:",e),u.logError("ConfigManager.loadConfig",e)}return this.createDefaultConfig()}needsMigration(e){return!(!e||"object"!=typeof e)&&("plan"in e||!("profiles"in e)||!("active_profile"in e)||!("presets"in e))}migrateOldConfig(e){if(!e||"object"!=typeof e)return this.createDefaultConfig();const t=e,{plan:i,...o}=t,s=this.normalizeProfiles(o.profiles),r=this.normalizeProfile({api_key:"string"==typeof o.api_key?o.api_key:void 0,base_url:"string"==typeof o.base_url?o.base_url:void 0,models:o.models}),n=Object.keys(s).length>0?s:{[DEFAULT_PROFILE_NAME]:r},a="string"==typeof o.active_profile&&o.active_profile.trim()?o.active_profile.trim():Object.keys(n)[0]||"default";return this.syncLegacyFields({lang:"string"==typeof o.lang&&o.lang?o.lang:"en_US",...!0===o.lang_chosen?{lang_chosen:!0}:{},...o.credential_store===k?{credential_store:k}:{},active_profile:a,profiles:n,presets:this.normalizePresets(o.presets)})}atomicWrite(t,i){const n=`${t}.tmp`;try{o(n,i,{encoding:"utf-8",mode:384}),s(n,t);try{l(t,384)}catch{}}catch(t){if(e(n))try{r(n)}catch{}throw t}}backupConfig(){if(!e(this.configPath))return;const s=this.getBackupDir();e(s)||t(s,{recursive:!0});const r=(new Date).toISOString().replace(/[:.]/g,"-"),n=f(s,`config-${r}.yaml`);try{const e=i(this.configPath,"utf-8");o(n,e,{encoding:"utf-8",mode:384}),this.rotateBackups(s,10,30)}catch(e){u.logError("ConfigManager.backupConfig",e)}}rotateBackups(t,i,o){try{const s=Date.now()-24*o*60*60*1e3,a=n(t).filter(e=>e.startsWith("config-")&&e.endsWith(".yaml")).map(e=>({name:e,path:f(t,e),mtime:c(f(t,e)).mtime})).sort((e,t)=>t.mtime.getTime()-e.mtime.getTime()),l=new Set;a.slice(i).forEach(e=>l.add(e.path)),a.forEach(e=>{e.mtime.getTime()<s&&l.add(e.path)});for(const t of l){let i=!1;try{r(t),i=!0}catch(e){u.logError("ConfigManager.rotateBackups",e)}if(i){const i=this.getSecretsBackupPath(t);if(e(i))try{r(i)}catch(e){u.logError("ConfigManager.rotateBackups.secrets",e)}}}}catch(e){u.logError("ConfigManager.rotateBackups",e)}}saveConfig(e){try{this.ensureConfigDir(),this.backupConfig();const t=this.syncLegacyFields(this.migrateOldConfig(e||this.config)),i=this.prepareForPersistence(t),o=m.dump(i);this.atomicWrite(this.configPath,o),this.config=t}catch(e){throw u.error("Failed to save config:",e),u.logError("ConfigManager.saveConfig",e),e}}prepareForPersistence(e){const t={...e,credential_store:k};return delete t.api_key,delete t.base_url,delete t.models,t}mergeLegacySecrets(e,t){const i={...e.profiles||{}},o=new Set([...Object.keys(t.api_keys||{}),...Object.keys(t.tool_api_keys||{})]);for(const e of o){const o=i[e]||{},s=o.api_key||t.api_keys?.[e],r={...t.tool_api_keys?.[e]||{},...o.tool_api_keys||{}};i[e]={...o,...s?{api_key:s}:{},...Object.keys(r).length>0?{tool_api_keys:r}:{}}}return this.syncLegacyFields({...e,profiles:i})}getConfig(){return this.syncLegacyFields({...this.config})}updateConfig(e){this.saveConfig({...this.config,...e})}isFirstRun(){return!e(this.configPath)}getLang(){return this.config.lang||"en_US"}isLangChosen(){return!0===this.config.lang_chosen}setLang(e){this.updateConfig({lang:e,lang_chosen:!0})}getActiveProfileName(){return this.config.active_profile||"default"}listProfiles(){return Object.keys(this.config.profiles||{}).sort()}getProfile(e=this.getActiveProfileName()){const t=this.config.profiles?.[e]||{};return{...t.api_key?{api_key:t.api_key}:{},...t.tool_api_keys&&Object.keys(t.tool_api_keys).length>0?{tool_api_keys:this.normalizeStringRecord(t.tool_api_keys)}:{},...t.base_url?{base_url:t.base_url}:{},models:t.models?this.normalizeModels(t.models):DEFAULT_MODELS,...t.tool_models&&Object.keys(t.tool_models).length>0?{tool_models:this.normalizeToolModels(t.tool_models)}:{},...t.tool_settings&&Object.keys(t.tool_settings).length>0?{tool_settings:this.normalizeToolSettings(t.tool_settings)}:{}}}createProfile(e,t=this.getActiveProfileName()){const i=this.getProfile(t),o={...this.config.profiles||{},[e]:{...i.api_key?{api_key:i.api_key}:{},...i.base_url?{base_url:i.base_url}:{},models:this.normalizeModels(i.models)}};this.saveConfig({...this.config,profiles:o})}setActiveProfile(e){if(!this.config.profiles?.[e])throw new Error(`Profile not found: ${e}`);this.saveConfig({...this.config,active_profile:e})}updateProfile(e,t){const i=this.config.profiles?.[e]||{},o=t.models?this.normalizeModels({...i.models,...t.models}):i.models?this.normalizeModels(i.models):void 0,s={...i,...void 0!==t.api_key?{api_key:t.api_key}:{},...void 0!==t.base_url?{base_url:t.base_url}:{},...o?{models:o}:{}},r={...this.config.profiles||{},[e]:s};this.saveConfig({...this.config,profiles:r})}deleteProfile(e){const t={...this.config.profiles||{}};if(!t[e])return;const i=Object.keys(t).filter(t=>t!==e);if(0===i.length)throw new Error("At least one profile must remain");delete t[e];const o=this.getActiveProfileName()===e?i[0]:this.getActiveProfileName();this.saveConfig({...this.config,profiles:t,active_profile:o})}getApiKey(){return this.config.profiles?.[this.getActiveProfileName()]?.api_key}setApiKey(e){this.updateProfile(this.getActiveProfileName(),{api_key:e})}revokeApiKey(){const e=this.config.profiles?.[this.getActiveProfileName()]||{},{api_key:t,tool_api_keys:i,...o}=e,s={...this.config.profiles||{},[this.getActiveProfileName()]:o};this.saveConfig({...this.config,profiles:s})}getApiKeyFingerprint(){const e=this.getApiKey();return e?p("sha256").update(e).digest("hex").slice(0,12):""}getApiKeyForTool(e){const t=this.config.profiles?.[this.getActiveProfileName()];return t?.tool_api_keys?.[e]||t?.api_key||void 0}setToolApiKey(e,t){const i=this.getActiveProfileName(),o=this.config.profiles?.[i]||{},s={...this.config.profiles||{},[i]:{...o,tool_api_keys:{...o.tool_api_keys||{},[e]:t}}};this.saveConfig({...this.config,profiles:s})}clearToolApiKey(e){const t=this.getActiveProfileName(),i=this.config.profiles?.[t]||{},{[e]:o,...s}=i.tool_api_keys||{},r={...i};Object.keys(s).length>0?r.tool_api_keys=s:delete r.tool_api_keys,this.saveConfig({...this.config,profiles:{...this.config.profiles||{},[t]:r}})}listToolApiKeys(){return Object.keys(this.config.profiles?.[this.getActiveProfileName()]?.tool_api_keys||{}).sort()}getToolApiKeyFingerprint(e){const t=this.config.profiles?.[this.getActiveProfileName()]?.tool_api_keys?.[e];return t?p("sha256").update(t).digest("hex").slice(0,12):""}getModels(){const e=this.config.profiles?.[this.getActiveProfileName()]?.models;return e?this.normalizeModels(e):DEFAULT_MODELS}setModels(e){this.updateProfile(this.getActiveProfileName(),{models:this.normalizeModels(e)})}updateModels(e){this.setModels(this.normalizeModels({...this.getModels(),...e}))}getModelsForTool(e){const t=this.getModels(),i=this.config.profiles?.[this.getActiveProfileName()]?.tool_models?.[e];return i?this.normalizeModels({...t,...i}):t}setToolModel(e,t){const i=this.getActiveProfileName(),o=this.config.profiles?.[i]||{},s={...o.tool_models||{}};t&&0!==Object.keys(t).length?s[e]=t:delete s[e];const r={...o};Object.keys(s).length>0?r.tool_models=s:delete r.tool_models;const n={...this.config.profiles||{},[i]:r};this.saveConfig({...this.config,profiles:n})}getToolSettings(e){const t=this.config.profiles?.[this.getActiveProfileName()]?.tool_settings?.[e];return t?{...t}:{}}getToolSetting(e,t){return this.config.profiles?.[this.getActiveProfileName()]?.tool_settings?.[e]?.[t]}setToolSetting(e,t,i){if(this.getToolSetting(e,t)===i)return;const o=this.getActiveProfileName(),s=this.config.profiles?.[o]||{},r={...s.tool_settings||{}};r[e]={...r[e]||{},[t]:i};const n={...this.config.profiles||{},[o]:{...s,tool_settings:r}};this.saveConfig({...this.config,profiles:n})}getCustomPresets(){return{...this.config.presets||{}}}saveCustomPreset(e,t){const i={...this.config.presets||{},[e]:{models:this.normalizeModels(t),updated_at:(new Date).toISOString()}};this.saveConfig({...this.config,presets:i})}deleteCustomPreset(e){const t={...this.config.presets||{}};t[e]&&(delete t[e],this.saveConfig({...this.config,presets:t}))}listBackups(){const t=this.getBackupDir();return e(t)?n(t).filter(e=>e.startsWith("config-")&&e.endsWith(".yaml")).map(e=>{const i=f(t,e);return{name:e,path:i,mtimeMs:c(i).mtimeMs}}).sort((e,t)=>t.mtimeMs-e.mtimeMs):[]}createBackup(s){this.ensureConfigDir();const r=this.getBackupDir();e(r)||t(r,{recursive:!0});const n=(new Date).toISOString().replace(/[:.]/g,"-"),a=this.sanitizeBackupLabel(s),c=f(r,a?`config-${n}-${a}.yaml`:`config-${n}.yaml`),l=e(this.configPath)?i(this.configPath,"utf-8"):m.dump(this.prepareForPersistence(this.getConfig()));return o(c,l,{encoding:"utf-8",mode:384}),this.rotateBackups(r,10,30),c}restoreBackup(t){if(!t||"."===t||".."===t||/[/\\]|\.\./.test(t))throw new Error(`Invalid backup name: ${t}`);const o=this.getBackupDir(),s=f(o,t);if(!e(s))throw new Error(`Backup not found: ${t}`);const r=i(s,"utf-8"),n=m.load(r),a=this.getSecretsBackupPath(s);let c=this.migrateOldConfig(n||this.createDefaultConfig());if(n?.credential_store!==k&&e(a))try{c=this.mergeLegacySecrets(c,_(a))}catch(e){throw new Error(`Legacy backup credentials could not be decrypted: ${a}`,{cause:e})}this.saveConfig(c)}getConfigDir(){return this.configDir}getBaseUrl(){return this.runtimeBaseUrl||this.config.profiles?.[this.getActiveProfileName()]?.base_url||DEFAULT_BASE_URL}hasRuntimeBaseUrl(){return Boolean(this.runtimeBaseUrl)}setBaseUrl(e,t=!1){this.runtimeBaseUrl=e,t&&this.updateProfile(this.getActiveProfileName(),{base_url:e})}resetBaseUrl(){this.runtimeBaseUrl=void 0;const e=this.config.profiles?.[this.getActiveProfileName()]||{},{base_url:t,...i}=e,o={...this.config.profiles||{},[this.getActiveProfileName()]:i};this.saveConfig({...this.config,profiles:o})}}export const configManager=ConfigManager.getInstance();
@@ -1,10 +1,13 @@
1
1
  import { type GatewayAddress } from './model-registry.js';
2
2
  export type { GatewayAddress };
3
- export type ProbeError = 'certificate' | 'timeout' | 'network' | 'not_gateway';
3
+ export type ProbeError = 'certificate' | 'timeout' | 'network' | 'not_gateway' | 'key_rejected';
4
4
  export interface ProbeResult {
5
5
  url: string;
6
6
  ok: boolean;
7
+ /** Median over the samples that were taken. */
7
8
  ms: number;
9
+ /** How many models the key can list here; only when probed with a key. */
10
+ models?: number;
8
11
  error?: ProbeError;
9
12
  detail?: string;
10
13
  }
@@ -30,12 +33,18 @@ export declare function rememberPublishedAddresses(statusData: unknown, source:
30
33
  * gateway's order. A built-in address takes the gateway's label when it has one.
31
34
  */
32
35
  export declare function gatewayAddresses(): GatewayAddress[];
33
- /**
34
- * One unauthenticated `GET /api/status`: reachable, valid certificate, and the
35
- * answer looks like this gateway (a JSON `data` object). A successful answer
36
- * also refreshes the cached address list.
37
- */
38
- export declare function probeGatewayAddress(url: string, options?: {
36
+ export interface ProbeOptions {
37
+ /** With a key the probe lists the models (`GET /v1/models`): the whole relay path works, not just the front page. */
38
+ apiKey?: string;
39
+ /** Requests per address; the reported time is their median. Default 1. */
40
+ samples?: number;
39
41
  timeoutMs?: number;
40
42
  fetchFn?: typeof fetch;
41
- }): Promise<ProbeResult>;
43
+ }
44
+ /**
45
+ * Whether the gateway really answers on `url`: with a key, the key's model list
46
+ * (`GET /v1/models`); without one, an unauthenticated `GET /api/status` that has
47
+ * to come back as this gateway's JSON (that answer also refreshes the cached
48
+ * address list). Stops at the first failed sample.
49
+ */
50
+ export declare function probeGatewayAddress(url: string, options?: ProbeOptions): Promise<ProbeResult>;
@@ -1 +1 @@
1
- import{existsSync as r,mkdirSync as e,readFileSync as t,writeFileSync as o}from"fs";import{join as s}from"path";import{configManager as n}from"./config.js";import{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(!i().some(r=>normalizeBaseUrl(r.url)===t))return;const s=r;if(!1===s.api_info_enabled)return;const l=parsePublishedAddresses(s.api_info);if(0!==l.length)try{e(n.getConfigDir(),{recursive:!0}),o(u(),JSON.stringify({addresses:l},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[]}}(),o=[];for(const r of i()){const t=normalizeBaseUrl(r.url);if(!t||o.some(r=>r.url===t))continue;const s=e.find(r=>r.url===t)?.label||r.label;o.push({url:t,label:s})}for(const r of e)o.some(e=>e.url===r.url)||o.push(r);return o}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)}}
1
+ import{existsSync as r,mkdirSync as t,readFileSync as e,writeFileSync as o}from"fs";import{join as s}from"path";import{configManager as n}from"./config.js";import{getBuiltinGatewayAddresses as a}from"./model-registry.js";import{logger as i}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 t,e=r.trim();if(!e)return null;/^[a-z][a-z0-9+.-]*:\/\//i.test(e)||(e=`https://${e}`);try{t=new URL(e)}catch{return null}if("https:"!==t.protocol&&"http:"!==t.protocol)return null;const o=t.pathname.replace(/\/+$/,"").replace(/\/v1$/i,"");return`${t.protocol}//${t.host}${o}`}export function parsePublishedAddresses(r){if(!Array.isArray(r))return[];const t=new Set,e=[];for(const o of r){if(!o||"object"!=typeof o)continue;const{url:r,route:s,description:n}=o,a="string"==typeof r?normalizeBaseUrl(r):null;if(!a?.startsWith("https://")||t.has(a))continue;t.add(a);const i="string"==typeof s?s.trim():"",l="string"==typeof n?n.trim():"";e.push({url:a,label:i&&l?`${i}(${l})`:i||l})}return e}export function rememberPublishedAddresses(r,e){if(!r||"object"!=typeof r)return;if(!a().some(r=>normalizeBaseUrl(r.url)===e))return;const s=r;if(!1===s.api_info_enabled)return;const l=parsePublishedAddresses(s.api_info);if(0!==l.length)try{t(n.getConfigDir(),{recursive:!0}),o(u(),JSON.stringify({addresses:l},null,2)+"\n")}catch(r){i.logError("rememberPublishedAddresses",r)}}export function gatewayAddresses(){const t=function(){const t=u();if(!r(t))return[];try{const r=JSON.parse(e(t,"utf-8"));return(Array.isArray(r.addresses)?r.addresses:[]).flatMap(r=>{const t="string"==typeof r?.url?normalizeBaseUrl(r.url):null;return t?[{url:t,label:"string"==typeof r.label?r.label:""}]:[]})}catch{return[]}}(),o=[];for(const r of a()){const e=normalizeBaseUrl(r.url);if(!e||o.some(r=>r.url===e))continue;const s=t.find(r=>r.url===e)?.label||r.label;o.push({url:e,label:s})}for(const r of t)o.some(t=>t.url===r.url)||o.push(r);return o}function c(r){const t=r?.cause,e="string"==typeof t?.code?t.code:"",o="string"==typeof t?.message?t.message:r instanceof Error?r.message:String(r);return r instanceof Error&&"AbortError"===r.name?{error:"timeout",detail:o}:l.has(e)||/certificate/i.test(o)?{error:"certificate",detail:e||o}:{error:"network",detail:e||o}}export async function probeGatewayAddress(r,t={}){const e=Math.max(1,t.samples??1),o=[];let s;for(let n=0;n<e;n+=1){const e=await f(r,t);if(!e.ok)return e;o.push(e.ms),s=e.models}return o.sort((r,t)=>r-t),{url:r,ok:!0,ms:o[Math.floor(o.length/2)],...void 0===s?{}:{models:s}}}async function f(r,t){const{apiKey:e,timeoutMs:o=8e3,fetchFn:s=fetch}=t,n=new AbortController,a=setTimeout(()=>n.abort(),o),i=Date.now();try{const t=await s(e?`${r}/v1/models`:`${r}/api/status`,{method:"GET",headers:e?{Authorization:`Bearer ${e}`}:void 0,signal:n.signal});let o;try{o=(await t.json())?.data}catch{o=void 0}const a=Date.now()-i;return!e||401!==t.status&&403!==t.status?e?t.ok&&Array.isArray(o)?{url:r,ok:!0,ms:a,models:o.length}:{url:r,ok:!1,ms:a,error:"not_gateway",detail:`HTTP ${t.status}`}:t.ok&&o&&"object"==typeof o?(rememberPublishedAddresses(o,r),{url:r,ok:!0,ms:a}):{url:r,ok:!1,ms:a,error:"not_gateway",detail:`HTTP ${t.status}`}:{url:r,ok:!1,ms:a,error:"key_rejected",detail:`HTTP ${t.status}`}}catch(t){return{url:r,ok:!1,ms:Date.now()-i,...c(t)}}finally{clearTimeout(a)}}
@@ -1 +1 @@
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();
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,ensureReachableGatewayAddress as z,fastestIndex as v,probeAll as k,probeSummary as x,reasonText as b,savedApiKey as C,switchGatewayAddress as S}from"../commands/base.js";import{readFileSync as M}from"fs";import{dirname as T,join as A}from"path";import{fileURLToPath as K}from"url";const N=T(K(import.meta.url)),B=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(B.bold("\n"+u("╔","╗","═",this.BOX_WIDTH))),console.log(B.bold(g(e,"║","║",this.BOX_WIDTH,"center"))),console.log(B.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(M(A(N,"..","..","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(B.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(B.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 z(),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 k(n,{apiKey:C(),samples:3});r.stop();const l=v(s),c=n.map((e,a)=>{const o=e.url===i?t.cyan(` ← ${p.t("base.current_mark")}`):"";return{name:`${e.url} ${t.gray(e.label)} ${x(s[a],a===l)}${o}`,value:e.url}});c.push(new e.Separator,{name:"<- "+p.t("wizard.nav_return"),value:"__back__"});const{url:d}=await this.promptWithHints([{type:"list",name:"url",message:p.t("wizard.base_select"),choices:c,default:i}]);if("__back__"===d)return;const m=s[n.findIndex(e=>e.url===d)];m.ok?await S(d):console.log(t.red(p.t("base.unreachable",{url:d,reason:b(m)}))),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(B("── "+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();
@@ -476,7 +476,7 @@
476
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
477
  "switched": "Switched to {{url}}",
478
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",
479
+ "no_tools": "No tool on this computer was configured by vibecli, so only the address was saved and nothing else changed. In tools you set up by hand, replace www.vibeapi.cn with {{url}}; or run vibecli setup --key <your key> to let vibecli configure them (it rewrites their VibeAPI settings, backing them up first)",
480
480
  "none_reachable": "None of the gateway addresses can be reached; check your network or proxy",
481
481
  "bad_index": "There is no address {{index}} ({{count}} in total); run vibecli base to see the list",
482
482
  "bad_url": "{{value}} is not a valid address, e.g. https://www.vibeapi.si",
@@ -486,6 +486,13 @@
486
486
  "error_network": "connection failed",
487
487
  "error_not_gateway": "the answer is not from VibeAPI",
488
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:"
489
+ "tools_moved": "These tools now use it:",
490
+ "setup_fallback": "{{from}} cannot be reached: {{reason}}. Using {{url}} from now on; run vibecli base default to go back to the main site once the network is fine",
491
+ "models_count": "{{count}} models",
492
+ "fastest": "fastest",
493
+ "error_key_rejected": "key rejected (invalid or expired)",
494
+ "key_rejected_everywhere": "Every address rejected this key; it may be invalid or expired, check it in the console",
495
+ "probe_with_key": "Listing models with your saved key on each address, median of 3",
496
+ "probe_without_key": "No key saved yet, so only checking that the gateway answers; after vibecli setup --key <your key> the model list is fetched instead"
490
497
  }
491
498
  }
@@ -476,7 +476,7 @@
476
476
  "usage_hint": "切换:vibecli base <序号或地址>;自动选第一个能用的:vibecli base auto;回到主站:vibecli base default",
477
477
  "switched": "已切换到 {{url}}",
478
478
  "kept": "当前地址 {{url}} 可用,已保存",
479
- "no_tools": "还没有用 vibecli 配置过的工具,运行 vibecli setup --key <你的 Key> 时会直接用这个地址",
479
+ "no_tools": "这台电脑上没有用 vibecli 配置过的工具,所以只保存了地址,其它配置都没动。自己手动配置的工具,请把里面的 www.vibeapi.cn 换成 {{url}};也可以运行 vibecli setup --key <你的 Key>,交给 vibecli 统一配置(会改写这些工具里的 VibeAPI 配置,改前自动备份)",
480
480
  "none_reachable": "所有接入地址都连不上,请检查网络或代理",
481
481
  "bad_index": "没有第 {{index}} 个地址(共 {{count}} 个),先运行 vibecli base 查看列表",
482
482
  "bad_url": "{{value}} 不是有效的地址,示例:https://www.vibeapi.si",
@@ -486,6 +486,13 @@
486
486
  "error_network": "网络连接失败",
487
487
  "error_not_gateway": "返回的不是 VibeAPI",
488
488
  "network_hint": "连不上当前接入地址时,运行 vibecli base 查看其他地址,vibecli base auto 自动换到能用的那个",
489
- "tools_moved": "以下工具已改用这个地址:"
489
+ "tools_moved": "以下工具已改用这个地址:",
490
+ "setup_fallback": "{{from}} 连不上:{{reason}}。已改用 {{url}},以后也用这个地址;网络恢复后可运行 vibecli base default 换回主站",
491
+ "models_count": "{{count}} 个模型",
492
+ "fastest": "最快",
493
+ "error_key_rejected": "令牌被拒绝(无效或已过期)",
494
+ "key_rejected_everywhere": "所有地址都拒绝了这把令牌,令牌可能无效或已过期,请到控制台检查",
495
+ "probe_with_key": "用你保存的令牌在每个地址拉一次模型列表,测 3 次取中位数",
496
+ "probe_without_key": "还没有保存令牌,只测网关能否打开;运行 vibecli setup --key <你的 Key> 后会改为实际拉模型列表"
490
497
  }
491
498
  }
package/dist/models.json CHANGED
@@ -302,6 +302,10 @@
302
302
  {
303
303
  "url": "https://token.nfeyre.top",
304
304
  "label": "全球加速"
305
+ },
306
+ {
307
+ "url": "https://us13-www.vibeapi.cn",
308
+ "label": "US13 线路"
305
309
  }
306
310
  ]
307
311
  }
package/models.json CHANGED
@@ -302,6 +302,10 @@
302
302
  {
303
303
  "url": "https://token.nfeyre.top",
304
304
  "label": "全球加速"
305
+ },
306
+ {
307
+ "url": "https://us13-www.vibeapi.cn",
308
+ "label": "US13 线路"
305
309
  }
306
310
  ]
307
311
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeapi/cli",
3
- "version": "0.0.72",
3
+ "version": "0.0.74",
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",