@vibeapi/cli 0.0.148 → 0.0.150

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +22 -17
  2. package/components.json +157 -0
  3. package/dist/commands/components.d.ts +26 -0
  4. package/dist/commands/components.js +1 -0
  5. package/dist/commands/config.js +1 -1
  6. package/dist/commands/setup.js +1 -1
  7. package/dist/commands/upgrade.d.ts +4 -2
  8. package/dist/commands/upgrade.js +1 -1
  9. package/dist/components.json +157 -0
  10. package/dist/lib/app-update.d.ts +20 -0
  11. package/dist/lib/app-update.js +1 -1
  12. package/dist/lib/brand.js +1 -1
  13. package/dist/lib/claude-code-manager.d.ts +4 -0
  14. package/dist/lib/claude-code-manager.js +1 -1
  15. package/dist/lib/command.d.ts +0 -5
  16. package/dist/lib/command.js +1 -1
  17. package/dist/lib/components.d.ts +204 -0
  18. package/dist/lib/components.js +1 -0
  19. package/dist/lib/config.d.ts +2 -0
  20. package/dist/lib/config.js +1 -1
  21. package/dist/lib/dry-run.js +1 -1
  22. package/dist/lib/env-cleanup.d.ts +32 -28
  23. package/dist/lib/env-cleanup.js +1 -1
  24. package/dist/lib/gateway-addresses.d.ts +4 -0
  25. package/dist/lib/gateway-addresses.js +1 -1
  26. package/dist/lib/model-registry.d.ts +2 -0
  27. package/dist/lib/model-registry.js +1 -1
  28. package/dist/lib/program.js +1 -1
  29. package/dist/lib/runtime-options.d.ts +5 -0
  30. package/dist/lib/runtime-options.js +1 -1
  31. package/dist/lib/tool-manager.d.ts +3 -0
  32. package/dist/lib/tool-manager.js +1 -1
  33. package/dist/lib/tool-upgrade.d.ts +2 -0
  34. package/dist/lib/tool-upgrade.js +1 -1
  35. package/dist/lib/tools/chatgpt-desktop.d.ts +14 -1
  36. package/dist/lib/tools/chatgpt-desktop.js +1 -1
  37. package/dist/lib/tools/claude-code.js +1 -1
  38. package/dist/lib/tools/claude-desktop.d.ts +1 -1
  39. package/dist/lib/tools/claude-desktop.js +1 -1
  40. package/dist/lib/tools/codex.js +1 -1
  41. package/dist/lib/tools/desktop-process.d.ts +4 -1
  42. package/dist/lib/tools/desktop-process.js +1 -1
  43. package/dist/lib/tools/tool-installs.d.ts +8 -0
  44. package/dist/lib/tools/tool-installs.js +1 -1
  45. package/dist/lib/tools/types.d.ts +26 -9
  46. package/dist/lib/wizard.js +1 -1
  47. package/dist/lib/write-plan.d.ts +2 -0
  48. package/dist/lib/write-plan.js +1 -1
  49. package/dist/lib/written-values.d.ts +21 -0
  50. package/dist/lib/written-values.js +1 -0
  51. package/dist/locales/en_US.json +140 -5
  52. package/dist/locales/zh_CN.json +140 -5
  53. package/package.json +2 -1
@@ -42,15 +42,35 @@ export declare function appTools(): string[];
42
42
  export declare function getApp(name: string): AppEntry | undefined;
43
43
  /** Apps that go with these configured tools (Claude Desktop with itself, ChatGPT with codex). */
44
44
  export declare function appsForTools(tools: string[]): string[];
45
+ /**
46
+ * The mirror's latest version only (cached an hour, failures ten minutes; `refresh` reads it again), no local
47
+ * probing: callers that check several apps fetch first, then probe (on Windows the PowerShell probes are
48
+ * synchronous and would hold up the event loop past the fetch timeouts).
49
+ */
50
+ export declare function appLatest(tool: string, options?: {
51
+ refresh?: boolean;
52
+ }): Promise<{
53
+ version: string | null;
54
+ error?: string;
55
+ }>;
45
56
  /** Installed / latest versions; the latest is read from the mirror at most once an hour unless `refresh`. */
46
57
  export declare function appStatus(tool: string, options?: {
47
58
  refresh?: boolean;
48
59
  }): Promise<AppStatus>;
60
+ /** Local state with a latest version already fetched (`appLatest`). */
61
+ export declare function appStatusWith(tool: string, latest: {
62
+ version: string | null;
63
+ error?: string;
64
+ }): AppStatus;
49
65
  /** From the cache only (menus): no network. */
50
66
  export declare function cachedAppStatus(tool: string): AppStatus;
51
67
  /** "current x, y available" / "x (latest)" / "not installed"; empty when nothing is known. */
52
68
  export declare function appStatusText(status: AppStatus): string;
53
69
  export declare function appUpgradeCommand(tool: string): string;
70
+ export declare function setQuitWaitForTests(wait: {
71
+ totalMs: number;
72
+ stepMs: number;
73
+ }): void;
54
74
  export interface UpgradeAppOptions {
55
75
  /** Install the app when it isn't installed (`vibecli upgrade`, setup after consent). */
56
76
  install?: boolean;
@@ -1 +1 @@
1
- import t from"chalk";import e from"inquirer";import{existsSync as r,mkdirSync as n,readFileSync as a,writeFileSync as o}from"fs";import{join as l}from"path";import{CLI_NAME as p}from"./brand.js";import{configManager as i}from"./config.js";import{i18n as s}from"./i18n.js";import{canPrompt as u,hasExplicitYes as d,isJsonMode as c}from"./runtime-options.js";import{track as f}from"./telemetry.js";import{compareVersions as m}from"./model-registry.js";import{listAdapters as g}from"./tools/registry.js";import{logger as _}from"../utils/logger.js";function y(){return l(i.getConfigDir(),"app-releases.json")}function w(){try{if(!r(y()))return{};const t=JSON.parse(a(y(),"utf-8"));return t&&"object"==typeof t?t:{}}catch{return{}}}function h(t,e){try{n(i.getConfigDir(),{recursive:!0}),o(y(),JSON.stringify({...w(),[t]:e},null,2))}catch(t){_.logError("app-update.writeCache",t)}}function k(){return g().flatMap(t=>[...t.appInstaller?[{name:t.name,displayName:t.displayName,appInstaller:t.appInstaller,installedVersion:t.installedVersion,runningApp:t.runningApp,tool:t.name}]:[],...(t.companionApps??[]).map(e=>({...e,tool:t.name}))])}export function appTools(){return k().map(t=>t.name)}export function getApp(t){return k().find(e=>e.name===t)}export function appsForTools(t){return k().filter(e=>t.includes(e.tool)).map(t=>t.name)}function v(t){const e=getApp(t);if(!e)throw new Error(`no app installer for ${t}`);return e}function b(t,e,r){try{return e()}catch(e){return _.logError(`app-update.${t}`,e),r}}function C(t){const e=v(t),r=e.appInstaller,n=b("installedPath",()=>null!==r.installedPath(),!1);return{tool:t,name:e.displayName,installed:n,current:n?b("installedVersion",()=>e.installedVersion?.()??null,null):null,running:!!n&&b("isRunning",()=>e.runningApp?.isRunning()??!1,!1),blocker:b("blocker",()=>r.blocker(),"unsupported_platform")}}function U(t,e,r){const n=Boolean(t.installed&&t.current&&e&&m(t.current,e)<0);return{...t,latest:e,outdated:n,...r?{error:r}:{}}}export async function appStatus(t,e={}){const r=C(t);if(r.blocker&&"classic_install"!==r.blocker)return U(r,null);const n=w()[t],a=n&&Date.now()-n.checked_at<(n.version?36e5:6e5);if(n&&a&&!e.refresh)return U(r,n.version??null,n.error);try{const e=await v(t).appInstaller.latestVersion();return h(t,{version:e,checked_at:Date.now()}),U(r,e)}catch(e){const a=e instanceof Error?e.message:String(e);return _.logError(`app-update.latest(${t})`,e),h(t,{error:a,checked_at:Date.now()}),U(r,n?.version??null,a)}}export function cachedAppStatus(t){const e=C(t);return U(e,e.blocker&&"classic_install"!==e.blocker?null:w()[t]?.version??null)}export function appStatusText(t){return t.installed?t.outdated?s.t("app_update.outdated",{current:t.current,latest:t.latest}):t.current&&t.latest?s.t("app_update.up_to_date",{current:t.current}):t.current??"":t.latest?s.t("app_update.not_installed",{latest:t.latest}):""}export function appUpgradeCommand(t){return`${p} upgrade ${t}`}function A(t){c()?process.stderr.write(t+"\n"):console.log(t)}async function j(t,r=!0){const{ok:n}=await e.prompt([{type:"confirm",name:"ok",message:t,default:r}]);return n}export async function upgradeApp(e,r={}){const n=Date.now(),a=await appStatus(e,{refresh:r.refresh??!0}),o=a.name,l=(t,r={})=>{const o={...a,result:t,upgraded:"upgraded"===t||"installed"===t,version_after:null,...r};return["up_to_date","unavailable","not_installed","offered"].includes(t)||f(`app-upgrade:${e}`,"failed"===t?"fail":"ok",{duration_ms:Date.now()-n,detail:{result:t,current:a.current,latest:a.latest,...r.reason?{reason:r.reason}:{}}}),o};if(a.blocker)return!r.quiet&&a.installed&&a.outdated&&A(t.yellow(" ! "+s.t("app_update.outdated",{current:a.current,latest:a.latest}))),r.quiet||A(t.yellow(" ! "+s.t(`app_update.blocked_${a.blocker}`,{tool:o,url:v(e).appInstaller.downloadPage,command:appUpgradeCommand(e)}))),l("unavailable",{reason:a.blocker});if(!a.latest)return r.quiet||A(t.yellow(" ! "+s.t("app_update.latest_unknown",{tool:o,reason:a.error??""}))),l("unavailable",{reason:a.error});if(!a.installed&&!r.install)return l("not_installed");const p=Boolean(r.force&&a.installed&&!a.outdated);if(a.installed&&!a.outdated&&!p)return r.quiet||(A(t.green(" √ "+s.t("app_update.already_latest",{tool:o,current:a.current??a.latest}))),A(t.gray(" "+s.t("app_update.force_hint",{command:appUpgradeCommand(e)})))),l("up_to_date");if(p?A(t.yellow(" ! "+s.t("app_update.reinstalling",{tool:o,latest:a.latest,current:a.current??"?"}))):!a.installed||r.askBeforeUpgrade&&!a.running||A(t.yellow(` ! ${o} `+appStatusText(a))),a.installed&&r.askBeforeUpgrade&&!a.running){if(!u()||d())return A(t.gray(" "+s.t("app_update.upgrade_hint",{tool:o,current:a.current??"?",latest:a.latest,command:appUpgradeCommand(e)}))),l("offered");if(!await j(s.t("app_update.upgrade_offer",{tool:o,current:a.current??"?",latest:a.latest})))return A(t.gray(" "+s.t("app_update.declined_hint",{command:appUpgradeCommand(e)}))),l("declined")}let i=!1;if(a.running){if(!u()||d())return A(t.yellow(" ! "+s.t("app_update.running_hint",{tool:o,command:appUpgradeCommand(e)}))),l("left_running");if(!await j(s.t("app_update.offer_quit",{tool:o,latest:a.latest})))return A(t.gray(" "+s.t("app_update.declined_hint",{command:appUpgradeCommand(e)}))),l("declined");const r=v(e).runningApp;if(!r?.quit?.())return A(t.yellow(" ! "+s.t("app_update.quit_failed",{tool:o,command:appUpgradeCommand(e)}))),l("failed",{reason:"quit_failed"});i=!0}try{const{version:r}=await async function(e){const r=v(e).appInstaller,n=!c()&&Boolean(process.stdout.isTTY);let a=-1;try{return await r.install({downloading:n=>A(t.gray(" "+s.t("app_update.downloading",{tool:v(e).displayName,version:n,size:String(r.sizeMb())}))),percent:e=>{!n||e-a<5||(a=e,process.stdout.write(t.gray(`\r ${e}%`)),e>=100&&process.stdout.write("\n"))},verifying:()=>{n&&a>=0&&a<100&&process.stdout.write("\n"),a=100,A(t.gray(" "+s.t("app_update.verifying",{vendor:r.vendor})))},installing:()=>A(t.gray(" "+s.t("app_update.installing")))})}finally{n&&a>=0&&a<100&&process.stdout.write("\n")}}(e);return A(t.green(" √ "+(p?s.t("app_update.reinstalled",{tool:o,version:r}):a.installed?s.t("app_update.upgraded",{tool:o,from:a.current??"?",to:r}):s.t("app_update.installed",{tool:o,version:r})))),i&&!v(e).runningApp?.launch?.()&&A(t.yellow(" ! "+s.t("setup.reopen_hint",{tool:o}))),l(a.installed?"upgraded":"installed",{version_after:r,current:a.current})}catch(r){const n=r instanceof Error?r.message:String(r);return _.logError(`app-update.install(${e})`,r),A(t.yellow(" ! "+(a.installed?s.t("app_update.upgrade_failed",{tool:o,reason:n,current:a.current??"?",command:appUpgradeCommand(e)}):s.t("app_update.install_failed",{tool:o,reason:n,command:appUpgradeCommand(e)})))),i&&v(e).runningApp?.launch?.(),l("failed",{reason:n})}}
1
+ import t from"chalk";import e from"inquirer";import{existsSync as r,mkdirSync as n,readFileSync as a,writeFileSync as o}from"fs";import{join as l}from"path";import{CLI_NAME as p}from"./brand.js";import{configManager as i}from"./config.js";import{i18n as s}from"./i18n.js";import{canPrompt as u,hasExplicitYes as d,isJsonMode as c}from"./runtime-options.js";import{track as f}from"./telemetry.js";import{compareVersions as m}from"./model-registry.js";import{listAdapters as g}from"./tools/registry.js";import{logger as _}from"../utils/logger.js";function y(){return l(i.getConfigDir(),"app-releases.json")}function w(){try{if(!r(y()))return{};const t=JSON.parse(a(y(),"utf-8"));return t&&"object"==typeof t?t:{}}catch{return{}}}function h(t,e){try{n(i.getConfigDir(),{recursive:!0}),o(y(),JSON.stringify({...w(),[t]:e},null,2))}catch(t){_.logError("app-update.writeCache",t)}}function v(){return g().flatMap(t=>[...t.appInstaller?[{name:t.name,displayName:t.displayName,appInstaller:t.appInstaller,installedVersion:t.installedVersion,runningApp:t.runningApp,tool:t.name}]:[],...(t.companionApps??[]).map(e=>({...e,tool:t.name}))])}export function appTools(){return v().map(t=>t.name)}export function getApp(t){return v().find(e=>e.name===t)}export function appsForTools(t){return v().filter(e=>t.includes(e.tool)).map(t=>t.name)}function k(t){const e=getApp(t);if(!e)throw new Error(`no app installer for ${t}`);return e}function b(t,e,r){try{return e()}catch(e){return _.logError(`app-update.${t}`,e),r}}function q(t){const e=k(t),r=e.appInstaller,n=b("installedPath",()=>null!==r.installedPath(),!1);return{tool:t,name:e.displayName,installed:n,current:n?b("installedVersion",()=>e.installedVersion?.()??null,null):null,running:!!n&&b("isRunning",()=>e.runningApp?.isRunning()??!1,!1),blocker:b("blocker",()=>r.blocker(),"unsupported_platform")}}function x(t,e,r){const n=Boolean(t.installed&&t.current&&e&&m(t.current,e)<0);return{...t,latest:e,outdated:n,...r?{error:r}:{}}}export async function appLatest(t,e={}){const r=w()[t],n=r&&Date.now()-r.checked_at<(r.version?36e5:6e5);if(r&&n&&!e.refresh)return{version:r.version??null,...r.error?{error:r.error}:{}};try{const e=await k(t).appInstaller.latestVersion();return h(t,{version:e,checked_at:Date.now()}),{version:e}}catch(e){const n=e instanceof Error?e.message:String(e);return _.logError(`app-update.latest(${t})`,e),h(t,{error:n,checked_at:Date.now()}),{version:r?.version??null,error:n}}}export async function appStatus(t,e={}){const r=q(t);if(r.blocker&&"classic_install"!==r.blocker)return x(r,null);const{version:n,error:a}=await appLatest(t,e);return x(r,n,a)}export function appStatusWith(t,e){return x(q(t),e.version,e.error)}export function cachedAppStatus(t){const e=q(t);return x(e,e.blocker&&"classic_install"!==e.blocker?null:w()[t]?.version??null)}export function appStatusText(t){return t.installed?t.outdated?s.t("app_update.outdated",{current:t.current,latest:t.latest}):t.current&&t.latest?s.t("app_update.up_to_date",{current:t.current}):t.current??"":t.latest?s.t("app_update.not_installed",{latest:t.latest}):""}export function appUpgradeCommand(t){return`${p} upgrade ${t}`}function C(t){c()?process.stderr.write(t+"\n"):console.log(t)}async function S(t,r=!0){const{ok:n}=await e.prompt([{type:"confirm",name:"ok",message:t,default:r}]);return n}let U={totalMs:12e4,stepMs:1e3};export function setQuitWaitForTests(t){U=t}export async function upgradeApp(e,r={}){const n=Date.now(),a=await appStatus(e,{refresh:r.refresh??!0}),o=a.name,l=(t,r={})=>{const o={...a,result:t,upgraded:"upgraded"===t||"installed"===t,version_after:null,...r};return["up_to_date","unavailable","not_installed","offered"].includes(t)||f(`app-upgrade:${e}`,"failed"===t?"fail":"ok",{duration_ms:Date.now()-n,detail:{result:t,current:a.current,latest:a.latest,...r.reason?{reason:r.reason}:{}}}),o};if(a.blocker)return!r.quiet&&a.installed&&a.outdated&&C(t.yellow(" ! "+s.t("app_update.outdated",{current:a.current,latest:a.latest}))),r.quiet||C(t.yellow(" ! "+s.t(`app_update.blocked_${a.blocker}`,{tool:o,url:k(e).appInstaller.downloadPage,command:appUpgradeCommand(e)}))),l("unavailable",{reason:a.blocker});if(!a.latest)return r.quiet||C(t.yellow(" ! "+s.t("app_update.latest_unknown",{tool:o,reason:a.error??""}))),l("unavailable",{reason:a.error});if(!a.installed&&!r.install)return l("not_installed");const p=Boolean(r.force&&a.installed&&!a.outdated);if(a.installed&&!a.outdated&&!p)return r.quiet||(C(t.green(" √ "+s.t("app_update.already_latest",{tool:o,current:a.current??a.latest}))),C(t.gray(" "+s.t("app_update.force_hint",{command:appUpgradeCommand(e)})))),l("up_to_date");if(p?C(t.yellow(" ! "+s.t("app_update.reinstalling",{tool:o,latest:a.latest,current:a.current??"?"}))):!a.installed||r.askBeforeUpgrade&&!a.running||C(t.yellow(` ! ${o} `+appStatusText(a))),a.installed&&r.askBeforeUpgrade&&!a.running){if(!u()||d())return C(t.gray(" "+s.t("app_update.upgrade_hint",{tool:o,current:a.current??"?",latest:a.latest,command:appUpgradeCommand(e)}))),l("offered");if(!await S(s.t("app_update.upgrade_offer",{tool:o,current:a.current??"?",latest:a.latest})))return C(t.gray(" "+s.t("app_update.declined_hint",{command:appUpgradeCommand(e)}))),l("declined")}let i=!1;if(a.running){if(!u()||d())return C(t.yellow(" ! "+s.t("app_update.running_hint",{tool:o,command:appUpgradeCommand(e)}))),l("left_running");if(!await S(s.t("app_update.offer_quit",{tool:o,latest:a.latest})))return C(t.gray(" "+s.t("app_update.declined_hint",{command:appUpgradeCommand(e)}))),l("declined");const r=k(e).runningApp;if(r?.quit?.())i=!0;else{if(!r?.quitRefused?.()||!await async function(e,r){C(t.yellow(" ! "+s.t("app_update.quit_confirm_in_app",{tool:e,seconds:String(Math.round(U.totalMs/1e3))})));const n=Date.now()+U.totalMs;for(;Date.now()<n;)if(await new Promise(t=>setTimeout(t,U.stepMs)),!r.isRunning())return await new Promise(t=>setTimeout(t,Math.min(1e3,U.stepMs))),!0;return!1}(o,r)){const n=r?.quitRefused?.()?"app_update.quit_refused":"app_update.quit_failed";return C(t.yellow(" ! "+s.t(n,{tool:o,command:appUpgradeCommand(e)}))),l(r?.quitRefused?.()?"left_running":"failed",{reason:r?.quitRefused?.()?"quit_refused":"quit_failed"})}i=!0}}try{const{version:r}=await async function(e){const r=k(e).appInstaller,n=!c()&&Boolean(process.stdout.isTTY);let a=-1;try{return await r.install({downloading:n=>C(t.gray(" "+s.t("app_update.downloading",{tool:k(e).displayName,version:n,size:String(r.sizeMb())}))),percent:e=>{!n||e-a<5||(a=e,process.stdout.write(t.gray(`\r ${e}%`)),e>=100&&process.stdout.write("\n"))},verifying:()=>{n&&a>=0&&a<100&&process.stdout.write("\n"),a=100,C(t.gray(" "+s.t("app_update.verifying",{vendor:r.vendor})))},installing:()=>C(t.gray(" "+s.t("app_update.installing")))})}finally{n&&a>=0&&a<100&&process.stdout.write("\n")}}(e);return C(t.green(" √ "+(p?s.t("app_update.reinstalled",{tool:o,version:r}):a.installed?s.t("app_update.upgraded",{tool:o,from:a.current??"?",to:r}):s.t("app_update.installed",{tool:o,version:r})))),i&&!k(e).runningApp?.launch?.()&&C(t.yellow(" ! "+s.t("setup.reopen_hint",{tool:o}))),l(a.installed?"upgraded":"installed",{version_after:r,current:a.current})}catch(r){const n=r instanceof Error?r.message:String(r);return _.logError(`app-update.install(${e})`,r),C(t.yellow(" ! "+(a.installed?s.t("app_update.upgrade_failed",{tool:o,reason:n,current:a.current??"?",command:appUpgradeCommand(e)}):s.t("app_update.install_failed",{tool:o,reason:n,command:appUpgradeCommand(e)})))),i&&k(e).runningApp?.launch?.(),l("failed",{reason:n})}}
package/dist/lib/brand.js CHANGED
@@ -1 +1 @@
1
- import{existsSync as e,readdirSync as r,readFileSync as t}from"fs";import{dirname as n,join as o}from"path";import{fileURLToPath as a}from"url";const i=["id","displayName","productName","cliName","envPrefix","configDirName","providerKey","apiKeyEnv","desktopEntryName","packageName"];export const NEUTRAL_CLI_NAME="codekey";export function normalizeBrand(e){const r=e.cliName??e.shortName??"codekey";return{...e,cliName:r,envPrefix:e.envPrefix??r.toUpperCase().replace(/-/g,"_"),configDirName:e.configDirName??`.${r}`}}export function validateBrand(e,r="brand"){if(!e||"object"!=typeof e)throw new Error(`${r}: not an object`);const t=normalizeBrand(e);for(const e of i)if("string"!=typeof t[e]||!t[e].trim())throw new Error(`${r}: ${e} is required`);if(!/^[a-z][a-z0-9-]*$/.test(t.cliName))throw new Error(`${r}: cliName must be a lowercase command name`);if(!/^[A-Z][A-Z0-9_]*$/.test(t.envPrefix))throw new Error(`${r}: envPrefix must be UPPER_SNAKE`);if(!/^[a-z][a-z0-9-]*$/.test(t.providerKey))throw new Error(`${r}: providerKey must be lowercase`);const n=t.gatewayAddresses;if(!Array.isArray(n)||0===n.length||n.some(e=>!e||"string"!=typeof e.url||!/^https:\/\//.test(e.url)||"string"!=typeof e.label))throw new Error(`${r}: gatewayAddresses must be a non-empty list of { url: https://…, label }`);const o=t.links;if(!o||"string"!=typeof o.tokenPage)throw new Error(`${r}: links.tokenPage is required`);if(!Array.isArray(t.pluginMarketplaces))throw new Error(`${r}: pluginMarketplaces must be a list`);if(!Array.isArray(t.updateRegistries)||0===t.updateRegistries.length)throw new Error(`${r}: updateRegistries must be a non-empty list`);return t}export function loadBrandFile(e){return validateBrand(JSON.parse(t(e,"utf-8")),e)}let s=function(){const i=n(a(import.meta.url)),s=o(i,"..","brand.json");if(e(s))return loadBrandFile(s);const d=o(i,"..","..","brands");for(const n of e(d)?r(d).filter(e=>e.endsWith(".json")).sort():[]){const e=JSON.parse(t(o(d,n),"utf-8"));if(!0===e.default)return validateBrand(e,o(d,n))}throw new Error(`brand.json not found next to ${i}`)}();export function brand(){return s}export function setBrandForTests(e){const r=s;return s=e,()=>{s=r}}export function brandEnv(e){return process.env[brandEnvName(e)]}export function brandEnvName(e){return`${s.envPrefix}_${e}`}function d(e,r){return"zh_CN"===r?e.join("、"):e.length>1?`${e.slice(0,-1).join(", ")} and ${e[e.length-1]}`:e.join("")}export function brandTokens(e){const r=new URL(s.gatewayAddresses[0].url),t=s.gatewayAddresses[1]?.url??s.gatewayAddresses[0].url;return{brand:s.displayName,product:s.productName,cli:s.cliName,configDir:s.configDirName,envPrefix:s.envPrefix,siteHost:r.host,altSiteUrl:t,updateSources:d(s.updateRegistries.map(e=>e.label),e)}}export const BRAND_NAME=s.displayName;export const PROVIDER_KEY=s.providerKey;export const API_KEY_ENV=s.apiKeyEnv;export const CLI_NAME=s.cliName;
1
+ import{existsSync as e,readdirSync as r,readFileSync as t}from"fs";import{dirname as n,join as o}from"path";import{fileURLToPath as a}from"url";const i=["id","displayName","productName","cliName","envPrefix","configDirName","providerKey","apiKeyEnv","desktopEntryName","packageName"];export const NEUTRAL_CLI_NAME="codekey";export function normalizeBrand(e){const r=e.cliName??e.shortName??"codekey";return{...e,cliName:r,envPrefix:e.envPrefix??r.toUpperCase().replace(/-/g,"_"),configDirName:e.configDirName??`.${r}`}}export function validateBrand(e,r="brand"){if(!e||"object"!=typeof e)throw new Error(`${r}: not an object`);const t=normalizeBrand(e);for(const e of i)if("string"!=typeof t[e]||!t[e].trim())throw new Error(`${r}: ${e} is required`);if(!/^[a-z][a-z0-9-]*$/.test(t.cliName))throw new Error(`${r}: cliName must be a lowercase command name`);if(!/^[A-Z][A-Z0-9_]*$/.test(t.envPrefix))throw new Error(`${r}: envPrefix must be UPPER_SNAKE`);if(!/^[a-z][a-z0-9-]*$/.test(t.providerKey))throw new Error(`${r}: providerKey must be lowercase`);const n=t.gatewayAddresses;if(!Array.isArray(n)||0===n.length||n.some(e=>!e||"string"!=typeof e.url||!/^https:\/\//.test(e.url)||"string"!=typeof e.label))throw new Error(`${r}: gatewayAddresses must be a non-empty list of { url: https://…, label }`);const o=t.links;if(!o||"string"!=typeof o.tokenPage)throw new Error(`${r}: links.tokenPage is required`);if(!Array.isArray(t.pluginMarketplaces))throw new Error(`${r}: pluginMarketplaces must be a list`);if(!Array.isArray(t.updateRegistries)||0===t.updateRegistries.length)throw new Error(`${r}: updateRegistries must be a non-empty list`);return t}export function loadBrandFile(e){return validateBrand(JSON.parse(t(e,"utf-8")),e)}let s=function(){const i=n(a(import.meta.url)),s=o(i,"..","brand.json");if(e(s))return loadBrandFile(s);const c=o(i,"..","..","brands");for(const n of e(c)?r(c).filter(e=>e.endsWith(".json")).sort():[]){const e=JSON.parse(t(o(c,n),"utf-8"));if(!0===e.default)return validateBrand(e,o(c,n))}throw new Error(`brand.json not found next to ${i}`)}();export function brand(){return s}export function setBrandForTests(e){const r=s;return s=e,()=>{s=r}}export function brandEnv(e){return process.env[brandEnvName(e)]}export function brandEnvName(e){return`${s.envPrefix}_${e}`}function c(e,r){return"zh_CN"===r?e.join("、"):e.length>1?`${e.slice(0,-1).join(", ")} and ${e[e.length-1]}`:e.join("")}export function brandTokens(e){const r=new URL(s.gatewayAddresses[0].url),t=s.gatewayAddresses[1]?.url??s.gatewayAddresses[0].url;return{brand:s.displayName,product:s.productName,cli:s.cliName,package:s.packageName,configDir:s.configDirName,envPrefix:s.envPrefix,siteHost:r.host,altSiteUrl:t,updateSources:c(s.updateRegistries.map(e=>e.label),e)}}export const BRAND_NAME=s.displayName;export const PROVIDER_KEY=s.providerKey;export const API_KEY_ENV=s.apiKeyEnv;export const CLI_NAME=s.cliName;
@@ -64,6 +64,8 @@ export declare class ClaudeCodeManager {
64
64
  private settingsPath;
65
65
  private mcpConfigPath;
66
66
  private lastTierFallbacks;
67
+ /** `NAME=value` of model settings the user chose that the last loadPrivateConfig left alone. */
68
+ private lastKept;
67
69
  private constructor();
68
70
  static getInstance(): ClaudeCodeManager;
69
71
  /**
@@ -110,6 +112,8 @@ export declare class ClaudeCodeManager {
110
112
  * @returns 实际被清除的键名(未改动时为空数组)
111
113
  */
112
114
  clearPinnedModels(availableModels?: string[]): string[];
115
+ /** 最近一次 loadPrivateConfig 保留下来的用户自己的模型设置(`名字=值`)。 */
116
+ getLastKept(): string[];
113
117
  /** 最近一次 loadPrivateConfig 实际写入的档位回退(用于应用后的提示)。 */
114
118
  getLastTierFallbacks(): TierFallback[];
115
119
  /**
@@ -1 +1 @@
1
- import{existsSync as e,mkdirSync as t,readFileSync as n,writeFileSync as o,renameSync as i,unlinkSync as r}from"fs";import{join as s,dirname as a}from"path";import{toolHome as c}from"./tools/types.js";import{logger as l}from"../utils/logger.js";import{configManager as d,DEFAULT_MODELS as g}from"./config.js";import{loadSettingsTemplate as f}from"./settings-template.js";import{cleanupAnthropicEnvVars as m}from"./env-cleanup.js";import{getClaudeCodeTierChains as C,getGatewayModelRules as u}from"./model-registry.js";import{autoCompactTokens as O,modelBlurb as h,prettyModelName as p}from"./gateway-models.js";import{baseModelId as _}from"./model-catalog.js";export const MODEL_ENV_VARS=["ANTHROPIC_DEFAULT_HAIKU_MODEL","ANTHROPIC_DEFAULT_SONNET_MODEL","ANTHROPIC_DEFAULT_OPUS_MODEL","ANTHROPIC_DEFAULT_FABLE_MODEL","ANTHROPIC_MODEL","ANTHROPIC_REASONING_MODEL"];const E={opus:"ANTHROPIC_DEFAULT_OPUS_MODEL",sonnet:"ANTHROPIC_DEFAULT_SONNET_MODEL",haiku:"ANTHROPIC_DEFAULT_HAIKU_MODEL",fable:"ANTHROPIC_DEFAULT_FABLE_MODEL"},A="CLAUDE_CODE_MAX_CONTEXT_TOKENS";function T(){return s(d.getConfigDir(),"claude-code-picker.json")}function P(){try{const e=JSON.parse(n(T(),"utf-8")).ids;return Array.isArray(e)?e.map(String):[]}catch{return[]}}function v(e,t=[]){if(!e||"object"!=typeof e)return!1;const{options:n,replaceBuiltInOptions:o}=e;if(!Array.isArray(n)||o)return!1;const i=u(),r=new Set([...i.models.map(e=>e.id),...i.retired,...P(),...t]);return n.every(e=>e&&"object"==typeof e&&r.has(_(String(e.model))))}export function applyGatewayModels(e,n){if(void 0===n)return e;const i={...e};if(void 0!==i.modelPicker&&!v(i.modelPicker,n.map(e=>e.id)))return i;const r=v(i.modelPicker)?i.modelPicker.options.map(e=>_(e.model)):[],s={...i.env||{}},a={...i.modelSettings||{}};for(const e of r){if(!a[e])continue;const{autoCompactWindow:t,...n}=a[e];Object.keys(n).length>0?a[e]=n:delete a[e]}const c=e=>(e.contextWindow??0)>=1e6,g=n.filter(e=>!c(e)).map(e=>e.contextWindow).filter(e=>"number"==typeof e&&e>0);!function(e){try{t(d.getConfigDir(),{recursive:!0}),o(T(),JSON.stringify({ids:e})+"\n")}catch(e){l.logError("claudeCode.recordWrittenPickerIds",e)}}(n.map(e=>e.id));const f=e=>h(e.description,{millionContext:c(e)});if(0===n.length)delete i.modelPicker,delete s[A];else{i.modelPicker={options:n.map(e=>({model:c(e)?`${e.id}[1m]`:e.id,label:e.name||p(e.id),...f(e)?{description:f(e)}:{}}))},g.length>0?s[A]=String(Math.min(...g)):delete s[A];for(const e of n){if(!e.contextWindow)continue;const t=O(e.contextWindow,e.autoCompactTokens);a[e.id]={...a[e.id],autoCompactWindow:Math.min(Math.max(t,1e5),1e6)}}}return Object.keys(a).length>0?i.modelSettings=a:delete i.modelSettings,Object.keys(s).length>0?i.env=s:delete i.env,i}export function resolveTierFallbacks(e){if(!e||0===e.length)return[];const t=new Set(e.map(_)),n=[];for(const[e,o]of Object.entries(C())){const i=E[e],[r,...s]=o;if(!i||!r||t.has(r))continue;const a=s.find(e=>t.has(e));a&&n.push({tier:e,envVar:i,from:r,to:a})}return n}export class ClaudeCodeManager{static instance;settingsPath;mcpConfigPath;lastTierFallbacks=[];constructor(){this.settingsPath=s(c(),".claude","settings.json"),this.mcpConfigPath=s(c(),".claude.json")}static getInstance(){return ClaudeCodeManager.instance||(ClaudeCodeManager.instance=new ClaudeCodeManager),ClaudeCodeManager.instance}ensureConfigDir(n){const o=a(n);e(o)||t(o,{recursive:!0})}atomicWrite(t,n){const s=`${t}.tmp`;try{o(s,n,"utf-8"),i(s,t)}catch(t){if(e(s))try{r(s)}catch{}throw t}}getSettings(){try{if(e(this.settingsPath)){const e=n(this.settingsPath,"utf-8");return JSON.parse(e)}}catch(e){l.warn("Failed to read Claude Code settings:",e),l.logError("ClaudeCodeManager.getSettings",e)}return{}}saveSettings(e){try{this.ensureConfigDir(this.settingsPath),this.atomicWrite(this.settingsPath,JSON.stringify(e,null,2))}catch(e){throw new Error(`Failed to save Claude Code settings: ${e}`)}}getMCPConfig(){try{if(e(this.mcpConfigPath)){const e=n(this.mcpConfigPath,"utf-8");return JSON.parse(e)}}catch(e){l.warn("Failed to read Claude Code MCP config:",e),l.logError("ClaudeCodeManager.getMCPConfig",e)}return{}}saveMCPConfig(e){try{this.ensureConfigDir(this.mcpConfigPath),this.atomicWrite(this.mcpConfigPath,JSON.stringify(e,null,2))}catch(e){throw new Error(`Failed to save Claude Code MCP config: ${e}`)}}loadPrivateConfig(e,t,n,o){this.ensureOnboardingCompleted(),m();const i=f(),r=i.env||{},{env:s,...a}=i,c=this.getSettings(),{ANTHROPIC_API_KEY:l,...C}=c.env||{},u={...C};for(const e of MODEL_ENV_VARS)delete u[e];const O={},h=e=>{const n=(t[e]||"").trim();return n&&n!==g[e]?n:null},p=h("haiku"),_=h("sonnet"),E=h("opus"),A=new Set([p&&"haiku",_&&"sonnet",E&&"opus"].filter(Boolean));this.lastTierFallbacks=resolveTierFallbacks(n).filter(e=>!A.has(e.tier));for(const e of this.lastTierFallbacks)O[e.envVar]=e.to;p&&(O.ANTHROPIC_DEFAULT_HAIKU_MODEL=p),_&&(O.ANTHROPIC_DEFAULT_SONNET_MODEL=_),E&&(O.ANTHROPIC_DEFAULT_OPUS_MODEL=E,O.ANTHROPIC_MODEL=E);const T={...c};for(const[e,t]of Object.entries(a))e in T||(T[e]=t);T.showThinkingSummaries=!0;const P={...T,env:{...u,...Object.fromEntries(Object.entries(r).filter(([e])=>!(e in u))),ANTHROPIC_AUTH_TOKEN:e,ANTHROPIC_BASE_URL:d.getBaseUrl(),...O}};E?P.model=E:Object.prototype.hasOwnProperty.call(a,"model")?P.model=a.model:delete P.model,this.saveSettings(applyGatewayModels(P,o))}ensureOnboardingCompleted(){try{const e=this.getMCPConfig();e.hasCompletedOnboarding||this.saveMCPConfig({...e,hasCompletedOnboarding:!0})}catch(e){l.warn("Failed to ensure onboarding completed:",e),l.logError("ClaudeCodeManager.ensureOnboardingCompleted",e)}}unloadPrivateConfig(){const e=this.getSettings();if(!e.env){if(void 0!==e.model){const{model:t,...n}=e;this.saveSettings(n)}return}const{ANTHROPIC_AUTH_TOKEN:t,ANTHROPIC_BASE_URL:n,API_TIMEOUT_MS:o,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:i,CLAUDE_CODE_AUTO_MODE_SERVER:r,...s}=e.env,a={...s};for(const e of MODEL_ENV_VARS)delete a[e];const c={...applyGatewayModels({...e,env:a},[])};c.env=c.env||{},delete c.model,c.env&&0===Object.keys(c.env).length&&delete c.env,this.saveSettings(c)}clearPinnedModels(e){const t=this.getSettings(),n=t.env,o=n?.ANTHROPIC_AUTH_TOKEN?Object.fromEntries(resolveTierFallbacks(e).map(e=>[e.envVar,e.to])):{},i=[];for(const e of MODEL_ENV_VARS)n&&e in n&&n[e]!==o[e]&&i.push(e);void 0!==t.model&&i.push("model");const r=Object.entries(o).some(([e,t])=>n?.[e]!==t);if(0===i.length&&!r)return[];const s={...t};if(delete s.model,n){const e={...n};for(const t of MODEL_ENV_VARS)delete e[t];Object.assign(e,o),0===Object.keys(e).length?delete s.env:s.env=e}return this.saveSettings(s),i}getLastTierFallbacks(){return[...this.lastTierFallbacks]}detectCurrentConfig(){try{const e=this.getSettings();return e.env&&e.env.ANTHROPIC_AUTH_TOKEN?{apiKey:e.env.ANTHROPIC_AUTH_TOKEN}:{apiKey:null}}catch{return{apiKey:null}}}getExperimentalFeature(e){const t=this.getSettings();return t.env?.[e]}setExperimentalFeature(e,t){const n=this.getSettings(),o=n.env||{},i={...n,env:{...o,[e]:t}};this.saveSettings(i)}removeExperimentalFeature(e){const t=this.getSettings();if(!t.env)return;const{[e]:n,...o}=t.env,i={...t,env:o};this.saveSettings(i)}}export const claudeCodeManager=ClaudeCodeManager.getInstance();
1
+ import{existsSync as e,mkdirSync as t,readFileSync as n,writeFileSync as o,renameSync as s,unlinkSync as i}from"fs";import{join as r,dirname as a}from"path";import{toolHome as l}from"./tools/types.js";import{logger as c}from"../utils/logger.js";import{configManager as d,DEFAULT_MODELS as g}from"./config.js";import{loadSettingsTemplate as u}from"./settings-template.js";import{cleanupAnthropicEnvVars as f}from"./env-cleanup.js";import{getClaudeCodeTierChains as m,getGatewayModelRules as O,getRetiredDefaultModelIds as C}from"./model-registry.js";import{claimField as h,forgetField as p,ownsValue as _}from"./written-values.js";import{noteWrittenGatewayUrl as E}from"./gateway-addresses.js";import{autoCompactTokens as A,modelBlurb as T,prettyModelName as v}from"./gateway-models.js";import{baseModelId as P}from"./model-catalog.js";export const MODEL_ENV_VARS=["ANTHROPIC_DEFAULT_HAIKU_MODEL","ANTHROPIC_DEFAULT_SONNET_MODEL","ANTHROPIC_DEFAULT_OPUS_MODEL","ANTHROPIC_DEFAULT_FABLE_MODEL","ANTHROPIC_MODEL","ANTHROPIC_REASONING_MODEL"];const N="claude-code.model",S=e=>`claude-code.env.${e}`;function M(e){const t=Object.values(m()).flat(),n=[...Object.values(g),...Object.values(e)].filter(e=>"string"==typeof e&&""!==e);return[...new Set(["opus[1m]",...n,...C(),...t])]}const D={opus:"ANTHROPIC_DEFAULT_OPUS_MODEL",sonnet:"ANTHROPIC_DEFAULT_SONNET_MODEL",haiku:"ANTHROPIC_DEFAULT_HAIKU_MODEL",fable:"ANTHROPIC_DEFAULT_FABLE_MODEL"},L="CLAUDE_CODE_MAX_CONTEXT_TOKENS";function y(){return r(d.getConfigDir(),"claude-code-picker.json")}function I(){try{const e=JSON.parse(n(y(),"utf-8")).ids;return Array.isArray(e)?e.map(String):[]}catch{return[]}}function R(e,t=[]){if(!e||"object"!=typeof e)return!1;const{options:n,replaceBuiltInOptions:o}=e;if(!Array.isArray(n)||o)return!1;const s=O(),i=new Set([...s.models.map(e=>e.id),...s.retired,...I(),...t]);return n.every(e=>e&&"object"==typeof e&&i.has(P(String(e.model))))}export function applyGatewayModels(e,n){if(void 0===n)return e;const s={...e};if(void 0!==s.modelPicker&&!R(s.modelPicker,n.map(e=>e.id)))return s;const i=R(s.modelPicker)?s.modelPicker.options.map(e=>P(e.model)):[],r={...s.env||{}},a={...s.modelSettings||{}};for(const e of i){if(!a[e])continue;const{autoCompactWindow:t,...n}=a[e];Object.keys(n).length>0?a[e]=n:delete a[e]}const l=e=>(e.contextWindow??0)>=1e6,g=n.filter(e=>!l(e)).map(e=>e.contextWindow).filter(e=>"number"==typeof e&&e>0);!function(e){try{t(d.getConfigDir(),{recursive:!0}),o(y(),JSON.stringify({ids:e})+"\n")}catch(e){c.logError("claudeCode.recordWrittenPickerIds",e)}}(n.map(e=>e.id));const u=e=>T(e.description,{millionContext:l(e)});if(0===n.length)delete s.modelPicker,delete r[L];else{s.modelPicker={options:n.map(e=>({model:l(e)?`${e.id}[1m]`:e.id,label:e.name||v(e.id),...u(e)?{description:u(e)}:{}}))},g.length>0?r[L]=String(Math.min(...g)):delete r[L];for(const e of n){if(!e.contextWindow)continue;const t=A(e.contextWindow,e.autoCompactTokens);a[e.id]={...a[e.id],autoCompactWindow:Math.min(Math.max(t,1e5),1e6)}}}return Object.keys(a).length>0?s.modelSettings=a:delete s.modelSettings,Object.keys(r).length>0?s.env=r:delete s.env,s}export function resolveTierFallbacks(e){if(!e||0===e.length)return[];const t=new Set(e.map(P)),n=[];for(const[e,o]of Object.entries(m())){const s=D[e],[i,...r]=o;if(!s||!i||t.has(i))continue;const a=r.find(e=>t.has(e));a&&n.push({tier:e,envVar:s,from:i,to:a})}return n}export class ClaudeCodeManager{static instance;settingsPath;mcpConfigPath;lastTierFallbacks=[];lastKept=[];constructor(){this.settingsPath=r(l(),".claude","settings.json"),this.mcpConfigPath=r(l(),".claude.json")}static getInstance(){return ClaudeCodeManager.instance||(ClaudeCodeManager.instance=new ClaudeCodeManager),ClaudeCodeManager.instance}ensureConfigDir(n){const o=a(n);e(o)||t(o,{recursive:!0})}atomicWrite(t,n){const r=`${t}.tmp`;try{o(r,n,"utf-8"),s(r,t)}catch(t){if(e(r))try{i(r)}catch{}throw t}}getSettings(){try{if(e(this.settingsPath)){const e=n(this.settingsPath,"utf-8");return JSON.parse(e)}}catch(e){c.warn("Failed to read Claude Code settings:",e),c.logError("ClaudeCodeManager.getSettings",e)}return{}}saveSettings(e){try{this.ensureConfigDir(this.settingsPath),this.atomicWrite(this.settingsPath,JSON.stringify(e,null,2))}catch(e){throw new Error(`Failed to save Claude Code settings: ${e}`)}}getMCPConfig(){try{if(e(this.mcpConfigPath)){const e=n(this.mcpConfigPath,"utf-8");return JSON.parse(e)}}catch(e){c.warn("Failed to read Claude Code MCP config:",e),c.logError("ClaudeCodeManager.getMCPConfig",e)}return{}}saveMCPConfig(e){try{this.ensureConfigDir(this.mcpConfigPath),this.atomicWrite(this.mcpConfigPath,JSON.stringify(e,null,2))}catch(e){throw new Error(`Failed to save Claude Code MCP config: ${e}`)}}loadPrivateConfig(e,t,n,o){this.ensureOnboardingCompleted(),f();const s=u(),i=s.env||{},{env:r,...a}=s,l=this.getSettings(),{ANTHROPIC_API_KEY:c,...m}=l.env||{},O={...m},C={},A=e=>{const n=(t[e]||"").trim();return n&&n!==g[e]?n:null},T=A("haiku"),v=A("sonnet"),P=A("opus"),D=new Set([T&&"haiku",v&&"sonnet",P&&"opus"].filter(Boolean)),L=resolveTierFallbacks(n).filter(e=>!D.has(e.tier));for(const e of L)C[e.envVar]=e.to;const y=new Set;T&&(C.ANTHROPIC_DEFAULT_HAIKU_MODEL=T),v&&(C.ANTHROPIC_DEFAULT_SONNET_MODEL=v),P&&(C.ANTHROPIC_DEFAULT_OPUS_MODEL=P,C.ANTHROPIC_MODEL=P),T&&y.add("ANTHROPIC_DEFAULT_HAIKU_MODEL"),v&&y.add("ANTHROPIC_DEFAULT_SONNET_MODEL"),P&&["ANTHROPIC_DEFAULT_OPUS_MODEL","ANTHROPIC_MODEL"].forEach(e=>y.add(e));const I=M(t);this.lastKept=[];for(const e of MODEL_ENV_VARS){const t=O[e],n=C[e];if(void 0!==n)h(S(e),t,n,{explicit:y.has(e),legacy:I})?O[e]=n:this.lastKept.push(`${e}=${t}`);else{if(void 0===t)continue;_(S(e),t,I)?(delete O[e],p(S(e))):this.lastKept.push(`${e}=${t}`)}}this.lastTierFallbacks=L.filter(e=>O[e.envVar]===e.to);const R={...l};for(const[e,t]of Object.entries(a))"model"!==e&&(e in R||(R[e]=t));R.showThinkingSummaries=!0;const U={...R,env:{...O,...Object.fromEntries(Object.entries(i).filter(([e])=>!(e in O))),ANTHROPIC_AUTH_TOKEN:e,ANTHROPIC_BASE_URL:d.getBaseUrl()}};E(d.getBaseUrl());const H=P??("string"==typeof a.model?a.model:void 0),F=l.model;void 0===H?void 0!==F&&_(N,F,I)&&(delete U.model,p(N)):h(N,F,H,{explicit:Boolean(P),legacy:I})?U.model=H:this.lastKept.push(`model=${String(F)}`),this.saveSettings(applyGatewayModels(U,o))}ensureOnboardingCompleted(){try{const e=this.getMCPConfig();e.hasCompletedOnboarding||this.saveMCPConfig({...e,hasCompletedOnboarding:!0})}catch(e){c.warn("Failed to ensure onboarding completed:",e),c.logError("ClaudeCodeManager.ensureOnboardingCompleted",e)}}unloadPrivateConfig(){const e=this.getSettings(),t=M(d.getModelsForTool("claude-code")),n=void 0!==e.model&&_(N,e.model,t);if(p(N),!e.env){if(n){const{model:t,...n}=e;this.saveSettings(n)}return}const{ANTHROPIC_AUTH_TOKEN:o,ANTHROPIC_BASE_URL:s,API_TIMEOUT_MS:i,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:r,CLAUDE_CODE_AUTO_MODE_SERVER:a,...l}=e.env,c={...l};for(const e of MODEL_ENV_VARS)e in c&&_(S(e),c[e],t)&&delete c[e],p(S(e));const g={...applyGatewayModels({...e,env:c},[])};g.env=g.env||{},n&&delete g.model,g.env&&0===Object.keys(g.env).length&&delete g.env,this.saveSettings(g)}clearPinnedModels(e){const t=this.getSettings(),n=t.env,o=n?.ANTHROPIC_AUTH_TOKEN?Object.fromEntries(resolveTierFallbacks(e).map(e=>[e.envVar,e.to])):{},s=[];for(const e of MODEL_ENV_VARS)n&&e in n&&n[e]!==o[e]&&s.push(e);void 0!==t.model&&s.push("model");const i=Object.entries(o).some(([e,t])=>n?.[e]!==t);if(0===s.length&&!i)return[];const r={...t};if(delete r.model,n){const e={...n};for(const t of MODEL_ENV_VARS)delete e[t];Object.assign(e,o),0===Object.keys(e).length?delete r.env:r.env=e}this.saveSettings(r),p(N);for(const e of MODEL_ENV_VARS)o[e]?h(S(e),o[e],o[e]):p(S(e));return s}getLastKept(){return[...this.lastKept]}getLastTierFallbacks(){return[...this.lastTierFallbacks]}detectCurrentConfig(){try{const e=this.getSettings();return e.env&&e.env.ANTHROPIC_AUTH_TOKEN?{apiKey:e.env.ANTHROPIC_AUTH_TOKEN}:{apiKey:null}}catch{return{apiKey:null}}}getExperimentalFeature(e){const t=this.getSettings();return t.env?.[e]}setExperimentalFeature(e,t){const n=this.getSettings(),o=n.env||{},s={...n,env:{...o,[e]:t}};this.saveSettings(s)}removeExperimentalFeature(e){const t=this.getSettings();if(!t.env)return;const{[e]:n,...o}=t.env,s={...t,env:o};this.saveSettings(s)}}export const claudeCodeManager=ClaudeCodeManager.getInstance();
@@ -6,11 +6,6 @@ export declare class Command {
6
6
  /** Set by `preflightArgs`: this run configures tools (see CONFIGURING_COMMANDS). */
7
7
  private configuring;
8
8
  constructor();
9
- /**
10
- * 配置类命令(CONFIGURING_COMMANDS 与向导)启动时检测 shell 环境里泄漏的
11
- * ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY 并清除,否则它们会覆盖 vibecli 写进各工具配置文件的凭据。
12
- */
13
- private cleanupLeakedEnvVars;
14
9
  /**
15
10
  * Reads the global flags before commander runs. A help request (`-h` / `--help`
16
11
  * anywhere, or `vibecli help ...`) only prints help: it neither applies `--base`
@@ -1 +1 @@
1
- import{i18n as n,detectSystemLang as r}from"./i18n.js";import{configManager as o}from"./config.js";import{runModelRefresh as e}from"./model-refresh.js";import{refreshClientConfig as t}from"./client-config-sync.js";import{normalizeBaseUrl as s}from"./gateway-addresses.js";import i from"chalk";import{setRuntimeOptions as m,detectColorPreferenceFromEnv as a,isColorEnabled as c,isDryRun as l,isJsonMode as d}from"./runtime-options.js";import{cleanupAnthropicEnvVars as u,envCleanupChangedProfile as g,machineEnvVarsLeft as p}from"./env-cleanup.js";import{runStartupUpdate as f}from"./auto-update.js";import{buildProgram as h}from"./program.js";import{firstCommandWord as _,isHelpRequest as y,renderHelpTopic as w}from"./cli-help.js";import{reportCliError as v,usageError as j,CliError as k}from"./errors.js";import{CLI_NAME as C}from"./brand.js";import{dryRunUnsupportedCommand as x}from"./dry-run.js";import{flushTelemetry as A,installClientHeaders as b,setTelemetryCommand as D,track as M}from"./telemetry.js";export{buildProgram}from"./program.js";const E=new Set(["setup","init","auth","enter","config"]);export class Command{program;configuring=!1;constructor(){const e=o.isLangChosen()?o.getLang():r();n.loadFromConfig(e),this.program=h()}cleanupLeakedEnvVars(){const r=u();if(0===r.length||d())return;const o=p(),e=g();if(e||0===o.length){const o=n.t(e?"messages.env_vars_cleared":"messages.env_vars_ignored",{vars:r.join(", ")});console.log(c()?i.yellow(o):o)}if(o.length>0){const r=n.t("messages.env_vars_machine_left",{vars:o.join(", ")});console.log(c()?i.yellow(r):r)}}preflightArgs(n){const r=r=>n.includes(r),e=!r("--no-color")&&a();if(m({nonInteractive:r("--non-interactive"),json:r("--json"),yes:r("--yes")||r("-y"),dryRun:r("--dry-run"),color:e}),e||(i.level=0),this.configuring=!1,y(n))return;let t;for(let r=0;r<n.length;r+=1){const o=n[r];"--base"===o&&n[r+1]?(t=n[r+1],r+=1):o.startsWith("--base=")&&(t=o.slice(7))}const c=_(n),l=void 0===c&&!["--version","-v"].some(r);this.configuring=l||void 0!==c&&E.has(c),t&&o.setBaseUrl(s(t)??t,this.configuring&&!r("--dry-run"))}async execute(n){const r=Date.now();let o=!1;try{if(this.preflightArgs(n),b(),y(n)||["--version","-v"].some(r=>n.includes(r))||(D(_(n)??"wizard"),M("command","start",{detail:{args:n.filter(n=>n.startsWith("-")&&!/key/i.test(n))}}),o=!0),"help"===n[0]&&n[1]){const r=w(n[1]);if(r)return void process.stdout.write(r)}if(this.rejectUnknownCommand(n),!y(n)){if(l()&&this.rejectUnsupportedDryRun(n),this.configuring&&!l()&&this.cleanupLeakedEnvVars(),(await f(n)).handled)return;this.configuring&&await t({timeoutMs:5e3}),await e(n)}if(await this.program.parseAsync(n,{from:"user"}),o){const n=Boolean(process.exitCode)&&0!==process.exitCode;M("command",n?"fail":"ok",{duration_ms:Date.now()-r,...n?{detail:{exit_code:process.exitCode}}:{}})}}catch(e){if("commander.helpDisplayed"===e?.code||"commander.version"===e?.code)return;const t=this.normalizeCommanderError(e,n),s=v(t);if(o){const n=t instanceof k?`${t.type}${t.subtype?"/"+t.subtype:""}`:void 0;M("command",130===s?"cancel":"fail",{duration_ms:Date.now()-r,error:t,detail:{exit_code:s,...n?{type:n}:{}}})}}finally{o&&await A()}}rejectUnsupportedDryRun(r){const o=x(r);if(null!==o)throw j(n.t("runtime.dry_run_unsupported",{command:o,cli:C}),n.t("runtime.dry_run_unsupported_hint",{cli:C}),"dry_run_unsupported")}rejectUnknownCommand(r){const o=_(r);if(!o||"help"===o)return;if(this.program.commands.some(n=>n.name()===o))return;const e=this.suggestCommand(o),t=e?n.t("cli.unknown_hint_suggestion",{command:e}):n.t("cli.unknown_hint");throw j(n.t("cli.error_unknown",{command:o}),t,"unknown_command")}suggestCommand(n){const r=(n,r)=>{const o=Array.from({length:r.length+1},(n,r)=>r);for(let e=1;e<=n.length;e+=1){let t=o[0];o[0]=e;for(let s=1;s<=r.length;s+=1){const i=o[s];o[s]=Math.min(o[s]+1,o[s-1]+1,t+(n[e-1]===r[s-1]?0:1)),t=i}}return o[r.length]};return this.program.commands.map(n=>n.name()).map(o=>({name:o,score:r(n,o)})).filter(({score:r})=>r<=Math.max(2,Math.floor(n.length/2))).sort((n,r)=>n.score-r.score)[0]?.name}normalizeCommanderError(r,o){if(r instanceof k)return r;const e=r?.code,t=r instanceof Error?r.message:String(r);if("commander.unknownOption"===e||"commander.missingArgument"===e||"commander.missingMandatoryOption"===e||"commander.invalidArgument"===e){const r=_(o),s=/'([^']+)'/.exec(t)?.[1]??"",i="commander.unknownOption"===e?"cli.err_unknown_option":"commander.missingArgument"===e?"cli.err_missing_argument":"commander.missingMandatoryOption"===e?"cli.err_missing_option":null,m=i&&s?n.t(i,{name:s}):t.replace(/^error:\s*/,"");return j(m,r?n.t("cli.unknown_option_hint",{command:r}):n.t("cli.unknown_option_hint_root"),e)}return r}getProgram(){return this.program}}
1
+ import{i18n as r,detectSystemLang as n}from"./i18n.js";import{configManager as o}from"./config.js";import{runModelRefresh as t}from"./model-refresh.js";import{refreshClientConfig as e}from"./client-config-sync.js";import{normalizeBaseUrl as i}from"./gateway-addresses.js";import s from"chalk";import{setRuntimeOptions as m,detectColorPreferenceFromEnv as a,isDryRun as c}from"./runtime-options.js";import{runStartupUpdate as d}from"./auto-update.js";import{buildProgram as u}from"./program.js";import{firstCommandWord as l,isHelpRequest as p,renderHelpTopic as g}from"./cli-help.js";import{reportCliError as f,usageError as h,CliError as y}from"./errors.js";import{CLI_NAME as _}from"./brand.js";import{dryRunUnsupportedCommand as w}from"./dry-run.js";import{flushTelemetry as j,installClientHeaders as k,setTelemetryCommand as v,track as C}from"./telemetry.js";export{buildProgram}from"./program.js";const x=new Set(["setup","init","auth","enter","config"]);export class Command{program;configuring=!1;constructor(){const t=o.isLangChosen()?o.getLang():n();r.loadFromConfig(t),this.program=u()}preflightArgs(r){const n=n=>r.includes(n),t=!n("--no-color")&&a();if(m({nonInteractive:n("--non-interactive"),json:n("--json"),yes:n("--yes")||n("-y"),dryRun:n("--dry-run"),color:t}),t||(s.level=0),this.configuring=!1,p(r))return;let e;for(let n=0;n<r.length;n+=1){const o=r[n];"--base"===o&&r[n+1]?(e=r[n+1],n+=1):o.startsWith("--base=")&&(e=o.slice(7))}const c=l(r),d=void 0===c&&!["--version","-v"].some(n);this.configuring=d||void 0!==c&&x.has(c),e&&o.setBaseUrl(i(e)??e,this.configuring&&!n("--dry-run"))}async execute(r){const n=Date.now();let o=!1;try{if(this.preflightArgs(r),k(),p(r)||["--version","-v"].some(n=>r.includes(n))||(v(l(r)??"wizard"),C("command","start",{detail:{args:r.filter(r=>r.startsWith("-")&&!/key/i.test(r))}}),o=!0),"help"===r[0]&&r[1]){const n=g(r[1]);if(n)return void process.stdout.write(n)}if(this.rejectUnknownCommand(r),!p(r)){if(c()&&this.rejectUnsupportedDryRun(r),(await d(r)).handled)return;this.configuring&&await e({timeoutMs:5e3}),await t(r)}if(await this.program.parseAsync(r,{from:"user"}),o){const r=Boolean(process.exitCode)&&0!==process.exitCode;C("command",r?"fail":"ok",{duration_ms:Date.now()-n,...r?{detail:{exit_code:process.exitCode}}:{}})}}catch(t){if("commander.helpDisplayed"===t?.code||"commander.version"===t?.code)return;const e=this.normalizeCommanderError(t,r),i=f(e);if(o){const r=e instanceof y?`${e.type}${e.subtype?"/"+e.subtype:""}`:void 0;C("command",130===i?"cancel":"fail",{duration_ms:Date.now()-n,error:e,detail:{exit_code:i,...r?{type:r}:{}}})}}finally{o&&await j()}}rejectUnsupportedDryRun(n){const o=w(n);if(null!==o)throw h(r.t("runtime.dry_run_unsupported",{command:o,cli:_}),r.t("runtime.dry_run_unsupported_hint",{cli:_}),"dry_run_unsupported")}rejectUnknownCommand(n){const o=l(n);if(!o||"help"===o)return;if(this.program.commands.some(r=>r.name()===o))return;const t=this.suggestCommand(o),e=t?r.t("cli.unknown_hint_suggestion",{command:t}):r.t("cli.unknown_hint");throw h(r.t("cli.error_unknown",{command:o}),e,"unknown_command")}suggestCommand(r){const n=(r,n)=>{const o=Array.from({length:n.length+1},(r,n)=>n);for(let t=1;t<=r.length;t+=1){let e=o[0];o[0]=t;for(let i=1;i<=n.length;i+=1){const s=o[i];o[i]=Math.min(o[i]+1,o[i-1]+1,e+(r[t-1]===n[i-1]?0:1)),e=s}}return o[n.length]};return this.program.commands.map(r=>r.name()).map(o=>({name:o,score:n(r,o)})).filter(({score:n})=>n<=Math.max(2,Math.floor(r.length/2))).sort((r,n)=>r.score-n.score)[0]?.name}normalizeCommanderError(n,o){if(n instanceof y)return n;const t=n?.code,e=n instanceof Error?n.message:String(n);if("commander.unknownOption"===t||"commander.missingArgument"===t||"commander.missingMandatoryOption"===t||"commander.invalidArgument"===t){const n=l(o),i=/'([^']+)'/.exec(e)?.[1]??"",s="commander.unknownOption"===t?"cli.err_unknown_option":"commander.missingArgument"===t?"cli.err_missing_argument":"commander.missingMandatoryOption"===t?"cli.err_missing_option":null,m=s&&i?r.t(s,{name:i}):e.replace(/^error:\s*/,"");return h(m,n?r.t("cli.unknown_option_hint",{command:n}):r.t("cli.unknown_option_hint_root"),t)}return n}getProgram(){return this.program}}
@@ -0,0 +1,204 @@
1
+ /**
2
+ * The components vibecli installs and upgrades (`vibecli list` / `install` / `upgrade`): runtimes (Node, Git, uv),
3
+ * coding CLIs (Claude Code, Codex, Grok Build, agy) and desktop apps (Claude Desktop, ChatGPT). What each one is,
4
+ * where it comes from and how it is checked is declared once in `components.json` (repo root, shipped with the
5
+ * package); the docs and the install scripts' README are generated from the same file
6
+ * (`scripts/gen-components-doc.mjs`).
7
+ *
8
+ * - CLIs and apps are driven by their tool adapter (`installSpec` / `appInstaller`), as before.
9
+ * - Runtimes are installed by the brand's install scripts (`node.sh` / `node.ps1` …), which already find an
10
+ * existing copy, pick a mirror and check sha256. vibecli only upgrades a copy that one of those scripts (or the
11
+ * vendor's own standalone installer at the same place) manages: fnm's Node, uv in ~/.local/bin, the per-user
12
+ * Git for Windows. A copy from Homebrew, nvm, the system … is reported with the command that upgrades it; `--force`
13
+ * installs our own copy anyway.
14
+ * - Already installed + `install`: only the state and the upgrade command, never overwritten (user, 11-01); `--force`
15
+ * reinstalls.
16
+ */
17
+ export type ComponentCategory = 'runtime' | 'cli' | 'app';
18
+ export interface ComponentSpec {
19
+ id: string;
20
+ name: string;
21
+ category: ComponentCategory;
22
+ aliases?: string[];
23
+ platforms: NodeJS.Platform[];
24
+ /** Runtimes: the command and how to read its version out of `<command> --version`. */
25
+ detect?: {
26
+ command: string;
27
+ pattern: string;
28
+ };
29
+ latest: {
30
+ type: 'node-lts';
31
+ url: string;
32
+ } | {
33
+ type: 'devtools';
34
+ key: string;
35
+ platforms?: NodeJS.Platform[];
36
+ } | {
37
+ type: 'npm';
38
+ package: string;
39
+ staleTag?: boolean;
40
+ } | {
41
+ type: 'version-file';
42
+ url: string;
43
+ } | {
44
+ type: 'app';
45
+ };
46
+ /** Where it is downloaded from, per platform (`all` for every one); `{scripts}`, `{agy}`, `{devtools}` expand. */
47
+ sources: Partial<Record<NodeJS.Platform | 'all', string>>;
48
+ verify: {
49
+ checksum: string;
50
+ signature: string;
51
+ };
52
+ install: {
53
+ type: 'script';
54
+ script: string;
55
+ forceEnv?: Record<string, string>;
56
+ } | {
57
+ type: 'adapter';
58
+ } | {
59
+ type: 'app';
60
+ };
61
+ upgrade?: {
62
+ managedBy: string;
63
+ migrateGlobalPackages?: boolean;
64
+ };
65
+ /** Tool adapter for CLIs, app id for desktop apps. */
66
+ adapter?: string;
67
+ app?: string;
68
+ /** Command that shows it works after installing. */
69
+ check?: string[];
70
+ /** Components installed first when missing. */
71
+ requires: string[];
72
+ }
73
+ interface Manifest {
74
+ schema: number;
75
+ devtools: string;
76
+ components: ComponentSpec[];
77
+ }
78
+ /** Validates the shape the code relies on; a broken manifest is a build error, caught by the unit tests. */
79
+ export declare function parseManifest(text: string): Manifest;
80
+ /** Tests swap in their own manifest (null = the shipped one). */
81
+ export declare function setComponentManifestForTests(text: string | null): void;
82
+ export declare function listComponents(): ComponentSpec[];
83
+ /** By id or alias, case-insensitive. */
84
+ export declare function resolveComponent(name: string): ComponentSpec | undefined;
85
+ export declare function componentPlatform(): NodeJS.Platform;
86
+ /** `{scripts}` / `{agy}` / `{devtools}` → this brand's mirrors; null when the brand has no such mirror. */
87
+ export declare function expandUrl(text: string): string | null;
88
+ export declare function componentSource(spec: ComponentSpec, platform?: NodeJS.Platform): string;
89
+ /**
90
+ * Why components can't be installed for this account, null when they can. Everything goes into the user's own
91
+ * home directory: under sudo it would land in root's (and belong to root), as SYSTEM in the system profile.
92
+ * Plain root (a container, a server) is its own user and is fine.
93
+ */
94
+ export declare function installBlocker(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): 'sudo' | 'system' | null;
95
+ export interface Probe {
96
+ path: string;
97
+ version: string | null;
98
+ }
99
+ /** Where our scripts (or the vendor's standalone installer) put the runtime, checked when PATH doesn't have it. */
100
+ export declare function knownRuntimePaths(id: string, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): string[];
101
+ /** Every working copy: PATH order first (the first one is what a command runs), then the known places. */
102
+ export declare function probeRuntime(spec: ComponentSpec, platform?: NodeJS.Platform): Probe[];
103
+ /** Where the install script puts the runtime (fnm's default alias, ~/.local/bin, the per-user Git for Windows). */
104
+ export declare function scriptTarget(id: string, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): string | null;
105
+ /**
106
+ * Who looks after this copy, and whether vibecli may upgrade it in place: `auto` = one of our install scripts
107
+ * (or the vendor's standalone installer at the same place) manages it; otherwise a hint names the right command.
108
+ */
109
+ export interface RuntimeManager {
110
+ by: string;
111
+ auto: boolean;
112
+ /** The command that upgrades it, for copies vibecli leaves alone. */
113
+ hint?: string;
114
+ }
115
+ export declare function runtimeManager(id: string, path: string, platform?: NodeJS.Platform, env?: NodeJS.ProcessEnv): RuntimeManager;
116
+ /** The newest LTS in Node's dist index (newest first; `lts` is false or the line's codename). */
117
+ export declare function latestNodeLts(indexJson: string): string;
118
+ /** `key=value` lines of the devtools mirror's versions.txt; versions come back as x.y.z (`v2.56.0.windows.4` → 2.56.0). */
119
+ export declare function devtoolsVersion(text: string, key: string): string;
120
+ /** The newest version (cached an hour, failures ten minutes; `refresh` reads it again). Null when there is none to compare with. */
121
+ export declare function latestVersion(spec: ComponentSpec, options?: {
122
+ refresh?: boolean;
123
+ }): Promise<{
124
+ version: string | null;
125
+ error?: string;
126
+ }>;
127
+ export interface ComponentStatus {
128
+ id: string;
129
+ name: string;
130
+ category: ComponentCategory;
131
+ /** This platform has it at all (Claude Desktop / ChatGPT: macOS and Windows). */
132
+ supported: boolean;
133
+ installed: boolean;
134
+ current: string | null;
135
+ latest: string | null;
136
+ outdated: boolean;
137
+ path: string | null;
138
+ /** fnm / brew / native / npm …; null when not installed. */
139
+ managed_by: string | null;
140
+ /** vibecli upgrades it in place (`vibecli upgrade <id>`); false = `hint` names how. */
141
+ auto_upgrade: boolean;
142
+ hint?: string;
143
+ source: string;
144
+ /** Why vibecli can't install it here (sudo, SYSTEM, a desktop app's blocker), null when it can. */
145
+ blocker: string | null;
146
+ /** The latest version doesn't run on this machine; this is the newest that does (found by the last upgrade). */
147
+ capped_at?: string;
148
+ error?: string;
149
+ }
150
+ export type LatestInfo = {
151
+ version: string | null;
152
+ error?: string;
153
+ };
154
+ /**
155
+ * Latest versions of several components at once, before any local probing: `list` checks them all, and on
156
+ * Windows the probes (PowerShell for app versions and processes) are synchronous, so fetches left running
157
+ * across them hit their timeouts. Pass the result to `componentStatus({ latest })`.
158
+ */
159
+ export declare function prefetchLatest(specs: ComponentSpec[], options?: {
160
+ refresh?: boolean;
161
+ }): Promise<Map<string, LatestInfo>>;
162
+ export declare function componentStatus(spec: ComponentSpec, options?: {
163
+ refresh?: boolean;
164
+ offline?: boolean;
165
+ latest?: LatestInfo;
166
+ }): Promise<ComponentStatus>;
167
+ /** Globally installed npm packages of the Node at `nodePath` (fnm keeps them per Node version). */
168
+ export declare function globalPackages(nodePath: string, platform?: NodeJS.Platform): Array<{
169
+ name: string;
170
+ version: string;
171
+ }>;
172
+ export type ComponentOutcome = 'installed' | 'upgraded' | 'reinstalled' | 'up_to_date' | 'already_installed' | 'manual' | 'unavailable' | 'failed';
173
+ export interface ComponentResult {
174
+ tool: string;
175
+ category: ComponentCategory;
176
+ current: string | null;
177
+ latest: string | null;
178
+ result: ComponentOutcome;
179
+ version_after: string | null;
180
+ ok: boolean;
181
+ reason?: string;
182
+ hint?: string;
183
+ }
184
+ /**
185
+ * Install (`mode: 'install'`) or upgrade a runtime. Installing one that is there only reports it (`--force`
186
+ * reinstalls); upgrading one another manager owns names that manager's command (`--force` installs ours).
187
+ */
188
+ export declare function runRuntime(spec: ComponentSpec, mode: 'install' | 'upgrade', options?: {
189
+ force?: boolean;
190
+ }): Promise<ComponentResult>;
191
+ /** "x a → b: run …" for an outdated copy. */
192
+ export declare function upgradeAdvice(status: ComponentStatus): string;
193
+ export declare function setComponentDepsForTests(deps: {
194
+ platform?: NodeJS.Platform | null;
195
+ uid?: number | null;
196
+ systemPaths?: boolean;
197
+ registryPath?: () => string[];
198
+ versionRunner?: (path: string) => string;
199
+ macDeveloperTools?: () => boolean;
200
+ fetchText?: (url: string) => Promise<string>;
201
+ scriptRunner?: (url: string, env: Record<string, string>) => Promise<boolean>;
202
+ commandRunner?: (command: string, args: string[]) => Promise<boolean>;
203
+ }): void;
204
+ export {};
@@ -0,0 +1 @@
1
+ import{spawnSync as t}from"child_process";import{existsSync as e,mkdirSync as n,readFileSync as o,readdirSync as r,realpathSync as s,statSync as a,writeFileSync as i}from"fs";import{delimiter as l,dirname as c,isAbsolute as u,join as p,sep as d}from"path";import{fileURLToPath as m}from"url";import f from"chalk";import{brand as g,CLI_NAME as h}from"./brand.js";import{configManager as w}from"./config.js";import{i18n as v}from"./i18n.js";import{appLatest as y,appStatus as b,appStatusWith as $,cachedAppStatus as _,getApp as P}from"./app-update.js";import{compareVersions as x}from"./model-registry.js";import{isJsonMode as A}from"./runtime-options.js";import{track as k}from"./telemetry.js";import{runWatched as S}from"./tool-upgrade.js";import{getAdapter as D}from"./tools/registry.js";import{findInstallsIncludingNative as L,forgetInstalls as j,NPM_MIRROR as E,npmMirrorVersion as R}from"./tools/tool-installs.js";import{toolHome as C}from"./tools/types.js";import{logger as T}from"../utils/logger.js";let M=null;export function parseManifest(t){const e=JSON.parse(t);if(1!==e.schema||!Array.isArray(e.components))throw new Error("components.json: schema must be 1 with a components list");const n=new Set;for(const t of e.components){if(!t.id||!t.name||!["runtime","cli","app"].includes(t.category))throw new Error(`components.json: bad entry ${JSON.stringify(t.id)}`);for(const e of[t.id,...t.aliases??[]]){if(n.has(e))throw new Error(`components.json: duplicate name ${e}`);n.add(e)}if("runtime"===t.category&&(!t.detect||"script"!==t.install.type))throw new Error(`components.json: runtime ${t.id} needs detect + a script`);if("cli"===t.category&&!t.adapter)throw new Error(`components.json: cli ${t.id} needs an adapter`);if("app"===t.category&&!t.app)throw new Error(`components.json: app ${t.id} needs an app id`)}for(const t of e.components)for(const e of t.requires)if(!n.has(e))throw new Error(`components.json: ${t.id} requires unknown ${e}`);return e}function N(){return M||(M=parseManifest(o(function(){const t=c(m(import.meta.url)),n=[p(t,"..","..","components.json"),p(t,"..","components.json"),p(t,"components.json")],o=n.find(t=>e(t));if(!o)throw new Error(`components.json not found, searched: ${n.join(", ")}`);return o}(),"utf-8"))),M}export function setComponentManifestForTests(t){M=null===t?null:parseManifest(t)}export function listComponents(){return N().components}export function resolveComponent(t){const e=t.trim().toLowerCase();return listComponents().find(t=>t.id===e||(t.aliases??[]).includes(e))}let O=null;export function componentPlatform(){return O??process.platform}export function expandUrl(t){const e=g().installMirror??{};let n=!1;const o=t.replace(/\{(scripts|agy|devtools)\}/g,(t,o)=>{const r="devtools"===o?N().devtools:e[o];return r||(n=!0),r??""});return n?null:o}export function componentSource(t,e=componentPlatform()){return(t.sources[e]??t.sources.all??"").replace(/\{(scripts|agy|devtools)\}/g,(t,e)=>("devtools"===e?N().devtools:g().installMirror?.[e])??t)}export function installBlocker(t=process.env,e=componentPlatform()){return"win32"===e?/\\system32\\config\\systemprofile/i.test(t.USERPROFILE??"")||t.USERNAME?.endsWith("$")?"system":null:0===(U??process.getuid?.())&&t.SUDO_USER&&"root"!==t.SUDO_USER?"sudo":null}let U=null,I=!0,B=e=>{const n=/\.(cmd|bat)$/i.test(e)?t(`"${e}" --version`,{encoding:"utf-8",timeout:1e4,windowsHide:!0,shell:!0,stdio:["ignore","pipe","pipe"]}):t(e,["--version"],{encoding:"utf-8",timeout:1e4,windowsHide:!0,stdio:["ignore","pipe","pipe"]});return`${n.stdout??""}${n.stderr??""}`},F=()=>0===t("xcode-select",["-p"],{stdio:"ignore",timeout:1e4}).status;function V(t){try{return s(t)}catch{return t}}function H(t,e){return"win32"===e?[`${t}.exe`,`${t}.cmd`]:[t]}export function knownRuntimePaths(t,e=componentPlatform(),n=process.env){const o=C();if("node"===t){if("win32"===e){const t=n.FNM_DIR||p(n.APPDATA||p(o,"AppData","Roaming"),"fnm");return[p(t,"aliases","default","node.exe"),p(n.LOCALAPPDATA||p(o,"AppData","Local"),"Programs","nodejs","node.exe")]}return[p(W(e,n),"aliases","default","bin","node"),"/opt/homebrew/bin/node","/usr/local/bin/node","/usr/bin/node"]}if("uv"===t)return"win32"===e?[p(o,".local","bin","uv.exe"),p(o,".cargo","bin","uv.exe")]:[p(n.UV_INSTALL_DIR||p(o,".local","bin"),"uv"),p(o,".cargo","bin","uv"),"/opt/homebrew/bin/uv","/usr/local/bin/uv"];if("git"===t){if("win32"===e){const t=n.LOCALAPPDATA||p(o,"AppData","Local");return[p(t,"Programs","Git","cmd","git.exe"),p(n.ProgramFiles||"C:\\Program Files","Git","cmd","git.exe")]}return["/opt/homebrew/bin/git","/usr/local/bin/git","/usr/bin/git"]}return[]}function W(t,n){if(n.FNM_DIR)return n.FNM_DIR;const o=C();if("win32"===t)return p(n.APPDATA||p(o,"AppData","Roaming"),"fnm");const r=p(o,".local","share","fnm"),s=p(o,"Library","Application Support","fnm");return"darwin"===t&&!e(r)&&e(s)?s:r}let G=()=>{const e=e=>{const n=`${t("reg",["query",e,"/v","Path"],{encoding:"utf-8",timeout:1e4,windowsHide:!0,stdio:["ignore","pipe","ignore"]}).stdout??""}`.split(/\r?\n/).find(t=>/\sREG_(EXPAND_)?SZ\s/.test(t));return n?n.replace(/^.*?REG_(EXPAND_)?SZ\s+/,""):""};return[e("HKCU\\Environment"),e("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment")].flatMap(t=>{return(e=t,e.replace(/%([^%]+)%/g,(t,e)=>process.env[e]??t)).split(";");var e}).map(t=>t.trim().replace(/^"|"$/g,"")).filter(Boolean)},J=null;export function probeRuntime(t,e=componentPlatform()){const n=t.detect,o=new RegExp(n.pattern),r=new Set,s=[],i=n=>{if(!function(t){try{return a(t).isFile()}catch{return!1}}(n))return;if("darwin"===e&&"git"===t.id&&"/usr/bin/git"===n&&!F())return;const i=V(n),l="win32"===e?i.toLowerCase():i;if(r.has(l))return;r.add(l);const c=o.exec(B(n));c&&s.push({path:n,version:c[1]??null})},c=(process.env.PATH??"").split("win32"===e?";":l);"win32"===e&&c.push(...J??=z(()=>G())??[]);for(const t of c)if(t&&u(t))for(const o of H(n.command,e))i(p(t,o));for(const n of knownRuntimePaths(t.id,e))(I||n.startsWith(C()))&&i(n);return s}export function scriptTarget(t,e=componentPlatform(),n=process.env){const o=C();return"node"===t?"win32"===e?p(W(e,n),"aliases","default","node.exe"):p(W(e,n),"aliases","default","bin","node"):"uv"===t?p(n.UV_INSTALL_DIR||p(o,".local","bin"),"win32"===e?"uv.exe":"uv"):"git"===t&&"win32"===e?p(n.LOCALAPPDATA||p(o,"AppData","Local"),"Programs","Git","cmd","git.exe"):null}export function runtimeManager(t,e,n=componentPlatform(),o=process.env){const r=V(e),s=t=>"win32"===n?t.replace(/\//g,"\\").toLowerCase():t,a=s(r),i=s(e),l="win32"===n?"\\":"\\"===d?"/":d,u=t=>a.includes(s(t))||i.includes(s(t)),m=t=>a.startsWith(s(t)+l)||i.startsWith(s(t)+l),f=C(),g=/[\\/](Cellar|Caskroom|homebrew)[\\/]/i.test(r)||/^\/opt\/homebrew\//.test(e)||/^\/usr\/local\/(Cellar|opt)\//.test(r);if("node"===t)return u(`${l}fnm${l}`)||m(W(n,o))?{by:"fnm",auto:!0}:g?{by:"brew",auto:!1,hint:"brew upgrade node"}:u(`${l}.nvm${l}`)?{by:"nvm",auto:!1,hint:"nvm install --lts --reinstall-packages-from=current"}:u(`${l}nvm${l}`)&&"win32"===n?{by:"nvm",auto:!1,hint:"nvm install lts"}:u(`${l}.volta${l}`)||u(`${l}volta${l}`)?{by:"volta",auto:!1,hint:"volta install node@lts"}:u(`${l}scoop${l}`)?{by:"scoop",auto:!1,hint:"scoop update nodejs-lts"}:u(`${l}.asdf${l}`)?{by:"asdf",auto:!1,hint:"asdf install nodejs latest"}:u(`${l}mise${l}`)?{by:"mise",auto:!1,hint:"mise use -g node@lts"}:{by:"system",auto:!1};if("uv"===t)return[o.UV_INSTALL_DIR,p(f,".local","bin"),p(f,".cargo","bin")].filter(Boolean).some(t=>s(c(e))===s(t)||s(c(r))===s(t))?{by:"standalone",auto:!0}:g?{by:"brew",auto:!1,hint:"brew upgrade uv"}:u("pipx")?{by:"pipx",auto:!1,hint:"pipx upgrade uv"}:u(`${l}scoop${l}`)?{by:"scoop",auto:!1,hint:"scoop update uv"}:u("winget")?{by:"winget",auto:!1,hint:"winget upgrade astral-sh.uv"}:u("site-packages")||u(`${l}python`)?{by:"pip",auto:!1,hint:"pip install -U uv"}:{by:"other",auto:!1};if("git"===t){if("win32"===n){const t=o.LOCALAPPDATA||p(f,"AppData","Local");return m(p(t,"Programs","Git"))?{by:"git-for-windows-user",auto:!0}:u(`${l}scoop${l}`)?{by:"scoop",auto:!1,hint:"scoop update git"}:{by:"git-for-windows",auto:!1,hint:"git update-git-for-windows"}}return g?{by:"brew",auto:!1,hint:"brew upgrade git"}:"darwin"===n?{by:"xcode",auto:!1,hint:"softwareupdate --list"}:{by:"system",auto:!1}}return{by:"other",auto:!1}}function q(){return p(w.getConfigDir(),"component-releases.json")}function K(){try{const t=JSON.parse(o(q(),"utf-8"));return t&&"object"==typeof t?t:{}}catch{return{}}}function X(t,e){try{n(w.getConfigDir(),{recursive:!0}),i(q(),JSON.stringify({...K(),[t]:e},null,2))}catch(t){T.logError("components.writeCache",t)}}let Z=async(t,e=15e3)=>{const n=await fetch(t,{signal:AbortSignal.timeout(e)});if(!n.ok)throw new Error(`HTTP ${n.status} ${t}`);return n.text()};const Y=/(\d+\.\d+\.\d+)/;export function latestNodeLts(t){const e=JSON.parse(t).find(t=>"string"==typeof t.lts&&t.lts&&"string"==typeof t.version);if(!e)throw new Error("no LTS release in the Node index");return e.version.replace(/^v/,"")}export function devtoolsVersion(t,e){const n=t.split(/\r?\n/).find(t=>t.startsWith(`${e}=`)),o=n?Y.exec(n.slice(e.length+1)):null;if(!o)throw new Error(`${e} is not in versions.txt`);return o[1]}export async function latestVersion(t,e={}){const n=componentPlatform(),o=`${t.id}:${n}`,r=K()[o],s=r&&Date.now()-r.checked_at<(r.version?36e5:6e5);if(r&&s&&!e.refresh)return{version:r.version??null,...r.error?{error:r.error}:{}};try{const e=await async function(t,e){const n=t.latest;switch(n.type){case"node-lts":return latestNodeLts(await Z(n.url));case"devtools":return n.platforms&&!n.platforms.includes(e)?null:devtoolsVersion(await Z(`${N().devtools}/versions.txt`),n.key);case"npm":{if(n.staleTag){const e=D(t.adapter??"")?.installSpec,n=e?await R(e):"latest";if("latest"!==n)return n}const e=JSON.parse(await Z(`${E}/${n.package}/latest`));if(!e.version)throw new Error(`no version for ${n.package}`);return e.version}case"version-file":{const t=expandUrl(n.url);if(!t)return null;const e=Y.exec(await Z(t));if(!e)throw new Error(`no version in ${t}`);return e[1]}default:return null}}(t,n);return e&&X(o,{version:e,checked_at:Date.now()}),{version:e}}catch(e){const n=e instanceof Error?e.message:String(e);return T.logError(`components.latest(${t.id})`,e),X(o,{error:n,checked_at:Date.now()}),{version:r?.version??null,error:n}}}export async function prefetchLatest(t,e={}){const n=componentPlatform(),o=await Promise.all(t.map(async t=>t.platforms.includes(n)?"app"===t.category?[t.id,P(t.app)?await y(t.app,e):{version:null}]:[t.id,await latestVersion(t,e)]:[t.id,{version:null}]));return new Map(o)}export async function componentStatus(t,e={}){const n=componentPlatform(),o={id:t.id,name:t.name,category:t.category,supported:t.platforms.includes(n),source:componentSource(t,n)},r={...o,installed:!1,current:null,latest:null,outdated:!1,path:null,managed_by:null,auto_upgrade:!1,blocker:o.supported?installBlocker():"unsupported_platform"};if(!o.supported)return r;if("app"===t.category){const n=P(t.app);if(!n)return{...r,blocker:"unsupported_platform"};const o=e.latest?$(t.app,e.latest):e.offline?_(t.app):await b(t.app,{refresh:e.refresh});return{...r,installed:o.installed,current:o.current,latest:o.latest,outdated:o.outdated,path:o.installed?z(()=>n.appInstaller.installedPath()):null,managed_by:o.installed?"app":null,auto_upgrade:o.installed&&!o.blocker,blocker:o.blocker??installBlocker(),...o.error?{error:o.error}:{}}}let s=null,a=null,i=null;if("cli"===t.category){const e=D(t.adapter)?.installSpec,n=e?L(e)[0]:void 0;n&&(s=n.version,a=n.path,i={by:n.kind,auto:!0})}else{const e=probeRuntime(t,n)[0];e&&(s=e.version,a=e.path,i=runtimeManager(t.id,e.path,n))}const{version:l,error:c}=e.latest?e.latest:e.offline?{version:K()[`${t.id}:${n}`]?.version??null,error:void 0}:await latestVersion(t,{refresh:e.refresh}),u=l?K()[`${t.id}:${n}:ceiling`]:void 0,p=Boolean(u?.version&&u.error===l&&s&&x(s,u.version)>=0);return{...r,installed:Boolean(a),current:s,latest:l,outdated:Boolean(s&&l&&x(s,l)<0&&!p),...p?{capped_at:u.version}:{},path:a,managed_by:i?.by??null,auto_upgrade:i?.auto??!1,...i?.hint?{hint:i.hint}:{},...c?{error:c}:{}}}function z(t){try{return t()}catch{return null}}const Q={idleMs:18e4,timeoutMs:36e5};let tt=async(t,e)=>{const n={...Q,env:e};return"ok"===("win32"===componentPlatform()?await S("powershell.exe",["-NoProfile","-ExecutionPolicy","Bypass","-Command",`irm ${t} -TimeoutSec 30 | iex`],n):await S("sh",["-c",'script=$(curl -fsSL --connect-timeout 15 --max-time 60 "$0") && printf \'%s\\n\' "$script" | sh',t],n))},et=async(t,e)=>"ok"===await S(t,e,Q);function nt(t){A()?process.stderr.write(t+"\n"):console.log(t)}export function globalPackages(t,e=componentPlatform()){const n=c(V(t)),s="win32"===e?p(n,"node_modules"):p(c(n),"lib","node_modules"),a=[],i=(t,e)=>{try{const n=JSON.parse(o(p(t,"package.json"),"utf-8"));n.version&&a.push({name:e,version:n.version})}catch{}};let l=[];try{l=r(s)}catch{return a}for(const t of l)if(!t.startsWith(".")&&"npm"!==t&&"corepack"!==t)if(t.startsWith("@"))try{for(const e of r(p(s,t)))e.startsWith(".")||i(p(s,t,e),`${t}/${e}`)}catch{}else i(p(s,t),t);return a}function ot(t,e,n={}){const o=["installed","upgraded","reinstalled","up_to_date","already_installed"].includes(e);return{tool:t.id,category:t.category,current:t.current,latest:t.latest,result:e,version_after:null,ok:o,...n}}function rt(t){return`${h} upgrade ${t}`}export async function runRuntime(t,e,n={}){const o=Date.now(),r=componentPlatform(),s=await componentStatus(t,{refresh:!0}),a=t.name;if(!s.supported)return nt(f.yellow(" ! "+v.t("components.unsupported_platform",{tool:a}))),ot(s,"unavailable",{reason:"unsupported_platform"});if(s.blocker)return nt(f.yellow(" ! "+function(t,e){return v.t(`components.blocked_${t}`,{tool:e})}(s.blocker,a))),ot(s,"unavailable",{reason:s.blocker});const i=function(t){if("script"!==t.install.type)return null;const e=g().installMirror?.scripts;return e?`${e}/${t.install.script}.${"win32"===componentPlatform()?"ps1":"sh"}`:null}(t);if(s.installed&&!n.force){const n=f.gray(`(${s.path})`);if(s.latest&&!s.outdated)return nt(f.green(" √ "+v.t("components.up_to_date",{tool:a,current:s.current??"?"}))+n),"upgrade"===e&&nt(f.gray(" "+v.t("components.force_hint",{command:`${rt(t.id)} --force`}))),ot(s,"install"===e?"already_installed":"up_to_date");if("install"===e)return nt(f.green(" √ "+v.t("components.already_installed",{tool:a,current:s.current??"?"}))+n),s.outdated&&nt(f.yellow(" ! "+upgradeAdvice(s))),ot(s,"already_installed",s.outdated?{hint:upgradeAdvice(s)}:{});if(!s.latest)return nt(f.yellow(" ! "+v.t("components.no_latest",{tool:a,current:s.current??"?",how:at(s)}))),ot(s,"manual",{reason:s.error??"no_latest",hint:s.hint});if(!s.auto_upgrade)return nt(f.yellow(" ! "+upgradeAdvice(s))),ot(s,"manual",{hint:s.hint})}if(!i)return nt(f.yellow(" ! "+v.t("components.no_mirror",{tool:a}))),ot(s,"unavailable",{reason:"no_mirror"});s.installed&&!s.auto_upgrade&&nt(f.yellow(" ! "+v.t("components.force_beside",{tool:a,path:s.path??"?",by:s.managed_by??"?"})));const u={};s.installed&&"script"===t.install.type&&Object.assign(u,t.install.forceEnv??{}),"uv"===t.id&&s.installed&&s.auto_upgrade&&s.path&&(u.UV_INSTALL_DIR=c(s.path));const d=t.upgrade?.migrateGlobalPackages&&s.installed&&"fnm"===s.managed_by&&s.path,m=d?globalPackages(s.path,r):[],h=d?V(s.path):null;nt(f.gray(" "+v.t(s.installed?"components.upgrading":"components.installing",{tool:a,latest:s.latest??"",source:s.source}))),k(`component:${t.id}`,"start",{detail:{mode:e,force:Boolean(n.force),current:s.current}});const w=await tt(i,u);j(),J=null;const y=probeRuntime(t,r),b=scriptTarget(t.id,r,{...process.env,...u}),$=(b?y.find(t=>{return e=t.path,n=b,"win32"===r?V(e).toLowerCase()===V(n).toLowerCase():V(e)===V(n);var e,n}):void 0)??y.find(e=>runtimeManager(t.id,e.path,r).auto)??y[0];if(!w||!$)return k(`component:${t.id}`,"fail",{duration_ms:Date.now()-o}),nt(f.red(" × "+v.t("components.script_failed",{tool:a,command:(s.installed?rt:st)(t.id)}))),ot(s,"failed",{reason:w?"not_found_after":"script_failed"});d&&m.length&&h&&V($.path)!==h&&await async function(t,e,n){const o=new Set(globalPackages(e,n).map(t=>t.name)),r=t.filter(t=>!o.has(t.name));if(!r.length)return;nt(f.gray(" "+v.t("components.migrating_globals",{packages:r.map(t=>t.name).join("、")})));const s=function(t,e){const n=c(V(t));return p(n,"win32"===e?"npm.cmd":"npm")}(e,n),a=["install","-g",...r.map(t=>`${t.name}@${t.version}`),`--registry=${E}`,"--no-fund","--no-audit","--loglevel=error"];await et(s,a)||nt(f.yellow(" ! "+v.t("components.migrate_failed",{command:[s,...a].join(" ")})))}(m,$.path,r);const _=y[0];_&&_.path!==$.path&&_.version&&$.version&&x(_.version,$.version)<0&&nt(f.yellow(" ! "+v.t("components.path_shadowed",{tool:a,active:_.path,version:$.version,path:$.path}))),s.latest&&$.version&&x($.version,s.latest)<0&&(X(`${t.id}:${r}:ceiling`,{version:$.version,error:s.latest,checked_at:Date.now()}),nt(f.yellow(" ! "+v.t("components.capped",{tool:a,latest:s.latest,version:$.version})))),k(`component:${t.id}`,"ok",{duration_ms:Date.now()-o,detail:{version:$.version}});const P=s.installed?n.force&&!s.outdated?"reinstalled":"upgraded":"installed";var A;return nt(f.green(" √ "+("installed"===P?v.t("components.installed",{tool:a,version:$.version??"?"}):"reinstalled"===P?v.t("components.reinstalled",{tool:a,version:$.version??"?"}):v.t("components.upgraded",{tool:a,from:s.current??"?",to:$.version??"?"})))+f.gray(`(${$.path})`)),A=c($.path),(process.env.PATH??"").split("win32"===componentPlatform()?";":l).some(t=>"win32"===componentPlatform()?t.toLowerCase()===A.toLowerCase():t===A)||nt(f.gray(" "+v.t("components.new_terminal"))),ot(s,P,{version_after:$.version})}function st(t){return`${h} install ${t}`}function at(t){return t.hint?t.hint:v.t(`components.how_${t.managed_by??"other"}`,{tool:t.name})}export function upgradeAdvice(t){return t.auto_upgrade?v.t("components.outdated_auto",{tool:t.name,current:t.current??"?",latest:t.latest??"?",command:rt(t.id)}):v.t("components.outdated_manual",{tool:t.name,current:t.current??"?",latest:t.latest??"?",by:t.managed_by??"?",how:at(t),command:`${rt(t.id)} --force`})}export function setComponentDepsForTests(t){void 0!==t.platform&&(O=t.platform),void 0!==t.uid&&(U=t.uid),void 0!==t.systemPaths&&(I=t.systemPaths),t.registryPath&&(G=t.registryPath,J=null),t.versionRunner&&(B=t.versionRunner),t.macDeveloperTools&&(F=t.macDeveloperTools),t.fetchText&&(Z=t.fetchText),t.scriptRunner&&(tt=t.scriptRunner),t.commandRunner&&(et=t.commandRunner)}
@@ -139,3 +139,5 @@ export declare class ConfigManager {
139
139
  resetBaseUrl(): void;
140
140
  }
141
141
  export declare const configManager: ConfigManager;
142
+ /** Every API key vibecli has saved (all profiles, default and tool-specific). */
143
+ export declare function savedApiKeys(): string[];
@@ -1 +1 @@
1
- import{existsSync as e,mkdirSync as t,readFileSync as i,writeFileSync as o,renameSync as s,unlinkSync as r,readdirSync as n,rmdirSync as a,statSync as l,chmodSync as c}from"fs";import{join as f,dirname as g}from"path";import{homedir as h}from"os";import{createHash as p}from"crypto";import*as m from"js-yaml";import{logger as u}from"../utils/logger.js";import{getDefaultModels as d,isRetiredDefaultModel as y}from"./model-registry.js";import{readLegacySecretsFile as _}from"./secret-store.js";import{brand as k,brandEnv as b}from"./brand.js";export const DEFAULT_BASE_URL=k().gatewayAddresses[0].url;export const DEFAULT_MODELS=d();export const DEFAULT_PROFILE_NAME="default";const v="plaintext";export class ConfigManager{static instance;configDir;configPath;config;runtimeBaseUrl;constructor(){this.configDir=this.resolveConfigDir(),this.migrateLegacyConfigDir(),this.configPath=f(this.configDir,"config.yaml"),this.config=this.loadConfig()}migrateLegacyConfigDir(){try{const t=f(g(this.configDir),".chelper");if(t===this.configDir||!e(t))return;if(["config.yaml","secrets.enc"].some(t=>e(f(this.configDir,t))))return;if(e(this.configDir)){for(const i of n(t)){const o=f(this.configDir,i);e(o)||s(f(t,i),o)}0===n(t).length&&a(t)}else s(t,this.configDir);u.record(`Migrated config dir: ${t} -> ${this.configDir}`)}catch(e){u.warn("Failed to migrate legacy .chelper config dir:",e)}}resolveConfigDir(){const e=b("CONFIG_DIR");if(e&&e.trim())return e.trim();const t=b("TEST_HOME");return t&&t.trim()?f(t.trim(),k().configDirName):f(h(),k().configDirName)}createDefaultConfig(){return this.syncLegacyFields({lang:"en_US",active_profile:"default",profiles:{[DEFAULT_PROFILE_NAME]:{}},presets:{}})}normalizeModels(e){const t=t=>{const i=e?.[t]?.trim();return i&&!y(t,i)?i:DEFAULT_MODELS[t]},i={haiku:t("haiku"),sonnet:t("sonnet"),opus:t("opus"),reasoning:t("reasoning")};if(e&&"object"==typeof e)for(const[t,o]of Object.entries(e))t in i||"string"==typeof o&&o.trim()&&(i[t]=o.trim());return i}normalizeToolModels(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||"string"!=typeof e||!t||"object"!=typeof t)return[];const i={};for(const[e,o]of Object.entries(t))"string"==typeof o&&o.trim()&&(i[e]=o.trim());return Object.keys(i).length>0?[[e,i]]:[]});return Object.fromEntries(t)}normalizeToolSettings(e){if(!e||"object"!=typeof e||Array.isArray(e))return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||!t||"object"!=typeof t||Array.isArray(t))return[];const i=Object.fromEntries(Object.entries(t).filter(([,e])=>"string"==typeof e||"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e)));return Object.keys(i).length>0?[[e,i]]:[]});return Object.fromEntries(t)}normalizeStringRecord(e){return!e||"object"!=typeof e||Array.isArray(e)?{}:Object.fromEntries(Object.entries(e).filter(e=>"string"==typeof e[1]&&Boolean(e[1].trim())).map(([e,t])=>[e,t.trim()]))}normalizeProfile(e){if(!e)return{};const t=this.normalizeToolModels(e.tool_models),i=this.normalizeToolSettings(e.tool_settings),o=this.normalizeStringRecord(e.tool_api_keys);return{..."string"==typeof e.api_key&&e.api_key?{api_key:e.api_key}:{},...Object.keys(o).length>0?{tool_api_keys:o}:{},..."string"==typeof e.base_url&&e.base_url?{base_url:e.base_url}:{},...e.models?{models:this.normalizeModels(e.models)}:{},...Object.keys(t).length>0?{tool_models:t}:{},...Object.keys(i).length>0?{tool_settings:i}:{}}}normalizeProfiles(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>e&&"string"==typeof e&&t&&"object"==typeof t?[[e,this.normalizeProfile(t)]]:[]);return Object.fromEntries(t)}normalizePresets(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||"string"!=typeof e||!t||"object"!=typeof t)return[];const i=t;return i.models?[[e,{models:this.normalizeModels(i.models),updated_at:i.updated_at||new Date(0).toISOString()}]]:[]});return Object.fromEntries(t)}getResolvedActiveProfile(e){const t=e.active_profile||"default";return e.profiles?.[t]||{}}syncLegacyFields(e){const t=this.normalizeProfiles(e.profiles),i=e.active_profile?.trim()||Object.keys(t)[0]||"default",o=t[i]?t:{...t,[i]:{}},s=this.getResolvedActiveProfile({...e,profiles:o,active_profile:i});return{...e,lang:e.lang||"en_US",active_profile:i,profiles:o,presets:this.normalizePresets(e.presets),api_key:s.api_key,base_url:s.base_url,models:s.models?this.normalizeModels(s.models):DEFAULT_MODELS}}sanitizeBackupLabel(e){return(e||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)}getBackupDir(){return f(this.configDir,"backups")}getLegacySecretsPath(){return f(this.configDir,"secrets.enc")}getSecretsBackupPath(e){return e.replace(/\.yaml$/,".secrets.enc")}static getInstance(){return ConfigManager.instance||(ConfigManager.instance=new ConfigManager),ConfigManager.instance}ensureConfigDir(){e(this.configDir)||t(this.configDir,{recursive:!0})}loadConfig(){try{if(e(this.configPath)){const t=i(this.configPath,"utf-8"),o=m.load(t);let s=this.migrateOldConfig(o||{lang:"en_US"}),n=!1;const a=this.getLegacySecretsPath();if(e(a)&&o?.credential_store!==v)try{s=this.mergeLegacySecrets(s,_(a)),n=!0}catch(e){u.warn("Legacy secrets.enc is unreadable on this host; it was kept for recovery and will not block new credentials."),u.logError("ConfigManager.migrateLegacySecrets",e)}if((this.needsMigration(o)||n)&&(this.backupConfig(),this.atomicWrite(this.configPath,m.dump(this.prepareForPersistence(s))),n))try{r(a)}catch(e){u.logError("ConfigManager.removeLegacySecrets",e)}try{c(this.configPath,384)}catch{}return s}const t=this.getLegacySecretsPath();if(e(t))try{const e=this.mergeLegacySecrets(this.createDefaultConfig(),_(t));this.ensureConfigDir(),this.atomicWrite(this.configPath,m.dump(this.prepareForPersistence(e)));try{r(t)}catch(e){u.logError("ConfigManager.removeLegacySecrets",e)}return e}catch(e){u.warn("Legacy secrets.enc is unreadable on this host; it was kept for recovery and will not block new credentials."),u.logError("ConfigManager.migrateLegacySecretsWithoutConfig",e)}}catch(e){u.warn("Failed to load config, using defaults:",e),u.logError("ConfigManager.loadConfig",e)}return this.createDefaultConfig()}needsMigration(e){return!(!e||"object"!=typeof e)&&("plan"in e||!("profiles"in e)||!("active_profile"in e)||!("presets"in e))}migrateOldConfig(e){if(!e||"object"!=typeof e)return this.createDefaultConfig();const t=e,{plan:i,...o}=t,s=this.normalizeProfiles(o.profiles),r=this.normalizeProfile({api_key:"string"==typeof o.api_key?o.api_key:void 0,base_url:"string"==typeof o.base_url?o.base_url:void 0,models:o.models}),n=Object.keys(s).length>0?s:{[DEFAULT_PROFILE_NAME]:r},a="string"==typeof o.active_profile&&o.active_profile.trim()?o.active_profile.trim():Object.keys(n)[0]||"default";return this.syncLegacyFields({lang:"string"==typeof o.lang&&o.lang?o.lang:"en_US",...!0===o.lang_chosen?{lang_chosen:!0}:{},...o.credential_store===v?{credential_store:v}:{},active_profile:a,profiles:n,presets:this.normalizePresets(o.presets)})}atomicWrite(t,i){const n=`${t}.tmp`;try{o(n,i,{encoding:"utf-8",mode:384}),s(n,t);try{c(t,384)}catch{}}catch(t){if(e(n))try{r(n)}catch{}throw t}}backupConfig(){if(!e(this.configPath))return;const s=this.getBackupDir();e(s)||t(s,{recursive:!0});const r=(new Date).toISOString().replace(/[:.]/g,"-"),n=f(s,`config-${r}.yaml`);try{const e=i(this.configPath,"utf-8");o(n,e,{encoding:"utf-8",mode:384}),this.rotateBackups(s,10,30)}catch(e){u.logError("ConfigManager.backupConfig",e)}}rotateBackups(t,i,o){try{const s=Date.now()-24*o*60*60*1e3,a=n(t).filter(e=>e.startsWith("config-")&&e.endsWith(".yaml")).map(e=>({name:e,path:f(t,e),mtime:l(f(t,e)).mtime})).sort((e,t)=>t.mtime.getTime()-e.mtime.getTime()),c=new Set;a.slice(i).forEach(e=>c.add(e.path)),a.forEach(e=>{e.mtime.getTime()<s&&c.add(e.path)});for(const t of c){let i=!1;try{r(t),i=!0}catch(e){u.logError("ConfigManager.rotateBackups",e)}if(i){const i=this.getSecretsBackupPath(t);if(e(i))try{r(i)}catch(e){u.logError("ConfigManager.rotateBackups.secrets",e)}}}}catch(e){u.logError("ConfigManager.rotateBackups",e)}}saveConfig(e){try{this.ensureConfigDir(),this.backupConfig();const t=this.syncLegacyFields(this.migrateOldConfig(e||this.config)),i=this.prepareForPersistence(t),o=m.dump(i);this.atomicWrite(this.configPath,o),this.config=t}catch(e){throw u.error("Failed to save config:",e),u.logError("ConfigManager.saveConfig",e),e}}prepareForPersistence(e){const t={...e,credential_store:v};return delete t.api_key,delete t.base_url,delete t.models,t}mergeLegacySecrets(e,t){const i={...e.profiles||{}},o=new Set([...Object.keys(t.api_keys||{}),...Object.keys(t.tool_api_keys||{})]);for(const e of o){const o=i[e]||{},s=o.api_key||t.api_keys?.[e],r={...t.tool_api_keys?.[e]||{},...o.tool_api_keys||{}};i[e]={...o,...s?{api_key:s}:{},...Object.keys(r).length>0?{tool_api_keys:r}:{}}}return this.syncLegacyFields({...e,profiles:i})}getConfig(){return this.syncLegacyFields({...this.config})}updateConfig(e){this.saveConfig({...this.config,...e})}isFirstRun(){return!e(this.configPath)}getLang(){return this.config.lang||"en_US"}isLangChosen(){return!0===this.config.lang_chosen}setLang(e){this.updateConfig({lang:e,lang_chosen:!0})}getActiveProfileName(){return this.config.active_profile||"default"}listProfiles(){return Object.keys(this.config.profiles||{}).sort()}getProfile(e=this.getActiveProfileName()){const t=this.config.profiles?.[e]||{};return{...t.api_key?{api_key:t.api_key}:{},...t.tool_api_keys&&Object.keys(t.tool_api_keys).length>0?{tool_api_keys:this.normalizeStringRecord(t.tool_api_keys)}:{},...t.base_url?{base_url:t.base_url}:{},models:t.models?this.normalizeModels(t.models):DEFAULT_MODELS,...t.tool_models&&Object.keys(t.tool_models).length>0?{tool_models:this.normalizeToolModels(t.tool_models)}:{},...t.tool_settings&&Object.keys(t.tool_settings).length>0?{tool_settings:this.normalizeToolSettings(t.tool_settings)}:{}}}createProfile(e,t=this.getActiveProfileName()){const i=this.getProfile(t),o={...this.config.profiles||{},[e]:{...i.api_key?{api_key:i.api_key}:{},...i.base_url?{base_url:i.base_url}:{},models:this.normalizeModels(i.models)}};this.saveConfig({...this.config,profiles:o})}setActiveProfile(e){if(!this.config.profiles?.[e])throw new Error(`Profile not found: ${e}`);this.saveConfig({...this.config,active_profile:e})}updateProfile(e,t){const i=this.config.profiles?.[e]||{},o=t.models?this.normalizeModels({...i.models,...t.models}):i.models?this.normalizeModels(i.models):void 0,s={...i,...void 0!==t.api_key?{api_key:t.api_key}:{},...void 0!==t.base_url?{base_url:t.base_url}:{},...o?{models:o}:{}},r={...this.config.profiles||{},[e]:s};this.saveConfig({...this.config,profiles:r})}deleteProfile(e){const t={...this.config.profiles||{}};if(!t[e])return;const i=Object.keys(t).filter(t=>t!==e);if(0===i.length)throw new Error("At least one profile must remain");delete t[e];const o=this.getActiveProfileName()===e?i[0]:this.getActiveProfileName();this.saveConfig({...this.config,profiles:t,active_profile:o})}getApiKey(){return this.config.profiles?.[this.getActiveProfileName()]?.api_key}setApiKey(e){this.updateProfile(this.getActiveProfileName(),{api_key:e})}revokeApiKey(){const e=this.config.profiles?.[this.getActiveProfileName()]||{},{api_key:t,tool_api_keys:i,...o}=e,s={...this.config.profiles||{},[this.getActiveProfileName()]:o};this.saveConfig({...this.config,profiles:s})}getApiKeyFingerprint(){const e=this.getApiKey();return e?p("sha256").update(e).digest("hex").slice(0,12):""}getApiKeyForTool(e){const t=this.config.profiles?.[this.getActiveProfileName()];return t?.tool_api_keys?.[e]||t?.api_key||void 0}setToolApiKey(e,t){const i=this.getActiveProfileName(),o=this.config.profiles?.[i]||{},s={...this.config.profiles||{},[i]:{...o,tool_api_keys:{...o.tool_api_keys||{},[e]:t}}};this.saveConfig({...this.config,profiles:s})}clearToolApiKey(e){const t=this.getActiveProfileName(),i=this.config.profiles?.[t]||{},{[e]:o,...s}=i.tool_api_keys||{},r={...i};Object.keys(s).length>0?r.tool_api_keys=s:delete r.tool_api_keys,this.saveConfig({...this.config,profiles:{...this.config.profiles||{},[t]:r}})}listToolApiKeys(){return Object.keys(this.config.profiles?.[this.getActiveProfileName()]?.tool_api_keys||{}).sort()}getToolApiKeyFingerprint(e){const t=this.config.profiles?.[this.getActiveProfileName()]?.tool_api_keys?.[e];return t?p("sha256").update(t).digest("hex").slice(0,12):""}getModels(){const e=this.config.profiles?.[this.getActiveProfileName()]?.models;return e?this.normalizeModels(e):DEFAULT_MODELS}setModels(e){this.updateProfile(this.getActiveProfileName(),{models:this.normalizeModels(e)})}updateModels(e){this.setModels(this.normalizeModels({...this.getModels(),...e}))}getModelsForTool(e){const t=this.getModels(),i=this.config.profiles?.[this.getActiveProfileName()]?.tool_models?.[e];return i?this.normalizeModels({...t,...i}):t}setToolModel(e,t){const i=this.getActiveProfileName(),o=this.config.profiles?.[i]||{},s={...o.tool_models||{}};t&&0!==Object.keys(t).length?s[e]=t:delete s[e];const r={...o};Object.keys(s).length>0?r.tool_models=s:delete r.tool_models;const n={...this.config.profiles||{},[i]:r};this.saveConfig({...this.config,profiles:n})}getToolSettings(e){const t=this.config.profiles?.[this.getActiveProfileName()]?.tool_settings?.[e];return t?{...t}:{}}getToolSetting(e,t){return this.config.profiles?.[this.getActiveProfileName()]?.tool_settings?.[e]?.[t]}setToolSetting(e,t,i){if(this.getToolSetting(e,t)===i)return;const o=this.getActiveProfileName(),s=this.config.profiles?.[o]||{},r={...s.tool_settings||{}};r[e]={...r[e]||{},[t]:i};const n={...this.config.profiles||{},[o]:{...s,tool_settings:r}};this.saveConfig({...this.config,profiles:n})}getCustomPresets(){return{...this.config.presets||{}}}saveCustomPreset(e,t){const i={...this.config.presets||{},[e]:{models:this.normalizeModels(t),updated_at:(new Date).toISOString()}};this.saveConfig({...this.config,presets:i})}deleteCustomPreset(e){const t={...this.config.presets||{}};t[e]&&(delete t[e],this.saveConfig({...this.config,presets:t}))}listBackups(){const t=this.getBackupDir();return e(t)?n(t).filter(e=>e.startsWith("config-")&&e.endsWith(".yaml")).map(e=>{const i=f(t,e);return{name:e,path:i,mtimeMs:l(i).mtimeMs}}).sort((e,t)=>t.mtimeMs-e.mtimeMs):[]}createBackup(s){this.ensureConfigDir();const r=this.getBackupDir();e(r)||t(r,{recursive:!0});const n=(new Date).toISOString().replace(/[:.]/g,"-"),a=this.sanitizeBackupLabel(s),l=f(r,a?`config-${n}-${a}.yaml`:`config-${n}.yaml`),c=e(this.configPath)?i(this.configPath,"utf-8"):m.dump(this.prepareForPersistence(this.getConfig()));return o(l,c,{encoding:"utf-8",mode:384}),this.rotateBackups(r,10,30),l}restoreBackup(t){if(!t||"."===t||".."===t||/[/\\]|\.\./.test(t))throw new Error(`Invalid backup name: ${t}`);const o=this.getBackupDir(),s=f(o,t);if(!e(s))throw new Error(`Backup not found: ${t}`);const r=i(s,"utf-8"),n=m.load(r),a=this.getSecretsBackupPath(s);let l=this.migrateOldConfig(n||this.createDefaultConfig());if(n?.credential_store!==v&&e(a))try{l=this.mergeLegacySecrets(l,_(a))}catch(e){throw new Error(`Legacy backup credentials could not be decrypted: ${a}`,{cause:e})}this.saveConfig(l)}getConfigDir(){return this.configDir}getBaseUrl(){return this.runtimeBaseUrl||this.config.profiles?.[this.getActiveProfileName()]?.base_url||DEFAULT_BASE_URL}hasRuntimeBaseUrl(){return Boolean(this.runtimeBaseUrl)}setBaseUrl(e,t=!1){this.runtimeBaseUrl=e,t&&this.updateProfile(this.getActiveProfileName(),{base_url:e})}resetBaseUrl(){this.runtimeBaseUrl=void 0;const e=this.config.profiles?.[this.getActiveProfileName()]||{},{base_url:t,...i}=e,o={...this.config.profiles||{},[this.getActiveProfileName()]:i};this.saveConfig({...this.config,profiles:o})}}export const configManager=ConfigManager.getInstance();
1
+ import{existsSync as e,mkdirSync as t,readFileSync as i,writeFileSync as o,renameSync as s,unlinkSync as r,readdirSync as n,rmdirSync as a,statSync as c,chmodSync as l}from"fs";import{join as f,dirname as g}from"path";import{homedir as h}from"os";import{createHash as p}from"crypto";import*as m from"js-yaml";import{logger as u}from"../utils/logger.js";import{getDefaultModels as d,isRetiredDefaultModel as y}from"./model-registry.js";import{readLegacySecretsFile as _}from"./secret-store.js";import{brand as k,brandEnv as b}from"./brand.js";export const DEFAULT_BASE_URL=k().gatewayAddresses[0].url;export const DEFAULT_MODELS=d();export const DEFAULT_PROFILE_NAME="default";const v="plaintext";export class ConfigManager{static instance;configDir;configPath;config;runtimeBaseUrl;constructor(){this.configDir=this.resolveConfigDir(),this.migrateLegacyConfigDir(),this.configPath=f(this.configDir,"config.yaml"),this.config=this.loadConfig()}migrateLegacyConfigDir(){try{const t=f(g(this.configDir),".chelper");if(t===this.configDir||!e(t))return;if(["config.yaml","secrets.enc"].some(t=>e(f(this.configDir,t))))return;if(e(this.configDir)){for(const i of n(t)){const o=f(this.configDir,i);e(o)||s(f(t,i),o)}0===n(t).length&&a(t)}else s(t,this.configDir);u.record(`Migrated config dir: ${t} -> ${this.configDir}`)}catch(e){u.warn("Failed to migrate legacy .chelper config dir:",e)}}resolveConfigDir(){const e=b("CONFIG_DIR");if(e&&e.trim())return e.trim();const t=b("TEST_HOME");return t&&t.trim()?f(t.trim(),k().configDirName):f(h(),k().configDirName)}createDefaultConfig(){return this.syncLegacyFields({lang:"en_US",active_profile:"default",profiles:{[DEFAULT_PROFILE_NAME]:{}},presets:{}})}normalizeModels(e){const t=t=>{const i=e?.[t]?.trim();return i&&!y(t,i)?i:DEFAULT_MODELS[t]},i={haiku:t("haiku"),sonnet:t("sonnet"),opus:t("opus"),reasoning:t("reasoning")};if(e&&"object"==typeof e)for(const[t,o]of Object.entries(e))t in i||"string"==typeof o&&o.trim()&&(i[t]=o.trim());return i}normalizeToolModels(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||"string"!=typeof e||!t||"object"!=typeof t)return[];const i={};for(const[e,o]of Object.entries(t))"string"==typeof o&&o.trim()&&(i[e]=o.trim());return Object.keys(i).length>0?[[e,i]]:[]});return Object.fromEntries(t)}normalizeToolSettings(e){if(!e||"object"!=typeof e||Array.isArray(e))return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||!t||"object"!=typeof t||Array.isArray(t))return[];const i=Object.fromEntries(Object.entries(t).filter(([,e])=>"string"==typeof e||"boolean"==typeof e||"number"==typeof e&&Number.isFinite(e)));return Object.keys(i).length>0?[[e,i]]:[]});return Object.fromEntries(t)}normalizeStringRecord(e){return!e||"object"!=typeof e||Array.isArray(e)?{}:Object.fromEntries(Object.entries(e).filter(e=>"string"==typeof e[1]&&Boolean(e[1].trim())).map(([e,t])=>[e,t.trim()]))}normalizeProfile(e){if(!e)return{};const t=this.normalizeToolModels(e.tool_models),i=this.normalizeToolSettings(e.tool_settings),o=this.normalizeStringRecord(e.tool_api_keys);return{..."string"==typeof e.api_key&&e.api_key?{api_key:e.api_key}:{},...Object.keys(o).length>0?{tool_api_keys:o}:{},..."string"==typeof e.base_url&&e.base_url?{base_url:e.base_url}:{},...e.models?{models:this.normalizeModels(e.models)}:{},...Object.keys(t).length>0?{tool_models:t}:{},...Object.keys(i).length>0?{tool_settings:i}:{}}}normalizeProfiles(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>e&&"string"==typeof e&&t&&"object"==typeof t?[[e,this.normalizeProfile(t)]]:[]);return Object.fromEntries(t)}normalizePresets(e){if(!e||"object"!=typeof e)return{};const t=Object.entries(e).flatMap(([e,t])=>{if(!e||"string"!=typeof e||!t||"object"!=typeof t)return[];const i=t;return i.models?[[e,{models:this.normalizeModels(i.models),updated_at:i.updated_at||new Date(0).toISOString()}]]:[]});return Object.fromEntries(t)}getResolvedActiveProfile(e){const t=e.active_profile||"default";return e.profiles?.[t]||{}}syncLegacyFields(e){const t=this.normalizeProfiles(e.profiles),i=e.active_profile?.trim()||Object.keys(t)[0]||"default",o=t[i]?t:{...t,[i]:{}},s=this.getResolvedActiveProfile({...e,profiles:o,active_profile:i});return{...e,lang:e.lang||"en_US",active_profile:i,profiles:o,presets:this.normalizePresets(e.presets),api_key:s.api_key,base_url:s.base_url,models:s.models?this.normalizeModels(s.models):DEFAULT_MODELS}}sanitizeBackupLabel(e){return(e||"").trim().replace(/[^a-zA-Z0-9._-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40)}getBackupDir(){return f(this.configDir,"backups")}getLegacySecretsPath(){return f(this.configDir,"secrets.enc")}getSecretsBackupPath(e){return e.replace(/\.yaml$/,".secrets.enc")}static getInstance(){return ConfigManager.instance||(ConfigManager.instance=new ConfigManager),ConfigManager.instance}ensureConfigDir(){e(this.configDir)||t(this.configDir,{recursive:!0})}loadConfig(){try{if(e(this.configPath)){const t=i(this.configPath,"utf-8"),o=m.load(t);let s=this.migrateOldConfig(o||{lang:"en_US"}),n=!1;const a=this.getLegacySecretsPath();if(e(a)&&o?.credential_store!==v)try{s=this.mergeLegacySecrets(s,_(a)),n=!0}catch(e){u.warn("Legacy secrets.enc is unreadable on this host; it was kept for recovery and will not block new credentials."),u.logError("ConfigManager.migrateLegacySecrets",e)}if((this.needsMigration(o)||n)&&(this.backupConfig(),this.atomicWrite(this.configPath,m.dump(this.prepareForPersistence(s))),n))try{r(a)}catch(e){u.logError("ConfigManager.removeLegacySecrets",e)}try{l(this.configPath,384)}catch{}return s}const t=this.getLegacySecretsPath();if(e(t))try{const e=this.mergeLegacySecrets(this.createDefaultConfig(),_(t));this.ensureConfigDir(),this.atomicWrite(this.configPath,m.dump(this.prepareForPersistence(e)));try{r(t)}catch(e){u.logError("ConfigManager.removeLegacySecrets",e)}return e}catch(e){u.warn("Legacy secrets.enc is unreadable on this host; it was kept for recovery and will not block new credentials."),u.logError("ConfigManager.migrateLegacySecretsWithoutConfig",e)}}catch(e){u.warn("Failed to load config, using defaults:",e),u.logError("ConfigManager.loadConfig",e)}return this.createDefaultConfig()}needsMigration(e){return!(!e||"object"!=typeof e)&&("plan"in e||!("profiles"in e)||!("active_profile"in e)||!("presets"in e))}migrateOldConfig(e){if(!e||"object"!=typeof e)return this.createDefaultConfig();const t=e,{plan:i,...o}=t,s=this.normalizeProfiles(o.profiles),r=this.normalizeProfile({api_key:"string"==typeof o.api_key?o.api_key:void 0,base_url:"string"==typeof o.base_url?o.base_url:void 0,models:o.models}),n=Object.keys(s).length>0?s:{[DEFAULT_PROFILE_NAME]:r},a="string"==typeof o.active_profile&&o.active_profile.trim()?o.active_profile.trim():Object.keys(n)[0]||"default";return this.syncLegacyFields({lang:"string"==typeof o.lang&&o.lang?o.lang:"en_US",...!0===o.lang_chosen?{lang_chosen:!0}:{},...o.credential_store===v?{credential_store:v}:{},active_profile:a,profiles:n,presets:this.normalizePresets(o.presets)})}atomicWrite(t,i){const n=`${t}.tmp`;try{o(n,i,{encoding:"utf-8",mode:384}),s(n,t);try{l(t,384)}catch{}}catch(t){if(e(n))try{r(n)}catch{}throw t}}backupConfig(){if(!e(this.configPath))return;const s=this.getBackupDir();e(s)||t(s,{recursive:!0});const r=(new Date).toISOString().replace(/[:.]/g,"-"),n=f(s,`config-${r}.yaml`);try{const e=i(this.configPath,"utf-8");o(n,e,{encoding:"utf-8",mode:384}),this.rotateBackups(s,10,30)}catch(e){u.logError("ConfigManager.backupConfig",e)}}rotateBackups(t,i,o){try{const s=Date.now()-24*o*60*60*1e3,a=n(t).filter(e=>e.startsWith("config-")&&e.endsWith(".yaml")).map(e=>({name:e,path:f(t,e),mtime:c(f(t,e)).mtime})).sort((e,t)=>t.mtime.getTime()-e.mtime.getTime()),l=new Set;a.slice(i).forEach(e=>l.add(e.path)),a.forEach(e=>{e.mtime.getTime()<s&&l.add(e.path)});for(const t of l){let i=!1;try{r(t),i=!0}catch(e){u.logError("ConfigManager.rotateBackups",e)}if(i){const i=this.getSecretsBackupPath(t);if(e(i))try{r(i)}catch(e){u.logError("ConfigManager.rotateBackups.secrets",e)}}}}catch(e){u.logError("ConfigManager.rotateBackups",e)}}saveConfig(e){try{this.ensureConfigDir(),this.backupConfig();const t=this.syncLegacyFields(this.migrateOldConfig(e||this.config)),i=this.prepareForPersistence(t),o=m.dump(i);this.atomicWrite(this.configPath,o),this.config=t}catch(e){throw u.error("Failed to save config:",e),u.logError("ConfigManager.saveConfig",e),e}}prepareForPersistence(e){const t={...e,credential_store:v};return delete t.api_key,delete t.base_url,delete t.models,t}mergeLegacySecrets(e,t){const i={...e.profiles||{}},o=new Set([...Object.keys(t.api_keys||{}),...Object.keys(t.tool_api_keys||{})]);for(const e of o){const o=i[e]||{},s=o.api_key||t.api_keys?.[e],r={...t.tool_api_keys?.[e]||{},...o.tool_api_keys||{}};i[e]={...o,...s?{api_key:s}:{},...Object.keys(r).length>0?{tool_api_keys:r}:{}}}return this.syncLegacyFields({...e,profiles:i})}getConfig(){return this.syncLegacyFields({...this.config})}updateConfig(e){this.saveConfig({...this.config,...e})}isFirstRun(){return!e(this.configPath)}getLang(){return this.config.lang||"en_US"}isLangChosen(){return!0===this.config.lang_chosen}setLang(e){this.updateConfig({lang:e,lang_chosen:!0})}getActiveProfileName(){return this.config.active_profile||"default"}listProfiles(){return Object.keys(this.config.profiles||{}).sort()}getProfile(e=this.getActiveProfileName()){const t=this.config.profiles?.[e]||{};return{...t.api_key?{api_key:t.api_key}:{},...t.tool_api_keys&&Object.keys(t.tool_api_keys).length>0?{tool_api_keys:this.normalizeStringRecord(t.tool_api_keys)}:{},...t.base_url?{base_url:t.base_url}:{},models:t.models?this.normalizeModels(t.models):DEFAULT_MODELS,...t.tool_models&&Object.keys(t.tool_models).length>0?{tool_models:this.normalizeToolModels(t.tool_models)}:{},...t.tool_settings&&Object.keys(t.tool_settings).length>0?{tool_settings:this.normalizeToolSettings(t.tool_settings)}:{}}}createProfile(e,t=this.getActiveProfileName()){const i=this.getProfile(t),o={...this.config.profiles||{},[e]:{...i.api_key?{api_key:i.api_key}:{},...i.base_url?{base_url:i.base_url}:{},models:this.normalizeModels(i.models)}};this.saveConfig({...this.config,profiles:o})}setActiveProfile(e){if(!this.config.profiles?.[e])throw new Error(`Profile not found: ${e}`);this.saveConfig({...this.config,active_profile:e})}updateProfile(e,t){const i=this.config.profiles?.[e]||{},o=t.models?this.normalizeModels({...i.models,...t.models}):i.models?this.normalizeModels(i.models):void 0,s={...i,...void 0!==t.api_key?{api_key:t.api_key}:{},...void 0!==t.base_url?{base_url:t.base_url}:{},...o?{models:o}:{}},r={...this.config.profiles||{},[e]:s};this.saveConfig({...this.config,profiles:r})}deleteProfile(e){const t={...this.config.profiles||{}};if(!t[e])return;const i=Object.keys(t).filter(t=>t!==e);if(0===i.length)throw new Error("At least one profile must remain");delete t[e];const o=this.getActiveProfileName()===e?i[0]:this.getActiveProfileName();this.saveConfig({...this.config,profiles:t,active_profile:o})}getApiKey(){return this.config.profiles?.[this.getActiveProfileName()]?.api_key}setApiKey(e){this.updateProfile(this.getActiveProfileName(),{api_key:e})}revokeApiKey(){const e=this.config.profiles?.[this.getActiveProfileName()]||{},{api_key:t,tool_api_keys:i,...o}=e,s={...this.config.profiles||{},[this.getActiveProfileName()]:o};this.saveConfig({...this.config,profiles:s})}getApiKeyFingerprint(){const e=this.getApiKey();return e?p("sha256").update(e).digest("hex").slice(0,12):""}getApiKeyForTool(e){const t=this.config.profiles?.[this.getActiveProfileName()];return t?.tool_api_keys?.[e]||t?.api_key||void 0}setToolApiKey(e,t){const i=this.getActiveProfileName(),o=this.config.profiles?.[i]||{},s={...this.config.profiles||{},[i]:{...o,tool_api_keys:{...o.tool_api_keys||{},[e]:t}}};this.saveConfig({...this.config,profiles:s})}clearToolApiKey(e){const t=this.getActiveProfileName(),i=this.config.profiles?.[t]||{},{[e]:o,...s}=i.tool_api_keys||{},r={...i};Object.keys(s).length>0?r.tool_api_keys=s:delete r.tool_api_keys,this.saveConfig({...this.config,profiles:{...this.config.profiles||{},[t]:r}})}listToolApiKeys(){return Object.keys(this.config.profiles?.[this.getActiveProfileName()]?.tool_api_keys||{}).sort()}getToolApiKeyFingerprint(e){const t=this.config.profiles?.[this.getActiveProfileName()]?.tool_api_keys?.[e];return t?p("sha256").update(t).digest("hex").slice(0,12):""}getModels(){const e=this.config.profiles?.[this.getActiveProfileName()]?.models;return e?this.normalizeModels(e):DEFAULT_MODELS}setModels(e){this.updateProfile(this.getActiveProfileName(),{models:this.normalizeModels(e)})}updateModels(e){this.setModels(this.normalizeModels({...this.getModels(),...e}))}getModelsForTool(e){const t=this.getModels(),i=this.config.profiles?.[this.getActiveProfileName()]?.tool_models?.[e];return i?this.normalizeModels({...t,...i}):t}setToolModel(e,t){const i=this.getActiveProfileName(),o=this.config.profiles?.[i]||{},s={...o.tool_models||{}};t&&0!==Object.keys(t).length?s[e]=t:delete s[e];const r={...o};Object.keys(s).length>0?r.tool_models=s:delete r.tool_models;const n={...this.config.profiles||{},[i]:r};this.saveConfig({...this.config,profiles:n})}getToolSettings(e){const t=this.config.profiles?.[this.getActiveProfileName()]?.tool_settings?.[e];return t?{...t}:{}}getToolSetting(e,t){return this.config.profiles?.[this.getActiveProfileName()]?.tool_settings?.[e]?.[t]}setToolSetting(e,t,i){if(this.getToolSetting(e,t)===i)return;const o=this.getActiveProfileName(),s=this.config.profiles?.[o]||{},r={...s.tool_settings||{}};r[e]={...r[e]||{},[t]:i};const n={...this.config.profiles||{},[o]:{...s,tool_settings:r}};this.saveConfig({...this.config,profiles:n})}getCustomPresets(){return{...this.config.presets||{}}}saveCustomPreset(e,t){const i={...this.config.presets||{},[e]:{models:this.normalizeModels(t),updated_at:(new Date).toISOString()}};this.saveConfig({...this.config,presets:i})}deleteCustomPreset(e){const t={...this.config.presets||{}};t[e]&&(delete t[e],this.saveConfig({...this.config,presets:t}))}listBackups(){const t=this.getBackupDir();return e(t)?n(t).filter(e=>e.startsWith("config-")&&e.endsWith(".yaml")).map(e=>{const i=f(t,e);return{name:e,path:i,mtimeMs:c(i).mtimeMs}}).sort((e,t)=>t.mtimeMs-e.mtimeMs):[]}createBackup(s){this.ensureConfigDir();const r=this.getBackupDir();e(r)||t(r,{recursive:!0});const n=(new Date).toISOString().replace(/[:.]/g,"-"),a=this.sanitizeBackupLabel(s),c=f(r,a?`config-${n}-${a}.yaml`:`config-${n}.yaml`),l=e(this.configPath)?i(this.configPath,"utf-8"):m.dump(this.prepareForPersistence(this.getConfig()));return o(c,l,{encoding:"utf-8",mode:384}),this.rotateBackups(r,10,30),c}restoreBackup(t){if(!t||"."===t||".."===t||/[/\\]|\.\./.test(t))throw new Error(`Invalid backup name: ${t}`);const o=this.getBackupDir(),s=f(o,t);if(!e(s))throw new Error(`Backup not found: ${t}`);const r=i(s,"utf-8"),n=m.load(r),a=this.getSecretsBackupPath(s);let c=this.migrateOldConfig(n||this.createDefaultConfig());if(n?.credential_store!==v&&e(a))try{c=this.mergeLegacySecrets(c,_(a))}catch(e){throw new Error(`Legacy backup credentials could not be decrypted: ${a}`,{cause:e})}this.saveConfig(c)}getConfigDir(){return this.configDir}getBaseUrl(){return this.runtimeBaseUrl||this.config.profiles?.[this.getActiveProfileName()]?.base_url||DEFAULT_BASE_URL}hasRuntimeBaseUrl(){return Boolean(this.runtimeBaseUrl)}setBaseUrl(e,t=!1){this.runtimeBaseUrl=e,t&&this.updateProfile(this.getActiveProfileName(),{base_url:e})}resetBaseUrl(){this.runtimeBaseUrl=void 0;const e=this.config.profiles?.[this.getActiveProfileName()]||{},{base_url:t,...i}=e,o={...this.config.profiles||{},[this.getActiveProfileName()]:i};this.saveConfig({...this.config,profiles:o})}}export const configManager=ConfigManager.getInstance();export function savedApiKeys(){const e=new Set;for(const t of configManager.listProfiles()){const i=configManager.getProfile(t);for(const t of[i.api_key,...Object.values(i.tool_api_keys??{})])t?.trim()&&e.add(t.trim())}return[...e]}
@@ -1 +1 @@
1
- import{firstCommandWord as e}from"./cli-help.js";export function dryRunUnsupportedCommand(s){const n=e(s);if(void 0===n)return"(wizard)";const r=s.filter(e=>!e.startsWith("-")),o=r[r.indexOf(n)+1];switch(n){case"setup":case"config":case"status":case"bench":case"logs":case"completion":case"help":return null;case"doctor":return s.includes("--report")?"doctor --report":null;case"base":return void 0===o?null:`base ${o}`;case"lang":return void 0===o||"show"===o?null:`lang ${o}`;case"codex":return s.includes("--undo")?"codex sessions --undo":null;case"sessions":return"export"===o?"sessions export":"resume"===o&&s.includes("--run")?"sessions resume --run":null;default:return n}}
1
+ import{firstCommandWord as e}from"./cli-help.js";export function dryRunUnsupportedCommand(s){const n=e(s);if(void 0===n)return"(wizard)";const r=s.filter(e=>!e.startsWith("-")),o=r[r.indexOf(n)+1];switch(n){case"setup":case"config":case"status":case"bench":case"logs":case"list":case"completion":case"help":return null;case"doctor":return s.includes("--report")?"doctor --report":null;case"base":return void 0===o?null:`base ${o}`;case"lang":return void 0===o||"show"===o?null:`lang ${o}`;case"codex":return s.includes("--undo")?"codex sessions --undo":null;case"sessions":return"export"===o?"sessions export":"resume"===o&&s.includes("--run")?"sessions resume --run":null;default:return n}}