@vibeapi/api-helper 0.0.147 → 0.0.149

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 (47) hide show
  1. package/components.json +157 -0
  2. package/dist/brand.json +2 -1
  3. package/dist/commands/components.d.ts +26 -0
  4. package/dist/commands/components.js +1 -0
  5. package/dist/commands/doctor.js +1 -1
  6. package/dist/commands/setup.d.ts +4 -3
  7. package/dist/commands/setup.js +1 -1
  8. package/dist/commands/upgrade.d.ts +7 -3
  9. package/dist/commands/upgrade.js +1 -1
  10. package/dist/components.json +157 -0
  11. package/dist/lib/app-update.d.ts +46 -6
  12. package/dist/lib/app-update.js +1 -1
  13. package/dist/lib/brand.d.ts +2 -0
  14. package/dist/lib/brand.js +1 -1
  15. package/dist/lib/codex-sessions.js +1 -1
  16. package/dist/lib/components.d.ts +204 -0
  17. package/dist/lib/components.js +1 -0
  18. package/dist/lib/dry-run.js +1 -1
  19. package/dist/lib/i18n.js +1 -1
  20. package/dist/lib/post-apply.js +1 -1
  21. package/dist/lib/powershell-env.d.ts +12 -0
  22. package/dist/lib/powershell-env.js +1 -0
  23. package/dist/lib/program.js +1 -1
  24. package/dist/lib/runtime-options.d.ts +5 -0
  25. package/dist/lib/runtime-options.js +1 -1
  26. package/dist/lib/tool-upgrade.d.ts +2 -0
  27. package/dist/lib/tool-upgrade.js +1 -1
  28. package/dist/lib/tools/chatgpt-desktop.d.ts +45 -0
  29. package/dist/lib/tools/chatgpt-desktop.js +1 -0
  30. package/dist/lib/tools/claude-desktop-app.d.ts +17 -58
  31. package/dist/lib/tools/claude-desktop-app.js +1 -1
  32. package/dist/lib/tools/claude-desktop-cli.js +1 -1
  33. package/dist/lib/tools/claude-desktop.js +1 -1
  34. package/dist/lib/tools/codex.js +1 -1
  35. package/dist/lib/tools/desktop-app-installer.d.ts +102 -0
  36. package/dist/lib/tools/desktop-app-installer.js +1 -0
  37. package/dist/lib/tools/desktop-process.d.ts +4 -1
  38. package/dist/lib/tools/desktop-process.js +1 -1
  39. package/dist/lib/tools/elevated-reg.js +1 -1
  40. package/dist/lib/tools/tool-installs.d.ts +8 -0
  41. package/dist/lib/tools/tool-installs.js +1 -1
  42. package/dist/lib/tools/types.d.ts +32 -4
  43. package/dist/lib/windows-env.js +1 -1
  44. package/dist/lib/wizard.js +1 -1
  45. package/dist/locales/en_US.json +152 -12
  46. package/dist/locales/zh_CN.json +152 -12
  47. package/package.json +2 -1
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Installs and upgrades a vendor's desktop app (Claude Desktop, ChatGPT) on macOS / Windows from the brand's
3
+ * download mirror: a CNB repo that re-publishes the vendor's own installers byte for byte every hour with a
4
+ * `SHA256SUMS`. The apps have their own updaters (Squirrel / Sparkle on macOS, App Installer / Store on
5
+ * Windows) that talk to vendor hosts often unreachable from mainland China; vibecli only steps in when the
6
+ * installed copy is older than the mirror's (or when asked to reinstall), and leaves those updaters on.
7
+ *
8
+ * Every download is checked twice before anything is replaced: its sha256 against the release's SHA256SUMS,
9
+ * then the vendor's code signature (macOS: `codesign --verify --deep --strict` of the copied bundle with the
10
+ * vendor's team id and bundle id, plus Gatekeeper `spctl -a`; Windows: Authenticode of the .msix, valid and
11
+ * signed by the vendor's publisher). A failed step leaves the installed copy as it was.
12
+ *
13
+ * - macOS: the .dmg is attached read-only, the app copied with `ditto` next to the target and swapped in with
14
+ * two renames (the old bundle stays until the new one is in place). A new install goes to /Applications, or
15
+ * ~/Applications when /Applications isn't writable for this user. Write access is checked before downloading.
16
+ * - Windows: `Add-AppxPackage` (per user, no administrator).
17
+ * - Not as root (sudo) on macOS, not as SYSTEM on Windows: the app would belong to the wrong account. Not over
18
+ * SSH on Windows: a network logon can't deploy MSIX packages.
19
+ */
20
+ export interface DesktopAppSpec {
21
+ /** Log label, e.g. `claude-desktop`. */
22
+ id: string;
23
+ /** The brand's download page `https://cnb.cool/<group>/<repo>/-/releases/latest`, undefined without a mirror. */
24
+ mirror(): string | undefined;
25
+ /** Release file for this machine, null where the vendor has none (Linux, Intel Mac for ChatGPT). */
26
+ asset(platform: NodeJS.Platform, arm64: boolean): string | null;
27
+ /** Release text file holding the version of `asset` (macOS and Windows builds may be numbered differently). */
28
+ versionFile(platform: NodeJS.Platform, arm64: boolean): string;
29
+ /**
30
+ * Release tag holding `version` (`v<version>` for Claude Desktop); absent when the tag follows another
31
+ * platform's numbering (ChatGPT's follows macOS), then files are read from `releases/latest/download`.
32
+ */
33
+ releaseTag?(version: string): string;
34
+ /** The bundle inside the .dmg, and the names an installed copy may have (first = name of a new install). */
35
+ macApp: {
36
+ source: string;
37
+ installedNames: string[];
38
+ bundleId: string;
39
+ teamId: string;
40
+ };
41
+ /** Authenticode subject of the .msix, and a `Where-Object` filter for the installed package. */
42
+ windows: {
43
+ signer: RegExp;
44
+ packageFilter: string;
45
+ };
46
+ /** Why this machine can't be served beyond platform / mirror (a key under `app_update.reason_*`), else null. */
47
+ extraBlocker?(platform: NodeJS.Platform): string | null;
48
+ }
49
+ type Runner = (command: string, args: string[], options?: {
50
+ timeout?: number;
51
+ input?: string;
52
+ }) => {
53
+ status: number | null;
54
+ stdout: string;
55
+ stderr: string;
56
+ };
57
+ /** Tests replace the process runner, the network, the platform, the CPU architecture and root / SYSTEM. */
58
+ export declare function setDesktopAppDepsForTests(deps: {
59
+ run?: Runner | null;
60
+ fetch?: typeof fetch | null;
61
+ platform?: NodeJS.Platform | null;
62
+ arm64?: boolean | null;
63
+ privileged?: boolean | null;
64
+ remote?: boolean | null;
65
+ }): void;
66
+ export declare function appPlatform(): NodeJS.Platform;
67
+ /** An ARM machine, also when this node runs under Rosetta / x64 emulation. */
68
+ export declare function isArm64Machine(): boolean;
69
+ export declare class DesktopInstallError extends Error {
70
+ constructor(message: string);
71
+ }
72
+ /** Localized failure; an empty detail (a tool that printed nothing) reads "reason unknown", never `{{reason}}`. */
73
+ export declare const fail: (key: string, vars?: Record<string, string>) => never;
74
+ /** `sha256sum` lines → file name → hex digest. */
75
+ export declare function parseSha256Sums(text: string): Map<string, string>;
76
+ /** `CFBundleIdentifier` of a bundle, null when unreadable. */
77
+ export declare function macBundleId(app: string): string | null;
78
+ export declare function macAppVersion(app: string): string | null;
79
+ /** First line of a PowerShell error worth showing (stderr is CLIXML when the output is redirected). */
80
+ export declare function powerShellError(text: string): string;
81
+ export interface DesktopInstallProgress {
82
+ downloading?(version: string): void;
83
+ percent?(percent: number): void;
84
+ verifying?(): void;
85
+ installing?(): void;
86
+ }
87
+ export interface DesktopAppInstaller {
88
+ mirrorRepo(): string | null;
89
+ assetName(): string | null;
90
+ blocker(): string | null;
91
+ latestVersion(timeoutMs?: number): Promise<string>;
92
+ installedMacApp(): string | null;
93
+ windowsPackageVersion(): string | null;
94
+ verifyMacApp(app: string): void;
95
+ verifyWindowsPackage(msix: string): void;
96
+ install(progress?: DesktopInstallProgress): Promise<{
97
+ version: string;
98
+ location: string;
99
+ }>;
100
+ }
101
+ export declare function createDesktopAppInstaller(spec: DesktopAppSpec): DesktopAppInstaller;
102
+ export {};
@@ -0,0 +1 @@
1
+ import{spawnSync as t}from"child_process";import{createHash as e}from"crypto";import{accessSync as r,constants as o,createReadStream as n,createWriteStream as s,existsSync as a,mkdirSync as i,mkdtempSync as c,readFileSync as l,renameSync as p,rmSync as u}from"fs";import{cpus as d,tmpdir as f}from"os";import{basename as m,dirname as g,join as w}from"path";import{Readable as h,Transform as $}from"stream";import{pipeline as S}from"stream/promises";import{logger as A}from"../../utils/logger.js";import{i18n as x}from"../i18n.js";import{powerShellEnv as y}from"../powershell-env.js";import{toolHome as E}from"./types.js";const v=(e,r,o={})=>{const n=t(e,r,{encoding:"utf-8",timeout:o.timeout??12e4,windowsHide:!0,input:o.input,stdio:[void 0===o.input?"ignore":"pipe","pipe","pipe"],maxBuffer:16777216,env:y()});return n.error&&A.logError(`desktop-app: ${e}`,n.error),{status:n.status,stdout:`${n.stdout??""}`,stderr:`${n.stderr??""}`}};let _=v,I=(...t)=>fetch(...t),k=null,P=null,b=null,C=null;export function setDesktopAppDepsForTests(t){void 0!==t.run&&(_=t.run??v),void 0!==t.fetch&&(I=t.fetch??((...t)=>fetch(...t))),void 0!==t.platform&&(k=t.platform),void 0!==t.arm64&&(P=t.arm64),void 0!==t.privileged&&(b=t.privileged),void 0!==t.remote&&(C=t.remote)}export function appPlatform(){return k??process.platform}export function isArm64Machine(){return null!==P?P:"arm64"===process.arch||("win32"===appPlatform()?"ARM64"===process.env.PROCESSOR_ARCHITECTURE||"ARM64"===process.env.PROCESSOR_ARCHITEW6432:"darwin"===appPlatform()&&/\bApple\b/.test(d()[0]?.model??""))}export class DesktopInstallError extends Error{constructor(t){super(t),this.name="DesktopInstallError"}}export const fail=(t,e={})=>{const r={};for(const[t,o]of Object.entries(e))r[t]=o.trim()||x.t("app_update.reason_unknown");throw new DesktopInstallError(x.t(`app_update.error_${t}`,r))};export function parseSha256Sums(t){const e=new Map;for(const r of t.split(/\r?\n/)){const t=/^([0-9a-f]{64})\s+\*?(\S.*)$/i.exec(r.trim());t&&e.set(t[2].trim(),t[1].toLowerCase())}return e}async function T(t,e){const r=await I(t,{signal:AbortSignal.timeout(e)});if(!r.ok)throw new Error(`${t}: HTTP ${r.status}`);return(await r.text()).trim()}function M(t,e){try{const r=l(w(t,"Contents","Info.plist"),"utf-8");return new RegExp(`<key>${e}</key>\\s*<string>([^<]*)</string>`).exec(r)?.[1]??null}catch{return null}}export function macBundleId(t){return M(t,"CFBundleIdentifier")}export function macAppVersion(t){return/^\d+\.\d+\.\d+/.exec(M(t,"CFBundleShortVersionString")??"")?.[0]??null}function R(t){try{return r(t,o.W_OK),!0}catch{return!1}}const O=t=>`'${t.replace(/'/g,"''")}'`;function j(t,e=12e4){return _("powershell.exe",["-NoProfile","-NonInteractive","-ExecutionPolicy","Bypass","-Command","[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; "+t],{timeout:e})}export function powerShellError(t){return t.replace(/#< CLIXML/g,"").replace(/_x000D__x000A_/g,"\n").replace(/<\/S>/g,"\n").replace(/<[^>]+>/g,"").replace(/_x[0-9A-F]{4}_/g,"").replace(/&apos;/g,"'").replace(/&quot;/g,'"').replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&").split(/\r?\n/).map(t=>t.trim()).find(t=>t&&!/^(At line|At char|所在位置|\+|~|CategoryInfo|FullyQualifiedErrorId)/i.test(t))??""}export function createDesktopAppInstaller(t){const r=()=>t.mirror()?.replace(/\/-\/releases\/latest\/?$/,"")??null,o=()=>t.asset(appPlatform(),isArm64Machine()),l=()=>{const e=appPlatform();return"darwin"!==e&&"win32"!==e?"unsupported_platform":o()?r()?"darwin"===e&&(b??0===process.getuid?.())?"root":"win32"===e&&(null!==b?b:/\\system32\\config\\systemprofile/i.test(process.env.USERPROFILE??"")||!0===process.env.USERNAME?.endsWith("$"))?"system_account":"win32"===e&&(null!==C?C:Boolean(process.env.SSH_CONNECTION||process.env.SSH_CLIENT))?"remote_session":t.extraBlocker?.(e)??null:"no_mirror":"darwin"===e?"unsupported_arch":"unsupported_platform"},d=async(e=8e3)=>{const o=r();if(!o)throw new Error("no mirror");const n=t.versionFile(appPlatform(),isArm64Machine()),s=await T(`${o}/-/releases/latest/download/${n}`,e),a=/^(\d+\.\d+\.\d+)(\.\d+)?$/.exec(s)?.[1];if(!a)throw new Error(`unexpected ${n}: ${s.slice(0,40)}`);return a},y=()=>{for(const e of["/Applications",w(E(),"Applications")])for(const r of t.macApp.installedNames){const o=w(e,r);if(!a(w(o,"Contents","Info.plist")))continue;const n=macBundleId(o);if(null===n||n===t.macApp.bundleId)return o}return null},v=e=>{const r=_("codesign",["--verify","--deep","--strict",e],{timeout:6e5});0!==r.status&&fail("signature",{reason:(r.stderr||r.stdout).trim().split("\n")[0]||`codesign ${r.status}`});const o=_("codesign",["-dv",e]),n=`${o.stdout}\n${o.stderr}`,s=/^TeamIdentifier=(\S+)/m.exec(n)?.[1],a=/^Identifier=(\S+)/m.exec(n)?.[1];s===t.macApp.teamId&&a===t.macApp.bundleId||fail("signature",{reason:`TeamIdentifier=${s??"?"} Identifier=${a??"?"}`});const i=_("spctl",["-a","-t","exec","-vv",e],{timeout:3e5});0!==i.status&&fail("signature",{reason:(i.stderr||i.stdout).trim().split("\n")[0]||`spctl ${i.status}`})},k=()=>{const e=y();let r=e;if(!r){const e=R("/Applications")?"/Applications":w(E(),"Applications");r=w(e,t.macApp.installedNames[0])}const o=g(r);return(a(o)&&!R(o)||e&&!R(e))&&fail("no_permission",{path:r}),{target:r,existing:e}},P=e=>{const r=j(`$s = Get-AuthenticodeSignature -LiteralPath ${O(e)}; [pscustomobject]@{ status = [string]$s.Status; subject = [string]$s.SignerCertificate.Subject } | ConvertTo-Json -Compress`);let o={};try{o=JSON.parse(r.stdout.trim()||"{}")}catch{}if("Valid"!==o.status||!t.windows.signer.test(o.subject??"")){const t=o.status?`${o.status} ${o.subject??""}`:powerShellError(r.stderr)||powerShellError(r.stdout.trim().startsWith("{")?"":r.stdout)||`exit ${r.status}`;fail("signature",{reason:t.trim()})}},M=()=>{const e=j(`Get-AppxPackage | Where-Object { ${t.windows.packageFilter} } | ForEach-Object { $_.Version.ToString() }`,3e4);return/\d+\.\d+\.\d+/.exec(e.stdout)?.[0]??null};return{mirrorRepo:r,assetName:o,blocker:l,latestVersion:d,installedMacApp:y,windowsPackageVersion:M,verifyMacApp:v,verifyWindowsPackage:P,install:async(y={})=>{const E=l();E&&fail("blocked",{reason:x.t(`app_update.reason_${E}`)});const b=r(),C=o();let R;try{R=await d(15e3)}catch(t){fail("download",{reason:t instanceof Error?t.message:String(t)})}const N=t.releaseTag?`${b}/-/releases/download/${t.releaseTag(R)}`:`${b}/-/releases/latest/download`;let F;try{F=parseSha256Sums(await T(`${N}/SHA256SUMS`,15e3)).get(C)}catch(e){A.logError(`${t.id}: SHA256SUMS`,e)}F||fail("no_checksum",{file:C}),"darwin"===appPlatform()&&k();const H=c(w(f(),`${t.id}-`));try{const r=w(H,C);y.downloading?.(R),await async function(t,e,r){let o;try{o=await I(t,{signal:AbortSignal.timeout(18e5)})}catch(t){fail("download",{reason:t instanceof Error?t.message:String(t)})}o.ok&&o.body||fail("download",{reason:`HTTP ${o.status}`});const n=Number(o.headers.get("content-length"))||0;let a=0,i=-1;const c=new $({transform(t,e,o){a+=t.length;const s=n?Math.floor(100*a/n):-1;s>=0&&s!==i&&(i=s,r?.(s)),o(null,t)}});try{await S(h.fromWeb(o.body),c,s(e))}catch(t){u(e,{force:!0}),fail("download",{reason:t instanceof Error?t.message:String(t)})}}(`${N}/${C}`,r,y.percent),y.verifying?.();const o=await async function(t){const r=e("sha256");for await(const e of n(t))r.update(e);return r.digest("hex")}(r);return o!==F&&fail("checksum",{file:C,expected:F.slice(0,12),actual:o.slice(0,12)}),y.installing?.(),"darwin"===appPlatform()?await(async(e,r)=>{const o=w(r,"mnt");i(o,{recursive:!0});const n=_("hdiutil",["attach","-nobrowse","-readonly","-noautoopen","-mountpoint",o,e],{timeout:3e5,input:"Y\n"});0!==n.status&&fail("mount",{reason:(n.stderr||n.stdout).trim().split("\n").pop()??""});let s=null;try{const e=w(o,t.macApp.source);a(e)||fail("bad_package",{reason:t.macApp.source});const{target:r,existing:n}=k(),c=g(r),l=m(r);i(c,{recursive:!0}),s=w(c,`.${l}.${process.pid}.new`),u(s,{recursive:!0,force:!0});const d=_("ditto",[e,s],{timeout:9e5});0!==d.status&&fail("copy",{reason:(d.stderr||d.stdout).trim().split("\n")[0]??""}),v(s);const f=macAppVersion(s);f||fail("bad_package",{reason:"Info.plist"});const h=w(c,`.${l}.${process.pid}.old`);try{n&&p(n,h)}catch(t){throw["EPERM","EACCES"].includes(t.code??"")&&fail("app_management",{path:n}),t}try{p(s,r)}catch(t){throw n&&p(h,n),t}return s=null,u(h,{recursive:!0,force:!0}),{version:f,location:r}}finally{s&&u(s,{recursive:!0,force:!0}),_("hdiutil",["detach",o,"-force"],{timeout:6e4})}})(r,H):(t=>{P(t);const e=j(`$ErrorActionPreference = 'Stop'; Add-AppxPackage -Path ${O(t)} -ForceApplicationShutdown`,9e5);0!==e.status&&fail("install",{reason:powerShellError(e.stderr)||powerShellError(e.stdout)||`Add-AppxPackage ${e.status}`});const r=M();return r||fail("install",{reason:"Get-AppxPackage"}),{version:r,location:"MSIX"}})(r)}finally{u(H,{recursive:!0,force:!0})}}}}
@@ -13,13 +13,16 @@ export declare const WINDOWS_PACKAGE_FAMILIES: string[];
13
13
  declare let run: (command: string, args: string[], timeout?: number) => {
14
14
  status: number | null;
15
15
  stdout: string;
16
+ stderr?: string;
16
17
  };
17
18
  /** Tests replace the process runner. */
18
19
  export declare function setDesktopProcessRunnerForTests(fake: typeof run): void;
19
20
  export declare function isDesktopRunning(): boolean;
20
21
  /** Whether this platform supports `restartDesktop()`. */
21
22
  export declare function canRestartDesktop(): boolean;
22
- /** Quit (gracefully, then forcibly); true when no instance is left. */
23
+ /** The last `quitDesktop()` was turned down by the app (a confirmation open, work running); it is still running. */
24
+ export declare function desktopQuitRefused(): boolean;
25
+ /** Quit (gracefully, then forcibly); true when no instance is left. A quit the app turns down on macOS is left alone (see `quitChatgpt`). */
23
26
  export declare function quitDesktop(): boolean;
24
27
  /** Start the installed app; true when it is running afterwards. */
25
28
  export declare function launchDesktop(): boolean;
@@ -1 +1 @@
1
- import{spawnSync as e}from"child_process";import{logger as t}from"../../utils/logger.js";export const WINDOWS_PACKAGE_FAMILIES=["Claude_pzs8sxrjxfjjc","AnthropicPBC.Claude_fnn82j28hfe8t"];const r="Get-Process -Name claude -ErrorAction SilentlyContinue | Where-Object { $_.Path -and ($_.Path -like '*\\AnthropicClaude\\*' -or $_.Path -like '*\\Programs\\Claude\\*' -or $_.Path -like '*\\WindowsApps\\*Claude*') }";let o=(r,o,s=3e4)=>{const n=e(r,o,{encoding:"utf-8",timeout:s,windowsHide:!0,stdio:["ignore","pipe","pipe"]});return n.error&&t.logError(`desktop-process: ${r}`,n.error),{status:n.status,stdout:`${n.stdout??""}`}};export function setDesktopProcessRunnerForTests(e){o=e}function s(e,t){return o("powershell.exe",["-NoProfile","-NonInteractive","-Command",e],t)}function n(e){Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,e)}export function isDesktopRunning(){return"darwin"===process.platform?0===o("pgrep",["-x","Claude"]).status:"win32"===process.platform&&Number(s(`@(${r}).Count`).stdout.trim())>0}export function canRestartDesktop(){return"darwin"===process.platform||"win32"===process.platform}export function quitDesktop(){if("darwin"===process.platform){o("osascript",["-e",'quit app "Claude"']);for(let e=0;e<30&&isDesktopRunning();e++)n(500);isDesktopRunning()&&o("pkill",["-x","Claude"]),n(1e3)}else{if("win32"!==process.platform)return!1;s([`$p = @(${r})`,"$p | ForEach-Object { [void]$_.CloseMainWindow() }","Start-Sleep -Seconds 3","$p | Where-Object { -not $_.HasExited } | Stop-Process -Force -ErrorAction SilentlyContinue","Start-Sleep -Seconds 1"].join("\n"),6e4)}return!isDesktopRunning()}export function launchDesktop(){if("darwin"===process.platform)o("open",["-a","Claude"]);else{if("win32"!==process.platform)return!1;s(["$squirrel = Join-Path $env:LOCALAPPDATA 'AnthropicClaude\\claude.exe'","$programs = Join-Path $env:LOCALAPPDATA 'Programs\\Claude\\Claude.exe'","if (Test-Path $squirrel) { Start-Process $squirrel }","elseif (Test-Path $programs) { Start-Process $programs }",`else { $pkg = Get-AppxPackage | Where-Object { @(${WINDOWS_PACKAGE_FAMILIES.map(e=>`'${e}'`).join(",")}) -contains $_.PackageFamilyName } | Select-Object -First 1`," if ($pkg) { $id = (Get-AppxPackageManifest $pkg).Package.Applications.Application.Id | Select-Object -First 1",' Start-Process "shell:AppsFolder\\$($pkg.PackageFamilyName)!$id" } }'].join("\n"),6e4)}for(let e=0;e<20;e++){if(isDesktopRunning())return!0;n(500)}return!1}export function restartDesktop(){return!!canRestartDesktop()&&(quitDesktop(),launchDesktop())}
1
+ import{spawnSync as e}from"child_process";import{logger as t}from"../../utils/logger.js";import{powerShellEnv as r}from"../powershell-env.js";export const WINDOWS_PACKAGE_FAMILIES=["Claude_pzs8sxrjxfjjc","AnthropicPBC.Claude_fnn82j28hfe8t"];const o="Get-Process -Name claude -ErrorAction SilentlyContinue | Where-Object { $_.Path -and ($_.Path -like '*\\AnthropicClaude\\*' -or $_.Path -like '*\\Programs\\Claude\\*' -or $_.Path -like '*\\WindowsApps\\*Claude*') }";let s=(o,s,n=3e4)=>{const i=e(o,s,{encoding:"utf-8",timeout:n,windowsHide:!0,stdio:["ignore","pipe","pipe"],env:r()});return i.error&&t.logError(`desktop-process: ${o}`,i.error),{status:i.status,stdout:`${i.stdout??""}`,stderr:`${i.stderr??""}`}};export function setDesktopProcessRunnerForTests(e){s=e}function n(e,t){return s("powershell.exe",["-NoProfile","-NonInteractive","-Command",e],t)}function i(e){Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,e)}export function isDesktopRunning(){return"darwin"===process.platform?0===s("pgrep",["-x","Claude"]).status:"win32"===process.platform&&Number(n(`@(${o}).Count`).stdout.trim())>0}export function canRestartDesktop(){return"darwin"===process.platform||"win32"===process.platform}let a=!1;export function desktopQuitRefused(){return a}export function quitDesktop(){if(a=!1,"darwin"===process.platform){const e=s("osascript",["-e",'quit app "Claude"']);if((null===e.status||/\(-128\)|-1712/.test(e.stderr??""))&&isDesktopRunning())return a=!0,!1;for(let e=0;e<30&&isDesktopRunning();e++)i(500);isDesktopRunning()&&s("pkill",["-x","Claude"]),i(1e3)}else{if("win32"!==process.platform)return!1;n([`$p = @(${o})`,"$p | ForEach-Object { [void]$_.CloseMainWindow() }","Start-Sleep -Seconds 3","$p | Where-Object { -not $_.HasExited } | Stop-Process -Force -ErrorAction SilentlyContinue","Start-Sleep -Seconds 1"].join("\n"),6e4)}return!isDesktopRunning()}export function launchDesktop(){if("darwin"===process.platform)s("open",["-a","Claude"]);else{if("win32"!==process.platform)return!1;n(["$squirrel = Join-Path $env:LOCALAPPDATA 'AnthropicClaude\\claude.exe'","$programs = Join-Path $env:LOCALAPPDATA 'Programs\\Claude\\Claude.exe'","if (Test-Path $squirrel) { Start-Process $squirrel }","elseif (Test-Path $programs) { Start-Process $programs }",`else { $pkg = Get-AppxPackage | Where-Object { @(${WINDOWS_PACKAGE_FAMILIES.map(e=>`'${e}'`).join(",")}) -contains $_.PackageFamilyName } | Select-Object -First 1`," if ($pkg) { $id = (Get-AppxPackageManifest $pkg).Package.Applications.Application.Id | Select-Object -First 1",' Start-Process "shell:AppsFolder\\$($pkg.PackageFamilyName)!$id" } }'].join("\n"),6e4)}for(let e=0;e<20;e++){if(isDesktopRunning())return!0;i(500)}return!1}export function restartDesktop(){return!!canRestartDesktop()&&!(!quitDesktop()&&a)&&launchDesktop()}
@@ -1 +1 @@
1
- import{spawnSync as r}from"child_process";import{mkdtempSync as e,rmSync as t,writeFileSync as o}from"fs";import{tmpdir as n}from"os";import{join as i}from"path";import{logger as s}from"../../utils/logger.js";import{CLI_NAME as u}from"../brand.js";function f(r){return`"${r.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`}export function buildRegFile(r,e,t,o=!1){const n=`HKEY_USERS\\${r}\\${e}`;return o?`Windows Registry Editor Version 5.00\r\n\r\n[-${n}]\r\n`:`Windows Registry Editor Version 5.00\r\n\r\n[${n}]\r\n${Object.entries(t).map(([r,e])=>`${f(r)}=${null===e?"-":function(r){return"boolean"==typeof r?"dword:"+(r?"00000001":"00000000"):"number"==typeof r?`dword:${(r>>>0).toString(16).padStart(8,"0")}`:f("string"==typeof r?r:JSON.stringify(r))}(e)}`).join("\r\n")}\r\n`}let d=()=>{const e=r("whoami",["/user","/fo","csv","/nh"],{encoding:"utf-8",windowsHide:!0});return/"(S-1-[\d-]+)"/.exec(`${e.stdout??""}`)?.[1]??null},l=e=>{const t=r("powershell.exe",["-NoProfile","-NonInteractive","-Command",`$p = Start-Process -FilePath 'reg.exe' -ArgumentList @('import', '"${e}"') -Verb RunAs -Wait -PassThru -WindowStyle Hidden; exit $p.ExitCode`],{encoding:"utf-8",windowsHide:!0,timeout:3e5});return 0!==t.status&&s.logError("elevatedImport",new Error(`${t.stderr??""}`.trim()||`exit ${t.status}`)),0===t.status};export function setElevationForTests(r,e){d=r,l=e}export function setUserRegistryElevated(r,s,f=!1){const c=d();if(!c)return!1;const m=e(i(n(),`${u}-reg-`)),a=i(m,`${u}.reg`);try{return o(a,Buffer.concat([Buffer.from([255,254]),Buffer.from(buildRegFile(c,r,s,f),"utf16le")])),l(a)}finally{t(m,{recursive:!0,force:!0})}}
1
+ import{spawnSync as r}from"child_process";import{mkdtempSync as e,rmSync as t,writeFileSync as o}from"fs";import{tmpdir as n}from"os";import{join as i}from"path";import{logger as s}from"../../utils/logger.js";import{CLI_NAME as u}from"../brand.js";import{powerShellEnv as f}from"../powershell-env.js";function d(r){return`"${r.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`}export function buildRegFile(r,e,t,o=!1){const n=`HKEY_USERS\\${r}\\${e}`;return o?`Windows Registry Editor Version 5.00\r\n\r\n[-${n}]\r\n`:`Windows Registry Editor Version 5.00\r\n\r\n[${n}]\r\n${Object.entries(t).map(([r,e])=>`${d(r)}=${null===e?"-":function(r){return"boolean"==typeof r?"dword:"+(r?"00000001":"00000000"):"number"==typeof r?`dword:${(r>>>0).toString(16).padStart(8,"0")}`:d("string"==typeof r?r:JSON.stringify(r))}(e)}`).join("\r\n")}\r\n`}let l=()=>{const e=r("whoami",["/user","/fo","csv","/nh"],{encoding:"utf-8",windowsHide:!0});return/"(S-1-[\d-]+)"/.exec(`${e.stdout??""}`)?.[1]??null},m=e=>{const t=r("powershell.exe",["-NoProfile","-NonInteractive","-Command",`$p = Start-Process -FilePath 'reg.exe' -ArgumentList @('import', '"${e}"') -Verb RunAs -Wait -PassThru -WindowStyle Hidden; exit $p.ExitCode`],{encoding:"utf-8",windowsHide:!0,timeout:3e5,env:f()});return 0!==t.status&&s.logError("elevatedImport",new Error(`${t.stderr??""}`.trim()||`exit ${t.status}`)),0===t.status};export function setElevationForTests(r,e){l=r,m=e}export function setUserRegistryElevated(r,s,f=!1){const d=l();if(!d)return!1;const c=e(i(n(),`${u}-reg-`)),p=i(c,`${u}.reg`);try{return o(p,Buffer.concat([Buffer.from([255,254]),Buffer.from(buildRegFile(d,r,s,f),"utf16le")])),m(p)}finally{t(c,{recursive:!0,force:!0})}}
@@ -77,3 +77,11 @@ export declare function setVersionRunnerForTests(fake: (path: string) => string)
77
77
  export declare function forgetInstalls(): void;
78
78
  /** In PATH order (the first one is what the command starts), one entry per real file. Probed once per run. */
79
79
  export declare function findInstalls(spec: ToolInstallSpec): ToolInstall[];
80
+ /**
81
+ * Like `findInstalls`, plus the vendor's native launcher when it exists but isn't on this process's PATH (just
82
+ * installed by the mirror script into ~/.local/bin, which a fresh shell profile only adds in new terminals).
83
+ * Such a copy comes last; `onPath` tells the caller to say "open a new terminal".
84
+ */
85
+ export declare function findInstallsIncludingNative(spec: ToolInstallSpec): Array<ToolInstall & {
86
+ onPath: boolean;
87
+ }>;
@@ -1 +1 @@
1
- import{spawnSync as t}from"child_process";import{existsSync as r,realpathSync as n,statSync as e}from"fs";import{delimiter as o,dirname as s,isAbsolute as i,join as a}from"path";import{i18n as c}from"../i18n.js";import{toolHome as p}from"./types.js";import{brand as m}from"../brand.js";export const NPM_MIRROR="https://registry.npmmirror.com";export function brandInstallScripts(t){const r=m().installMirror?.scripts;return r?{sh:`${r}/${t}.sh`,ps1:`${r}/${t}.ps1`}:void 0}export function npmMirrorInstallCommand(t,r="latest"){return`npm install -g ${t.npmPackage}@${r} --registry=${NPM_MIRROR}`}const l=/^(\d+)\.(\d+)\.(\d+)$/;function u(t,r){const n=l.exec(t).slice(1).map(Number),e=l.exec(r).slice(1).map(Number);for(let o=0;o<3;o++)if(n[o]!==e[o])return n[o]>e[o]?t:r;return t}async function f(t,r,n){const e=await fetch(t,{signal:AbortSignal.timeout(r),headers:n});if(!e.ok)throw new Error(`HTTP ${e.status}`);return e.json()}export async function npmMirrorVersion(t){if(!t.npmPackage||!t.npmMirrorTagStale)return"latest";let r=d.get(t.npmPackage);return r||d.set(t.npmPackage,r=async function(t){try{const{version:r}=await f(`https://registry.npmjs.org/${t}/latest`,1e4);if("string"==typeof r&&l.test(r))return r}catch{}try{const r=await f(`${NPM_MIRROR}/${t}`,2e4,{accept:"application/vnd.npm.install-v1+json"}),n=Object.keys(r.versions??{}).filter(t=>l.test(t));if(n.length)return n.reduce(u)}catch{}return"latest"}(t.npmPackage)),r}const d=new Map;export function mirrorInstallCommand(t){return t.mirrorScript?"win32"===process.platform?`powershell -NoProfile -ExecutionPolicy Bypass -Command "irm ${t.mirrorScript.ps1} | iex"`:`curl -fsSL ${t.mirrorScript.sh} | sh`:t.npmPackage?npmMirrorInstallCommand(t):""}export function npmUninstallCommand(t){return`npm uninstall -g ${t.npmPackage}`}export function installLabel(t){return c.t("upgrade.install_label",{kind:c.t(`upgrade.kind.${t.kind}`),version:t.version??"?",path:t.path})}let g=r=>{const n={encoding:"utf-8",timeout:1e4,windowsHide:!0,stdio:["ignore","pipe","pipe"]};return`${(/\.cmd$/i.test(r)?t(`"${r}" --version`,{...n,shell:!0}):t(r,["--version"],n)).stdout??""}`};const h=new Map;export function setVersionRunnerForTests(t){g=t,h.clear(),d.clear()}export function forgetInstalls(){h.clear()}function $(t){try{return e(t).isFile()}catch{return!1}}function w(t,e){if(t.native&&(o=s(e),i=a(p(),...t.native.binDir),"win32"===process.platform?o.toLowerCase()===i.toLowerCase():o===i))return"native";var o,i;const c=t.npmPackage?a("node_modules",...t.npmPackage.split("/")):null;if(c&&r(a(s(e),c)))return"npm";let m=e;try{m=n(e)}catch{return"other"}return c&&m.includes(c)?"npm":/[\\/](Cellar|Caskroom)[\\/]/.test(m)?"brew":"other"}export function findInstalls(t){const r=h.get(t.command);if(r)return r;const e="win32"===process.platform?[`${t.command}.exe`,`${t.command}.cmd`]:[t.command],s=new Set,c=[];for(const r of(process.env.PATH??"").split(o))if(r&&i(r))for(const o of e){const e=a(r,o);if(!$(e))continue;let i=e;try{i=n(e)}catch{}const p="win32"===process.platform?i.toLowerCase():i;if(s.has(p))continue;s.add(p);const m=g(e).match(/(\d+)\.(\d+)\.(\d+)/);c.push({path:e,kind:w(t,e),version:m?m[0]:null})}return h.set(t.command,c),c}
1
+ import{spawnSync as t}from"child_process";import{existsSync as n,realpathSync as r,statSync as e}from"fs";import{delimiter as o,dirname as s,isAbsolute as i,join as a}from"path";import{i18n as c}from"../i18n.js";import{toolHome as p}from"./types.js";import{brand as m}from"../brand.js";export const NPM_MIRROR="https://registry.npmmirror.com";export function brandInstallScripts(t){const n=m().installMirror?.scripts;return n?{sh:`${n}/${t}.sh`,ps1:`${n}/${t}.ps1`}:void 0}export function npmMirrorInstallCommand(t,n="latest"){return`npm install -g ${t.npmPackage}@${n} --registry=${NPM_MIRROR}`}const l=/^(\d+)\.(\d+)\.(\d+)$/;function u(t,n){const r=l.exec(t).slice(1).map(Number),e=l.exec(n).slice(1).map(Number);for(let o=0;o<3;o++)if(r[o]!==e[o])return r[o]>e[o]?t:n;return t}async function f(t,n,r){const e=await fetch(t,{signal:AbortSignal.timeout(n),headers:r});if(!e.ok)throw new Error(`HTTP ${e.status}`);return e.json()}export async function npmMirrorVersion(t){if(!t.npmPackage||!t.npmMirrorTagStale)return"latest";let n=d.get(t.npmPackage);return n||d.set(t.npmPackage,n=async function(t){try{const{version:n}=await f(`https://registry.npmjs.org/${t}/latest`,1e4);if("string"==typeof n&&l.test(n))return n}catch{}try{const n=await f(`${NPM_MIRROR}/${t}`,2e4,{accept:"application/vnd.npm.install-v1+json"}),r=Object.keys(n.versions??{}).filter(t=>l.test(t));if(r.length)return r.reduce(u)}catch{}return"latest"}(t.npmPackage)),n}const d=new Map;export function mirrorInstallCommand(t){return t.mirrorScript?"win32"===process.platform?`powershell -NoProfile -ExecutionPolicy Bypass -Command "irm ${t.mirrorScript.ps1} | iex"`:`curl -fsSL ${t.mirrorScript.sh} | sh`:t.npmPackage?npmMirrorInstallCommand(t):""}export function npmUninstallCommand(t){return`npm uninstall -g ${t.npmPackage}`}export function installLabel(t){return c.t("upgrade.install_label",{kind:c.t(`upgrade.kind.${t.kind}`),version:t.version??"?",path:t.path})}let h=n=>{const r={encoding:"utf-8",timeout:1e4,windowsHide:!0,stdio:["ignore","pipe","pipe"]};return`${(/\.cmd$/i.test(n)?t(`"${n}" --version`,{...r,shell:!0}):t(n,["--version"],r)).stdout??""}`};const g=new Map;export function setVersionRunnerForTests(t){h=t,g.clear(),d.clear()}export function forgetInstalls(){g.clear()}function $(t,n){return"win32"===process.platform?t.toLowerCase()===n.toLowerCase():t===n}function w(t){try{return e(t).isFile()}catch{return!1}}function v(t,e){if(t.native&&$(s(e),a(p(),...t.native.binDir)))return"native";const o=t.npmPackage?a("node_modules",...t.npmPackage.split("/")):null;if(o&&n(a(s(e),o)))return"npm";let i=e;try{i=r(e)}catch{return"other"}return o&&i.includes(o)?"npm":/[\\/](Cellar|Caskroom)[\\/]/.test(i)?"brew":"other"}export function findInstalls(t){const n=g.get(t.command);if(n)return n;const e="win32"===process.platform?[`${t.command}.exe`,`${t.command}.cmd`]:[t.command],s=new Set,c=[];for(const n of(process.env.PATH??"").split(o))if(n&&i(n))for(const o of e){const e=a(n,o);if(!w(e))continue;let i=e;try{i=r(e)}catch{}const p="win32"===process.platform?i.toLowerCase():i;if(s.has(p))continue;s.add(p);const m=h(e).match(/(\d+)\.(\d+)\.(\d+)/);c.push({path:e,kind:v(t,e),version:m?m[0]:null})}return g.set(t.command,c),c}export function findInstallsIncludingNative(t){const n=findInstalls(t).map(t=>({...t,onPath:!0}));if(!t.native)return n;const r=a(p(),...t.native.binDir),e="win32"===process.platform?[`${t.command}.exe`]:[t.command];for(const t of e){const e=a(r,t);if(!w(e)||n.some(t=>$(t.path,e)))continue;const o=h(e).match(/(\d+)\.(\d+)\.(\d+)/);n.push({path:e,kind:"native",version:o?o[0]:null,onPath:!1})}return n}
@@ -75,6 +75,12 @@ export interface ToolSessionStore {
75
75
  * app's own updater: only when the installed copy is older than the mirror's. Driven by `lib/app-update.ts`.
76
76
  */
77
77
  export interface AppInstaller {
78
+ /** Whose signature is checked (`Anthropic`, `OpenAI`), for progress lines. */
79
+ vendor: string;
80
+ /** About how big the download is here, in MB. */
81
+ sizeMb(): number;
82
+ /** The vendor's download page, named where vibecli can't install the app. */
83
+ downloadPage: string;
78
84
  /** Why this machine can't be served (a locale key under `app_update.reason_*`), null when it can. */
79
85
  blocker(): string | null;
80
86
  /** The mirror's newest version; throws when it can't be read. */
@@ -92,6 +98,29 @@ export interface AppInstaller {
92
98
  location: string;
93
99
  }>;
94
100
  }
101
+ /**
102
+ * A desktop app that uses another tool's config and that vibecli installs / upgrades alongside it (ChatGPT, which
103
+ * runs Codex on `~/.codex`, for the `codex` adapter). Same handling as an adapter's own `appInstaller`.
104
+ */
105
+ export interface CompanionApp {
106
+ /** Id on the command line (`vibecli upgrade chatgpt`). */
107
+ name: string;
108
+ displayName: string;
109
+ appInstaller: AppInstaller;
110
+ installedVersion(): string | null;
111
+ runningApp?: RunningApp;
112
+ }
113
+ /**
114
+ * A running desktop app vibecli may quit (after the user agreed) and start again. `quitRefused()`: the last
115
+ * `quit()` was turned down by the app itself (macOS: a task running or a confirmation open), so it is still
116
+ * running and the user has to confirm the quit in the app; it is never killed then.
117
+ */
118
+ export interface RunningApp {
119
+ isRunning(): boolean;
120
+ quit?(): boolean;
121
+ quitRefused?(): boolean;
122
+ launch?(): boolean;
123
+ }
95
124
  export interface ToolAdapter {
96
125
  /** Stable id used on the CLI and as the config key (e.g. `codex`). */
97
126
  name: string;
@@ -140,14 +169,13 @@ export interface ToolAdapter {
140
169
  * GUI apps that only read their config at start: setup and the wizard offer
141
170
  * to restart a running app after writing it.
142
171
  */
143
- runningApp?: {
144
- isRunning(): boolean;
172
+ runningApp?: RunningApp & {
145
173
  restart(): boolean;
146
- quit?(): boolean;
147
- launch?(): boolean;
148
174
  };
149
175
  /** The app itself can be installed and upgraded by vibecli; see {@link AppInstaller}. */
150
176
  appInstaller?: AppInstaller;
177
+ /** Desktop apps that run on this tool's config; installed by setup and upgraded with it. */
178
+ companionApps?: CompanionApp[];
151
179
  /** Extra lines shown under the "too old" note (e.g. a release channel that hasn't reached it yet). */
152
180
  upgradeHints?(min: string): string[];
153
181
  /**
@@ -1 +1 @@
1
- import{execFileSync as e,spawn as r}from"child_process";import{logger as n}from"../utils/logger.js";import{brandEnvName as t}from"./brand.js";const o={user:"HKCU\\Environment",machine:"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"},s=r=>e("reg.exe",r,{encoding:"utf-8",windowsHide:!0,timeout:15e3,stdio:["ignore","pipe","pipe"]}),i=()=>{const e=r("powershell.exe",["-NoProfile","-NonInteractive","-Command",`[Environment]::SetEnvironmentVariable('${t("ENV_REFRESH")}',$null,'User')`],{detached:!0,stdio:"ignore",windowsHide:!0});e.on("error",e=>n.logError("windowsEnv.broadcast",e)),e.unref()};let a=s,d=i;export function setWindowsEnvRunnersForTests(e,r){a=e??s,d=e?r??(()=>{}):i}export function readWindowsEnv(e,r){let n;try{n=a(["query",o[e],"/v",r])}catch{return}const t=n.match(new RegExp(String.raw`^\s*${r}\s+REG_(?:EXPAND_)?SZ\s+(.*?)\r?$`,"im"));return t?t[1]:void 0}export function addUserPathDir(e){let r="REG_EXPAND_SZ",n="";try{const e=a(["query",o.user,"/v","Path"]).match(/^\s*Path\s+(REG_(?:EXPAND_)?SZ)\s+(.*?)\r?$/im);e&&([,r,n]=e)}catch{}const t=e=>(e=>e.replace(/%([^%]+)%/g,(e,r)=>process.env[r]??e))(e).replace(/[\\/]+$/,"").toLowerCase();return!n.split(";").some(r=>r&&t(r)===t(e))&&(a(["add",o.user,"/v","Path","/t",r,"/d",n?`${n.replace(/;+$/,"")};${e}`:e,"/f"]),!0)}export function setUserEnv(e,r){a(["add",o.user,"/v",e,"/t","REG_SZ","/d",r,"/f"])}export function deleteWindowsEnv(e,r){a(["delete",o[e],"/v",r,"/f"])}export function broadcastEnvChange(){d()}
1
+ import{execFileSync as e,spawn as r}from"child_process";import{logger as n}from"../utils/logger.js";import{brandEnvName as t}from"./brand.js";import{powerShellEnv as o}from"./powershell-env.js";const s={user:"HKCU\\Environment",machine:"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"},i=r=>e("reg.exe",r,{encoding:"utf-8",windowsHide:!0,timeout:15e3,stdio:["ignore","pipe","pipe"]}),a=()=>{const e=r("powershell.exe",["-NoProfile","-NonInteractive","-Command",`[Environment]::SetEnvironmentVariable('${t("ENV_REFRESH")}',$null,'User')`],{detached:!0,stdio:"ignore",windowsHide:!0,env:o()});e.on("error",e=>n.logError("windowsEnv.broadcast",e)),e.unref()};let d=i,c=a;export function setWindowsEnvRunnersForTests(e,r){d=e??i,c=e?r??(()=>{}):a}export function readWindowsEnv(e,r){let n;try{n=d(["query",s[e],"/v",r])}catch{return}const t=n.match(new RegExp(String.raw`^\s*${r}\s+REG_(?:EXPAND_)?SZ\s+(.*?)\r?$`,"im"));return t?t[1]:void 0}export function addUserPathDir(e){let r="REG_EXPAND_SZ",n="";try{const e=d(["query",s.user,"/v","Path"]).match(/^\s*Path\s+(REG_(?:EXPAND_)?SZ)\s+(.*?)\r?$/im);e&&([,r,n]=e)}catch{}const t=e=>(e=>e.replace(/%([^%]+)%/g,(e,r)=>process.env[r]??e))(e).replace(/[\\/]+$/,"").toLowerCase();return!n.split(";").some(r=>r&&t(r)===t(e))&&(d(["add",s.user,"/v","Path","/t",r,"/d",n?`${n.replace(/;+$/,"")};${e}`:e,"/f"]),!0)}export function setUserEnv(e,r){d(["add",s.user,"/v",e,"/t","REG_SZ","/d",r,"/f"])}export function deleteWindowsEnv(e,r){d(["delete",s[e],"/v",r,"/f"])}export function broadcastEnvChange(){c()}
@@ -1 +1 @@
1
- import e from"inquirer";import t from"chalk";import a from"ora";import{configManager as o,DEFAULT_MODELS as i}from"./config.js";import{getPresets as n,resolvePreset as r,getToolDefaultModel as s}from"./model-registry.js";import{toolManager as l,SUPPORTED_TOOLS as c}from"./tool-manager.js";import{confirmWritePlan as d}from"./write-plan.js";import{postApplyPrompts as p}from"./post-apply.js";import{appStatus as m,appStatusText as u,appTools as _,cachedAppStatus as g,upgradeApp as w}from"./app-update.js";import{i18n as f,detectSystemLang as y}from"./i18n.js";import{createBorderLine as h,createContentLine as z}from"../utils/string-width.js";import{validateApiKey as v,fetchModels as k}from"./api-validator.js";import{CODEX_ONE_MILLION_CONTEXT_SETTING as x}from"./tools/codex.js";import{CLAUDE_DESKTOP_HIDE_OFFICIAL_LOGIN_SETTING as b}from"./tools/claude-desktop.js";import{addressesWithCurrent as S,ensureReachableGatewayAddress as C,fastestIndex as M,probeAll as T,probeSummary as A,reasonText as N,savedApiKey as K,switchGatewayAddress as B}from"../commands/base.js";import{readFileSync as P}from"fs";import{dirname as j,join as H}from"path";import{fileURLToPath as O}from"url";import{brand as W}from"./brand.js";const L=j(O(import.meta.url)),F=t.hex("#FF8700");export class Wizard{static instance;BOX_WIDTH=63;validatedApiKeys=new Set;constructor(){}static getInstance(){return Wizard.instance||(Wizard.instance=new Wizard),Wizard.instance}createBox(e){console.log(F.bold("\n"+h("╔","╗","═",this.BOX_WIDTH))),console.log(F.bold(z(e,"║","║",this.BOX_WIDTH,"center"))),console.log(F.bold(h("╚","╝","═",this.BOX_WIDTH))),console.log("")}showOperationHints(){const e=[t.gray(f.t("wizard.hint_navigate")),t.gray(f.t("wizard.hint_confirm"))];console.log(t.gray("💡 ")+e.join(t.gray(" | "))+"\n")}async promptWithHints(t){this.showOperationHints();const a=t.map(e=>"list"!==e.type||e.pageSize?e:{...e,pageSize:20});return e.prompt(a)}printBanner(){const e=60,t=f.t("wizard.banner_subtitle"),a=z(t,"║","║",e,"center"),o=z("","║","║",e,"center"),i=z(`${W().productName} v${function(){try{return JSON.parse(P(H(L,"..","..","package.json"),"utf-8")).version}catch{return"0.0.0"}}()}`,"║","║",e,"center"),n=[String.raw`__ ___ _ ____ _ ___ `,String.raw`\ \ / (_) |__ ___ / ___| | |_ _|`,String.raw` \ \ / /| | '_ \ / _ \ | | | | | `,String.raw` \ V / | | |_) | __/ |___| |___ | | `,String.raw` \_/ |_|_.__/ \___|\____|_____|___|`],r=Math.max(...n.map(e=>e.length)),s=" ".repeat(Math.floor((58-r)/2)),l=n.map(t=>z(s+t,"║","║",e,"left")),c=[h("╔","╗","═",e),o,...l,o,i,a,h("╚","╝","═",e)];console.log(F.bold("\n"+c.join("\n")))}resetScreen(){console.clear(),this.printBanner()}printGoodbye(){const e=l.getSupportedTools().filter(e=>{if(e.gui)return!1;try{return Boolean(l.detectCurrentConfig(e.name).apiKey)&&l.isToolInstalled(e.name)}catch{return!1}});let a;if(e.length>0){const t=e.map(e=>e.command).join(" / ");a=f.t("wizard.goodbye_message_tools",{commands:t}),e.some(e=>"claude-code"===e.name)&&(a+=" "+f.t("wizard.goodbye_claude_hint"))}else a=f.t("wizard.goodbye_message");console.log(t.green("\n👋 "+a)),this.printPathReloadHint(e.map(e=>e.name))}printPathReloadHint(e){const a=e.filter(e=>!l.isOnPath(e));if(0===a.length)return;const o=a.map(e=>c[e].command).join(" / ");console.log(t.yellow(" ! "+f.t("win32"===process.platform?"setup.path_reload_windows":"setup.path_reload",{commands:o})))}async runFirstTimeSetup(){const e="zh_CN"===y();e&&(o.setLang("zh_CN"),f.setLocale("zh_CN")),this.resetScreen(),console.log(F.bold("\n"+f.t("wizard.welcome"))),console.log(t.gray(f.t("wizard.privacy_note")+"\n")),e?console.log(t.gray(f.t("wizard.lang_auto_zh")+"\n")):await this.configLanguage(),await C(),await this.configApiKey()&&await this.selectAndConfigureTool(!0)}async configLanguage(){for(;;){this.resetScreen(),this.createBox(f.t("wizard.select_language"));const a=f.getLocale(),{language:i}=await this.promptWithHints([{type:"list",name:"language",message:"✨ "+f.t("wizard.select_language"),choices:[{name:"[EN] English"+("en_US"===a?t.green(" ✓ ("+f.t("wizard.current_active")+")"):""),value:"en_US"},{name:"[CN] 中文"+("zh_CN"===a?t.green(" ✓ ("+f.t("wizard.current_active")+")"):""),value:"zh_CN"},new e.Separator,{name:"<- "+f.t("wizard.nav_return"),value:"back"},{name:"x "+f.t("wizard.nav_exit"),value:"exit"}],default:"zh_CN"}]);if("exit"===i)this.printGoodbye(),process.exit(0);else if("back"===i)return;return o.setLang(i),void f.setLocale(i)}}buildPresetChoices(){const a=n(),o=a.filter(e=>e.recommended),i=a.filter(e=>!e.recommended),r=e=>({name:(f.t(e.name)!==e.name?f.t(e.name):e.name)+(e.models?t.gray(` [H:${e.models.haiku} S:${e.models.sonnet} O:${e.models.opus}]`):""),value:{type:"preset",preset:e}}),s=[];return o.length>0&&(s.push(new e.Separator(t.hex("#FF8700")("── "+f.t("wizard.recommended")+" ──"))),s.push(...o.map(r))),i.length>0&&(s.push(new e.Separator(t.gray("── "+f.t("wizard.other_models")+" ──"))),s.push(...i.map(r))),s}async handleCustomModelInput(){const a=o.getModels(),{customMode:n}=await e.prompt([{type:"list",name:"customMode",message:f.t("wizard.custom_model_mode"),choices:[{name:f.t("wizard.custom_model_single"),value:"single"},{name:f.t("wizard.custom_model_per_role"),value:"per_role"}]}]);if("single"===n){const{customModel:t}=await e.prompt([{type:"input",name:"customModel",message:f.t("wizard.input_custom_model"),default:i.sonnet}]);o.setModels({haiku:t,sonnet:t,opus:t,reasoning:t})}else{const t=await e.prompt([{type:"input",name:"haiku",message:f.t("wizard.model_haiku"),default:a.haiku},{type:"input",name:"sonnet",message:f.t("wizard.model_sonnet"),default:a.sonnet},{type:"input",name:"opus",message:f.t("wizard.model_opus"),default:a.opus},{type:"input",name:"reasoning",message:f.t("wizard.model_reasoning"),default:a.reasoning}]);o.setModels(t)}console.log(t.green("\n✓ "+f.t("wizard.models_saved")))}async configModels(a){const i=o.getApiKey();if(await this.validatedKeyForConfiguration(i))for(;;){this.resetScreen(),this.createBox(f.t("wizard.config_models"));const i=[...this.buildPresetChoices()];i.push(new e.Separator),i.push({name:"✏️ "+f.t("wizard.custom_model_input"),value:{type:"custom_input"}}),i.push(new e.Separator),i.push({name:"<- "+f.t("wizard.nav_return"),value:{type:"back"}}),i.push({name:"x "+f.t("wizard.nav_exit"),value:{type:"exit"}});const{modelChoice:n}=await this.promptWithHints([{type:"list",name:"modelChoice",message:f.t("wizard.select_model_config"),choices:i}]);if("exit"===n.type)this.printGoodbye(),process.exit(0);else{if("back"===n.type)return;if("custom_input"===n.type)return await this.handleCustomModelInput(),void(a?.skipLoadPrompt||await this.promptLoadAfterModelConfig());if("preset"===n.type){const e=r(n.preset);return o.setModels(e),console.log(t.green("\n✓ "+f.t("wizard.models_saved"))),void(a?.skipLoadPrompt||await this.promptLoadAfterModelConfig())}}}else await e.prompt([{type:"input",name:"continue",message:f.t("wizard.press_enter")}])}async promptLoadAfterModelConfig(){const{shouldLoad:t}=await e.prompt([{type:"list",name:"shouldLoad",message:f.t("wizard.load_config_now"),choices:[{name:f.t("wizard.load_config_yes"),value:!0},{name:f.t("wizard.load_config_later"),value:!1}]}]);t&&await this.selectAndConfigureTool()}async configBaseUrl(){const i=o.getBaseUrl(),n=S(),r=a(f.t("wizard.base_testing")).start(),s=await T(n,{apiKey:K(),samples:3});r.stop();const l=M(s),c=n.map((e,a)=>{const o=e.url===i?t.cyan(` ← ${f.t("base.current_mark")}`):"";return{name:`${e.url} ${t.gray(e.label)} ${A(s[a],a===l)}${o}`,value:e.url}});c.push(new e.Separator,{name:"<- "+f.t("wizard.nav_return"),value:"__back__"});const{url:d}=await this.promptWithHints([{type:"list",name:"url",message:f.t("wizard.base_select"),choices:c,default:i}]);if("__back__"===d)return;const p=s[n.findIndex(e=>e.url===d)];p.ok?await B(d):console.log(t.red(f.t("base.unreachable",{url:d,reason:N(p)}))),await e.prompt([{type:"input",name:"continue",message:f.t("wizard.press_enter")}])}async configApiKey(){for(;;){this.resetScreen(),this.createBox(f.t("wizard.input_api_key")),console.log(t.gray("\n💡 "+f.t("wizard.api_key_input_hint"))),console.log(t.gray(" "+f.t("wizard.api_key_get_hint",{url:W().links.tokenPage}))),console.log();const{apiKey:i}=await e.prompt([{type:"password",name:"apiKey",message:f.t("wizard.input_your_api_key"),mask:"*"}]);if(!i||""===i.trim()){const{action:t}=await e.prompt([{type:"list",name:"action",message:f.t("wizard.select_action"),choices:[{name:f.t("wizard.retry_input"),value:"retry"},{name:"<- "+f.t("wizard.nav_return"),value:"back"}]}]);if("back"===t)return!1;continue}const n=a(f.t("wizard.validating_api_key")).start(),r=await v(i.trim());if(n.stop(),r.valid)return o.setApiKey(i.trim()),this.validatedApiKeys.add(i.trim()),console.log(t.green("\n✓ "+f.t("wizard.api_key_valid"))),!0;{this.validatedApiKeys.delete(i.trim()),"invalid_api_key"===r.error?console.log(t.red("\n✗ "+f.t("wizard.api_key_invalid"))):console.log(t.red("\n✗ "+f.t("wizard.api_key_network_error")));const{action:a}=await e.prompt([{type:"list",name:"action",message:f.t("wizard.select_action"),choices:[{name:f.t("wizard.retry_input"),value:"retry"},{name:"<- "+f.t("wizard.nav_return"),value:"back"}]}]);if("back"===a)return!1}}}async selectAndConfigureTool(a=!1){for(const e of _())await m(e).catch(()=>{});for(;;){this.resetScreen(),this.createBox(f.t("wizard.select_tool"));const o=l.getSupportedTools().map(e=>{const a=_().includes(e.name)?g(e.name):null;return{name:e.displayName+(a?.outdated?t.yellow(" "+u(a)):""),value:e.name}}),{tool:i}=await this.promptWithHints([{type:"list",name:"tool",message:"🛠️ "+f.t("wizard.select_tool"),choices:[...o,new e.Separator,{name:"<- "+f.t("wizard.nav_return"),value:"back"},{name:"x "+f.t("wizard.nav_exit"),value:"exit"}]}]);if("exit"===i)this.printGoodbye(),process.exit(0);else if("back"===i)return;await this.configureTool(i,a)}}async configureTool(a,o=!1){const i=c[a];if(i){if(!l.isToolInstalled(a)){console.log(t.yellow("\n"+f.t("wizard.tool_not_installed",{tool:i.displayName})));const{install:o}=await e.prompt([{type:"confirm",name:"install",message:f.t("wizard.install_tool_confirm"),default:!0}]);if(!o)return;try{await l.installTool(a)}catch{return void console.log(t.red(f.t("wizard.install_failed")))}}o?await this.configureToolModel(a)&&await this.loadConfig(a):await this.showToolMenu(a)}}async showToolMenu(t){for(;;){this.resetScreen();const a=c[t];if(!a)return;this.createBox(a.displayName+" "+f.t("wizard.menu_title"));const o=[{name:"📥 "+f.t("wizard.action_load_config",{tool:a.displayName}),value:"load"},{name:"📤 "+f.t("wizard.action_unload_config",{tool:a.displayName}),value:"unload"}];l.supportsClearPinnedModels(t)&&o.push({name:"🧹 "+f.t("wizard.action_clear_model",{tool:a.displayName}),value:"clear-model"});const i=_().includes(t)?g(t):null;if(i&&!i.blocker&&i.latest&&(i.outdated||!i.installed)){const e=i.installed?f.t("wizard.action_upgrade_app",{tool:a.displayName,current:i.current??"?",latest:i.latest}):f.t("wizard.action_install_app",{tool:a.displayName,latest:i.latest});o.unshift({name:"↑ "+e,value:"upgrade-app"})}o.push({name:"🧨 "+f.t("wizard.action_force_config",{tool:a.displayName}),value:"force"},{name:"↩️ "+f.t("wizard.action_restore_config",{tool:a.displayName}),value:"restore"},new e.Separator,{name:"<- "+f.t("wizard.nav_return"),value:"back"},{name:"x "+f.t("wizard.nav_exit"),value:"exit"});const{action:n}=await this.promptWithHints([{type:"list",name:"action",message:"⚙️ "+f.t("wizard.select_action"),choices:o}]);if("exit"===n)this.printGoodbye(),process.exit(0);else{if("back"===n)return;"load"===n?await this.configureToolModel(t)&&await this.loadConfig(t):"force"===n?await this.forceConfig(t):"unload"===n?await this.unloadConfig(t):"upgrade-app"===n?(await w(t,{install:!0}),await e.prompt([{type:"input",name:"continue",message:f.t("wizard.press_enter")}])):"clear-model"===n?await this.clearPinnedModels(t):"restore"===n&&await this.restoreConfig(t)}}}singleModelOverride(e){const t=e.trim();return/claude/i.test(t)?{opus:t}:{haiku:t,sonnet:t,opus:t,reasoning:t}}hasConfiguredApiKey(){return!!o.getApiKey()?.trim()||o.listToolApiKeys().some(e=>Boolean(o.getApiKeyForTool(e)?.trim()))}async validatedKeyForConfiguration(e){const o=e?.trim();if(!o)return console.log(t.red("\n"+f.t("wizard.api_key_required"))),console.log(t.yellow(f.t("wizard.please_config_api_key_first"))),null;if(this.validatedApiKeys.has(o))return o;const i=a(f.t("wizard.validating_api_key")).start(),n=await v(o);if(n.valid)return this.validatedApiKeys.add(o),i.succeed(f.t("wizard.api_key_valid")),o;this.validatedApiKeys.delete(o);const r="invalid_api_key"===n.error?f.t("wizard.api_key_invalid"):f.t("wizard.api_key_network_error");return i.fail(r),console.log(t.red("\n"+f.t("wizard.please_reconfig_api_key"))),null}async configureToolModel(i){const n=c[i];if(!n)return!1;const l=await this.validatedKeyForConfiguration(o.getApiKeyForTool(i));if(!l)return await e.prompt([{type:"input",name:"continue",message:f.t("wizard.press_enter")}]),!1;const d="claude-code"===i,p=d||"claude-desktop"===i||"agy"===i,m=s(i),u=p||!m?f.t("wizard.model_builtin_default"):m;this.resetScreen(),this.createBox(n.displayName+" · "+f.t("wizard.config_models"));const _=[{name:"✅ "+f.t("wizard.use_default_model",{model:u}),value:{type:"default"}},{name:"🌐 "+f.t("wizard.fetch_models_from_api"),value:{type:"fetch"}},...this.buildPresetChoices(),new e.Separator,{name:"✏️ "+f.t("wizard.input_custom_model"),value:{type:"custom"}},new e.Separator,{name:"<- "+f.t("wizard.nav_return"),value:{type:"back"}}],{choice:g}=await this.promptWithHints([{type:"list",name:"choice",message:f.t("wizard.select_tool_model",{tool:n.displayName}),choices:_}]);if("back"===g.type)return!1;if("default"===g.type)o.setToolModel(i,null);else if("preset"===g.type)o.setToolModel(i,r(g.preset));else if("custom"===g.type){const{model:t}=await e.prompt([{type:"input",name:"model",message:f.t("wizard.input_custom_model"),default:!p&&m?m:void 0}]),a=(t||"").trim();o.setToolModel(i,a?this.singleModelOverride(a):null)}else if("fetch"===g.type){const n=a(f.t("wizard.fetching_models")).start(),r=await k(l);let s;if(n.stop(),r.ok&&r.models.length>0){const{model:t}=await e.prompt([{type:"list",name:"model",message:f.t("wizard.select_fetched_model"),pageSize:20,choices:[...r.models,new e.Separator,{name:"<- "+f.t("wizard.nav_return"),value:"__back__"}]}]);if("__back__"===t)return!1;s=t}else{console.log(t.yellow("\n"+f.t("wizard.fetch_models_failed")));const{model:a}=await e.prompt([{type:"input",name:"model",message:f.t("wizard.input_custom_model"),default:!p&&m?m:void 0}]);s=(a||"").trim()}o.setToolModel(i,s?this.singleModelOverride(s):null)}if("codex"===i){const t=o.getToolSetting(i,x),{enableOneMillionContext:a}=await e.prompt([{type:"confirm",name:"enableOneMillionContext",message:f.t("wizard.codex_enable_1m_context"),default:!1!==t}]);o.setToolSetting(i,x,Boolean(a))}if("claude-desktop"===i){const t=o.getToolSetting(i,b),{hideOfficialLogin:a}=await e.prompt([{type:"confirm",name:"hideOfficialLogin",message:f.t("wizard.desktop_hide_official_login"),default:!1!==t}]);o.setToolSetting(i,b,Boolean(a))}return console.log(t.green("\n✓ "+f.t("wizard.models_saved"))),d&&console.log(t.gray("💡 "+f.t("wizard.claude_model_hint"))),!0}async forceConfig(t){const a=c[t]?.displayName||t,{confirm:o}=await e.prompt([{type:"confirm",name:"confirm",message:f.t("config.confirm_force",{tool:a}),default:!1}]);o&&await this.loadConfig(t,{force:!0})}async loadConfig(i,n={}){const r=o.getModelsForTool(i),s=await this.validatedKeyForConfiguration(o.getApiKeyForTool(i));if(!s)return void await e.prompt([{type:"input",name:"continue",message:f.t("wizard.press_enter")}]);if(!n.force&&!await d([i]))return;const m=a(f.t("wizard.loading_config")).start();try{await l.applyPrivateConfig(i,s,r,n),m.stop(),l.printApplied(i),await p([i],{apps:{install:!1}}),n.force&&console.log(t.yellow(f.t("config.force_done",{tool:c[i]?.displayName||i,name:i})));const a=c[i];a&&!a.gui&&(l.isOnPath(i)&&console.log(t.green("\n"+f.t("wizard.run_tool_hint",{command:a.command}))),this.printPathReloadHint([i]));const{nextAction:o}=await e.prompt([{type:"list",name:"nextAction",message:f.t("wizard.what_next"),choices:[{name:f.t("wizard.exit_and_start"),value:"exit"},{name:f.t("wizard.continue_config"),value:"continue"}]}]);"exit"===o&&(this.printGoodbye(),process.exit(0))}catch(a){m.fail(f.t("wizard.config_failed")),console.log(t.red(a instanceof Error?a.message:String(a))),await e.prompt([{type:"input",name:"continue",message:f.t("wizard.press_enter")}])}}async clearPinnedModels(a){const o=c[a]?.displayName||a,{confirm:i}=await e.prompt([{type:"confirm",name:"confirm",message:f.t("wizard.confirm_clear_model",{tool:o}),default:!1}]);if(i){try{const e=await l.clearPinnedModels(a);0===e.length?console.log(t.yellow("\n"+f.t("config.clear_model_none",{tool:o}))):(console.log(t.green("\n✓ "+f.t("config.clear_model_done",{tool:o}))),e.forEach(e=>console.log(t.gray(` - ${e}`))),console.log(t.gray(f.t("config.clear_model_restart_hint",{tool:o}))))}catch(e){console.log(t.red("\n"+(e instanceof Error?e.message:String(e))))}await e.prompt([{type:"input",name:"continue",message:f.t("wizard.press_enter")}])}}async unloadConfig(o){const{confirm:i}=await e.prompt([{type:"confirm",name:"confirm",message:f.t("wizard.confirm_unload_config",{tool:c[o]?.displayName||o}),default:!1}]);if(!i)return;const n=a(f.t("wizard.unloading_config")).start();try{const a=l.holdsOurConfig(o),i=l.unloadPrivateConfig(o);if(!a)return void n.info(f.t("config.remove_nothing",{tool:c[o]?.displayName||o}));n.succeed(f.t("wizard.config_unloaded")),i.forEach(e=>console.log(t.yellow(" ! "+e.text)));const{nextAction:r}=await e.prompt([{type:"list",name:"nextAction",message:f.t("wizard.what_next"),choices:[{name:f.t("wizard.nav_exit"),value:"exit"},{name:f.t("wizard.continue_config"),value:"continue"}]}]);"exit"===r&&(this.printGoodbye(),process.exit(0))}catch(a){n.fail(f.t("wizard.config_unload_failed")),console.log(t.red(a instanceof Error?a.message:String(a))),await e.prompt([{type:"input",name:"continue",message:f.t("wizard.press_enter")}])}}async restoreConfig(o){const i=c[o]?.displayName||o,{confirm:n}=await e.prompt([{type:"confirm",name:"confirm",message:f.t("wizard.confirm_restore_config",{tool:i}),default:!1}]);if(!n)return;const r=a(f.t("wizard.restoring_config")).start();try{const e=l.restorePrivateConfig(o).filter(e=>e.result.restored);0===e.length?r.warn(f.t("wizard.restore_no_backup")):(r.succeed(f.t("wizard.config_restored",{tool:i})),e.forEach(e=>console.log(t.gray(` ${e.file} ← ${e.result.from}`))),console.log(t.yellow(" ! "+f.t("config.reopen_hint",{tool:i}))))}catch(e){r.fail(f.t("wizard.config_restore_failed")),console.log(t.red(e instanceof Error?e.message:String(e)))}await e.prompt([{type:"input",name:"continue",message:f.t("wizard.press_enter")}])}async run(){await this.showMainMenu()}async showMainMenu(){if(this.hasConfiguredApiKey())for(;;){this.resetScreen();const a=o.getApiKey()||"",i=a.length>4?"sk-****"+a.slice(-4):"****";console.log(t.gray(` 🔑 API Key: ${i}`)+t.green(" ✓")),console.log("");const n=l.getSupportedTools().map(e=>({name:"🛠️ "+e.displayName,value:{type:"tool",tool:e.name}})),r=[new e.Separator(F("── "+f.t("wizard.select_tool")+" ──")),...n,new e.Separator(t.gray("── "+f.t("wizard.settings")+" ──")),{name:"🔑 "+f.t("wizard.menu_change_api_key"),value:{type:"apikey"}},{name:"🔗 "+f.t("wizard.menu_base_url",{url:o.getBaseUrl()}),value:{type:"base"}},{name:"🌐 "+f.t("wizard.menu_config_language"),value:{type:"language"}},new e.Separator,{name:"x "+f.t("wizard.nav_exit"),value:{type:"exit"}}],{action:s}=await this.promptWithHints([{type:"list",name:"action",message:"🛠️ "+f.t("wizard.select_tool"),choices:r}]);"exit"===s.type?(this.printGoodbye(),process.exit(0)):"tool"===s.type?await this.configureTool(s.tool):"apikey"===s.type?await this.configApiKey():"base"===s.type?await this.configBaseUrl():"language"===s.type&&await this.configLanguage()}else await this.runFirstTimeSetup()}}export const wizard=Wizard.getInstance();
1
+ import e from"inquirer";import t from"chalk";import a from"ora";import{configManager as o,DEFAULT_MODELS as i}from"./config.js";import{getPresets as n,resolvePreset as r,getToolDefaultModel as s}from"./model-registry.js";import{toolManager as l,SUPPORTED_TOOLS as c}from"./tool-manager.js";import{confirmWritePlan as d}from"./write-plan.js";import{postApplyPrompts as p}from"./post-apply.js";import{appStatus as m,appStatusText as u,appTools as _,appsForTools as g,cachedAppStatus as w,upgradeApp as f}from"./app-update.js";import{i18n as y,detectSystemLang as h}from"./i18n.js";import{createBorderLine as z,createContentLine as v}from"../utils/string-width.js";import{validateApiKey as k,fetchModels as x}from"./api-validator.js";import{CODEX_ONE_MILLION_CONTEXT_SETTING as b}from"./tools/codex.js";import{CLAUDE_DESKTOP_HIDE_OFFICIAL_LOGIN_SETTING as S}from"./tools/claude-desktop.js";import{addressesWithCurrent as C,ensureReachableGatewayAddress as M,fastestIndex as T,probeAll as A,probeSummary as K,reasonText as N,savedApiKey as B,switchGatewayAddress as P}from"../commands/base.js";import{readFileSync as W}from"fs";import{dirname as j,join as H}from"path";import{fileURLToPath as O}from"url";import{brand as L}from"./brand.js";const $=j(O(import.meta.url)),F=t.hex("#FF8700");export class Wizard{static instance;BOX_WIDTH=63;validatedApiKeys=new Set;constructor(){}static getInstance(){return Wizard.instance||(Wizard.instance=new Wizard),Wizard.instance}createBox(e){console.log(F.bold("\n"+z("╔","╗","═",this.BOX_WIDTH))),console.log(F.bold(v(e,"║","║",this.BOX_WIDTH,"center"))),console.log(F.bold(z("╚","╝","═",this.BOX_WIDTH))),console.log("")}showOperationHints(){const e=[t.gray(y.t("wizard.hint_navigate")),t.gray(y.t("wizard.hint_confirm"))];console.log(t.gray("💡 ")+e.join(t.gray(" | "))+"\n")}async promptWithHints(t){this.showOperationHints();const a=t.map(e=>"list"!==e.type||e.pageSize?e:{...e,pageSize:20});return e.prompt(a)}printBanner(){const e=60,t=y.t("wizard.banner_subtitle"),a=v(t,"║","║",e,"center"),o=v("","║","║",e,"center"),i=v(`${L().productName} v${function(){try{return JSON.parse(W(H($,"..","..","package.json"),"utf-8")).version}catch{return"0.0.0"}}()}`,"║","║",e,"center"),n=[String.raw`__ ___ _ ____ _ ___ `,String.raw`\ \ / (_) |__ ___ / ___| | |_ _|`,String.raw` \ \ / /| | '_ \ / _ \ | | | | | `,String.raw` \ V / | | |_) | __/ |___| |___ | | `,String.raw` \_/ |_|_.__/ \___|\____|_____|___|`],r=Math.max(...n.map(e=>e.length)),s=" ".repeat(Math.floor((58-r)/2)),l=n.map(t=>v(s+t,"║","║",e,"left")),c=[z("╔","╗","═",e),o,...l,o,i,a,z("╚","╝","═",e)];console.log(F.bold("\n"+c.join("\n")))}resetScreen(){console.clear(),this.printBanner()}printGoodbye(){const e=l.getSupportedTools().filter(e=>{if(e.gui)return!1;try{return Boolean(l.detectCurrentConfig(e.name).apiKey)&&l.isToolInstalled(e.name)}catch{return!1}});let a;if(e.length>0){const t=e.map(e=>e.command).join(" / ");a=y.t("wizard.goodbye_message_tools",{commands:t}),e.some(e=>"claude-code"===e.name)&&(a+=" "+y.t("wizard.goodbye_claude_hint"))}else a=y.t("wizard.goodbye_message");console.log(t.green("\n👋 "+a)),this.printPathReloadHint(e.map(e=>e.name))}printPathReloadHint(e){const a=e.filter(e=>!l.isOnPath(e));if(0===a.length)return;const o=a.map(e=>c[e].command).join(" / ");console.log(t.yellow(" ! "+y.t("win32"===process.platform?"setup.path_reload_windows":"setup.path_reload",{commands:o})))}async runFirstTimeSetup(){const e="zh_CN"===h();e&&(o.setLang("zh_CN"),y.setLocale("zh_CN")),this.resetScreen(),console.log(F.bold("\n"+y.t("wizard.welcome"))),console.log(t.gray(y.t("wizard.privacy_note")+"\n")),e?console.log(t.gray(y.t("wizard.lang_auto_zh")+"\n")):await this.configLanguage(),await M(),await this.configApiKey()&&await this.selectAndConfigureTool(!0)}async configLanguage(){for(;;){this.resetScreen(),this.createBox(y.t("wizard.select_language"));const a=y.getLocale(),{language:i}=await this.promptWithHints([{type:"list",name:"language",message:"✨ "+y.t("wizard.select_language"),choices:[{name:"[EN] English"+("en_US"===a?t.green(" ✓ ("+y.t("wizard.current_active")+")"):""),value:"en_US"},{name:"[CN] 中文"+("zh_CN"===a?t.green(" ✓ ("+y.t("wizard.current_active")+")"):""),value:"zh_CN"},new e.Separator,{name:"<- "+y.t("wizard.nav_return"),value:"back"},{name:"x "+y.t("wizard.nav_exit"),value:"exit"}],default:"zh_CN"}]);if("exit"===i)this.printGoodbye(),process.exit(0);else if("back"===i)return;return o.setLang(i),void y.setLocale(i)}}buildPresetChoices(){const a=n(),o=a.filter(e=>e.recommended),i=a.filter(e=>!e.recommended),r=e=>({name:(y.t(e.name)!==e.name?y.t(e.name):e.name)+(e.models?t.gray(` [H:${e.models.haiku} S:${e.models.sonnet} O:${e.models.opus}]`):""),value:{type:"preset",preset:e}}),s=[];return o.length>0&&(s.push(new e.Separator(t.hex("#FF8700")("── "+y.t("wizard.recommended")+" ──"))),s.push(...o.map(r))),i.length>0&&(s.push(new e.Separator(t.gray("── "+y.t("wizard.other_models")+" ──"))),s.push(...i.map(r))),s}async handleCustomModelInput(){const a=o.getModels(),{customMode:n}=await e.prompt([{type:"list",name:"customMode",message:y.t("wizard.custom_model_mode"),choices:[{name:y.t("wizard.custom_model_single"),value:"single"},{name:y.t("wizard.custom_model_per_role"),value:"per_role"}]}]);if("single"===n){const{customModel:t}=await e.prompt([{type:"input",name:"customModel",message:y.t("wizard.input_custom_model"),default:i.sonnet}]);o.setModels({haiku:t,sonnet:t,opus:t,reasoning:t})}else{const t=await e.prompt([{type:"input",name:"haiku",message:y.t("wizard.model_haiku"),default:a.haiku},{type:"input",name:"sonnet",message:y.t("wizard.model_sonnet"),default:a.sonnet},{type:"input",name:"opus",message:y.t("wizard.model_opus"),default:a.opus},{type:"input",name:"reasoning",message:y.t("wizard.model_reasoning"),default:a.reasoning}]);o.setModels(t)}console.log(t.green("\n✓ "+y.t("wizard.models_saved")))}async configModels(a){const i=o.getApiKey();if(await this.validatedKeyForConfiguration(i))for(;;){this.resetScreen(),this.createBox(y.t("wizard.config_models"));const i=[...this.buildPresetChoices()];i.push(new e.Separator),i.push({name:"✏️ "+y.t("wizard.custom_model_input"),value:{type:"custom_input"}}),i.push(new e.Separator),i.push({name:"<- "+y.t("wizard.nav_return"),value:{type:"back"}}),i.push({name:"x "+y.t("wizard.nav_exit"),value:{type:"exit"}});const{modelChoice:n}=await this.promptWithHints([{type:"list",name:"modelChoice",message:y.t("wizard.select_model_config"),choices:i}]);if("exit"===n.type)this.printGoodbye(),process.exit(0);else{if("back"===n.type)return;if("custom_input"===n.type)return await this.handleCustomModelInput(),void(a?.skipLoadPrompt||await this.promptLoadAfterModelConfig());if("preset"===n.type){const e=r(n.preset);return o.setModels(e),console.log(t.green("\n✓ "+y.t("wizard.models_saved"))),void(a?.skipLoadPrompt||await this.promptLoadAfterModelConfig())}}}else await e.prompt([{type:"input",name:"continue",message:y.t("wizard.press_enter")}])}async promptLoadAfterModelConfig(){const{shouldLoad:t}=await e.prompt([{type:"list",name:"shouldLoad",message:y.t("wizard.load_config_now"),choices:[{name:y.t("wizard.load_config_yes"),value:!0},{name:y.t("wizard.load_config_later"),value:!1}]}]);t&&await this.selectAndConfigureTool()}async configBaseUrl(){const i=o.getBaseUrl(),n=C(),r=a(y.t("wizard.base_testing")).start(),s=await A(n,{apiKey:B(),samples:3});r.stop();const l=T(s),c=n.map((e,a)=>{const o=e.url===i?t.cyan(` ← ${y.t("base.current_mark")}`):"";return{name:`${e.url} ${t.gray(e.label)} ${K(s[a],a===l)}${o}`,value:e.url}});c.push(new e.Separator,{name:"<- "+y.t("wizard.nav_return"),value:"__back__"});const{url:d}=await this.promptWithHints([{type:"list",name:"url",message:y.t("wizard.base_select"),choices:c,default:i}]);if("__back__"===d)return;const p=s[n.findIndex(e=>e.url===d)];p.ok?await P(d):console.log(t.red(y.t("base.unreachable",{url:d,reason:N(p)}))),await e.prompt([{type:"input",name:"continue",message:y.t("wizard.press_enter")}])}async configApiKey(){for(;;){this.resetScreen(),this.createBox(y.t("wizard.input_api_key")),console.log(t.gray("\n💡 "+y.t("wizard.api_key_input_hint"))),console.log(t.gray(" "+y.t("wizard.api_key_get_hint",{url:L().links.tokenPage}))),console.log();const{apiKey:i}=await e.prompt([{type:"password",name:"apiKey",message:y.t("wizard.input_your_api_key"),mask:"*"}]);if(!i||""===i.trim()){const{action:t}=await e.prompt([{type:"list",name:"action",message:y.t("wizard.select_action"),choices:[{name:y.t("wizard.retry_input"),value:"retry"},{name:"<- "+y.t("wizard.nav_return"),value:"back"}]}]);if("back"===t)return!1;continue}const n=a(y.t("wizard.validating_api_key")).start(),r=await k(i.trim());if(n.stop(),r.valid)return o.setApiKey(i.trim()),this.validatedApiKeys.add(i.trim()),console.log(t.green("\n✓ "+y.t("wizard.api_key_valid"))),!0;{this.validatedApiKeys.delete(i.trim()),"invalid_api_key"===r.error?console.log(t.red("\n✗ "+y.t("wizard.api_key_invalid"))):console.log(t.red("\n✗ "+y.t("wizard.api_key_network_error")));const{action:a}=await e.prompt([{type:"list",name:"action",message:y.t("wizard.select_action"),choices:[{name:y.t("wizard.retry_input"),value:"retry"},{name:"<- "+y.t("wizard.nav_return"),value:"back"}]}]);if("back"===a)return!1}}}async selectAndConfigureTool(a=!1){for(const e of _())await m(e).catch(()=>{});for(;;){this.resetScreen(),this.createBox(y.t("wizard.select_tool"));const o=l.getSupportedTools().map(e=>{const a=g([e.name]).map(e=>w(e)).find(e=>e.outdated);return{name:e.displayName+(a?t.yellow(" "+(a.tool===e.name?"":`${a.name} `)+u(a)):""),value:e.name}}),{tool:i}=await this.promptWithHints([{type:"list",name:"tool",message:"🛠️ "+y.t("wizard.select_tool"),choices:[...o,new e.Separator,{name:"<- "+y.t("wizard.nav_return"),value:"back"},{name:"x "+y.t("wizard.nav_exit"),value:"exit"}]}]);if("exit"===i)this.printGoodbye(),process.exit(0);else if("back"===i)return;await this.configureTool(i,a)}}async configureTool(a,o=!1){const i=c[a];if(i){if(!l.isToolInstalled(a)){console.log(t.yellow("\n"+y.t("wizard.tool_not_installed",{tool:i.displayName})));const{install:o}=await e.prompt([{type:"confirm",name:"install",message:y.t("wizard.install_tool_confirm"),default:!0}]);if(!o)return;try{await l.installTool(a)}catch{return void console.log(t.red(y.t("wizard.install_failed")))}}o?await this.configureToolModel(a)&&await this.loadConfig(a):await this.showToolMenu(a)}}async showToolMenu(t){for(;;){this.resetScreen();const a=c[t];if(!a)return;this.createBox(a.displayName+" "+y.t("wizard.menu_title"));const o=[{name:"📥 "+y.t("wizard.action_load_config",{tool:a.displayName}),value:"load"},{name:"📤 "+y.t("wizard.action_unload_config",{tool:a.displayName}),value:"unload"}];l.supportsClearPinnedModels(t)&&o.push({name:"🧹 "+y.t("wizard.action_clear_model",{tool:a.displayName}),value:"clear-model"});for(const e of g([t]).map(e=>w(e)).reverse()){if(e.blocker||!e.latest||!e.outdated&&e.installed)continue;const t=e.installed?y.t("wizard.action_upgrade_app",{tool:e.name,current:e.current??"?",latest:e.latest}):y.t("wizard.action_install_app",{tool:e.name,latest:e.latest});o.unshift({name:"↑ "+t,value:`upgrade-app:${e.tool}`})}o.push({name:"🧨 "+y.t("wizard.action_force_config",{tool:a.displayName}),value:"force"},{name:"↩️ "+y.t("wizard.action_restore_config",{tool:a.displayName}),value:"restore"},new e.Separator,{name:"<- "+y.t("wizard.nav_return"),value:"back"},{name:"x "+y.t("wizard.nav_exit"),value:"exit"});const{action:i}=await this.promptWithHints([{type:"list",name:"action",message:"⚙️ "+y.t("wizard.select_action"),choices:o}]);if("exit"===i)this.printGoodbye(),process.exit(0);else{if("back"===i)return;"load"===i?await this.configureToolModel(t)&&await this.loadConfig(t):"force"===i?await this.forceConfig(t):"unload"===i?await this.unloadConfig(t):"string"==typeof i&&i.startsWith("upgrade-app:")?(await f(i.slice(12),{install:!0}),await e.prompt([{type:"input",name:"continue",message:y.t("wizard.press_enter")}])):"clear-model"===i?await this.clearPinnedModels(t):"restore"===i&&await this.restoreConfig(t)}}}singleModelOverride(e){const t=e.trim();return/claude/i.test(t)?{opus:t}:{haiku:t,sonnet:t,opus:t,reasoning:t}}hasConfiguredApiKey(){return!!o.getApiKey()?.trim()||o.listToolApiKeys().some(e=>Boolean(o.getApiKeyForTool(e)?.trim()))}async validatedKeyForConfiguration(e){const o=e?.trim();if(!o)return console.log(t.red("\n"+y.t("wizard.api_key_required"))),console.log(t.yellow(y.t("wizard.please_config_api_key_first"))),null;if(this.validatedApiKeys.has(o))return o;const i=a(y.t("wizard.validating_api_key")).start(),n=await k(o);if(n.valid)return this.validatedApiKeys.add(o),i.succeed(y.t("wizard.api_key_valid")),o;this.validatedApiKeys.delete(o);const r="invalid_api_key"===n.error?y.t("wizard.api_key_invalid"):y.t("wizard.api_key_network_error");return i.fail(r),console.log(t.red("\n"+y.t("wizard.please_reconfig_api_key"))),null}async configureToolModel(i){const n=c[i];if(!n)return!1;const l=await this.validatedKeyForConfiguration(o.getApiKeyForTool(i));if(!l)return await e.prompt([{type:"input",name:"continue",message:y.t("wizard.press_enter")}]),!1;const d="claude-code"===i,p=d||"claude-desktop"===i||"agy"===i,m=s(i),u=p||!m?y.t("wizard.model_builtin_default"):m;this.resetScreen(),this.createBox(n.displayName+" · "+y.t("wizard.config_models"));const _=[{name:"✅ "+y.t("wizard.use_default_model",{model:u}),value:{type:"default"}},{name:"🌐 "+y.t("wizard.fetch_models_from_api"),value:{type:"fetch"}},...this.buildPresetChoices(),new e.Separator,{name:"✏️ "+y.t("wizard.input_custom_model"),value:{type:"custom"}},new e.Separator,{name:"<- "+y.t("wizard.nav_return"),value:{type:"back"}}],{choice:g}=await this.promptWithHints([{type:"list",name:"choice",message:y.t("wizard.select_tool_model",{tool:n.displayName}),choices:_}]);if("back"===g.type)return!1;if("default"===g.type)o.setToolModel(i,null);else if("preset"===g.type)o.setToolModel(i,r(g.preset));else if("custom"===g.type){const{model:t}=await e.prompt([{type:"input",name:"model",message:y.t("wizard.input_custom_model"),default:!p&&m?m:void 0}]),a=(t||"").trim();o.setToolModel(i,a?this.singleModelOverride(a):null)}else if("fetch"===g.type){const n=a(y.t("wizard.fetching_models")).start(),r=await x(l);let s;if(n.stop(),r.ok&&r.models.length>0){const{model:t}=await e.prompt([{type:"list",name:"model",message:y.t("wizard.select_fetched_model"),pageSize:20,choices:[...r.models,new e.Separator,{name:"<- "+y.t("wizard.nav_return"),value:"__back__"}]}]);if("__back__"===t)return!1;s=t}else{console.log(t.yellow("\n"+y.t("wizard.fetch_models_failed")));const{model:a}=await e.prompt([{type:"input",name:"model",message:y.t("wizard.input_custom_model"),default:!p&&m?m:void 0}]);s=(a||"").trim()}o.setToolModel(i,s?this.singleModelOverride(s):null)}if("codex"===i){const t=o.getToolSetting(i,b),{enableOneMillionContext:a}=await e.prompt([{type:"confirm",name:"enableOneMillionContext",message:y.t("wizard.codex_enable_1m_context"),default:!1!==t}]);o.setToolSetting(i,b,Boolean(a))}if("claude-desktop"===i){const t=o.getToolSetting(i,S),{hideOfficialLogin:a}=await e.prompt([{type:"confirm",name:"hideOfficialLogin",message:y.t("wizard.desktop_hide_official_login"),default:!1!==t}]);o.setToolSetting(i,S,Boolean(a))}return console.log(t.green("\n✓ "+y.t("wizard.models_saved"))),d&&console.log(t.gray("💡 "+y.t("wizard.claude_model_hint"))),!0}async forceConfig(t){const a=c[t]?.displayName||t,{confirm:o}=await e.prompt([{type:"confirm",name:"confirm",message:y.t("config.confirm_force",{tool:a}),default:!1}]);o&&await this.loadConfig(t,{force:!0})}async loadConfig(i,n={}){const r=o.getModelsForTool(i),s=await this.validatedKeyForConfiguration(o.getApiKeyForTool(i));if(!s)return void await e.prompt([{type:"input",name:"continue",message:y.t("wizard.press_enter")}]);if(!n.force&&!await d([i]))return;const m=a(y.t("wizard.loading_config")).start();try{await l.applyPrivateConfig(i,s,r,n),m.stop(),l.printApplied(i),await p([i],{apps:{install:!1}}),n.force&&console.log(t.yellow(y.t("config.force_done",{tool:c[i]?.displayName||i,name:i})));const a=c[i];a&&!a.gui&&(l.isOnPath(i)&&console.log(t.green("\n"+y.t("wizard.run_tool_hint",{command:a.command}))),this.printPathReloadHint([i]));const{nextAction:o}=await e.prompt([{type:"list",name:"nextAction",message:y.t("wizard.what_next"),choices:[{name:y.t("wizard.exit_and_start"),value:"exit"},{name:y.t("wizard.continue_config"),value:"continue"}]}]);"exit"===o&&(this.printGoodbye(),process.exit(0))}catch(a){m.fail(y.t("wizard.config_failed")),console.log(t.red(a instanceof Error?a.message:String(a))),await e.prompt([{type:"input",name:"continue",message:y.t("wizard.press_enter")}])}}async clearPinnedModels(a){const o=c[a]?.displayName||a,{confirm:i}=await e.prompt([{type:"confirm",name:"confirm",message:y.t("wizard.confirm_clear_model",{tool:o}),default:!1}]);if(i){try{const e=await l.clearPinnedModels(a);0===e.length?console.log(t.yellow("\n"+y.t("config.clear_model_none",{tool:o}))):(console.log(t.green("\n✓ "+y.t("config.clear_model_done",{tool:o}))),e.forEach(e=>console.log(t.gray(` - ${e}`))),console.log(t.gray(y.t("config.clear_model_restart_hint",{tool:o}))))}catch(e){console.log(t.red("\n"+(e instanceof Error?e.message:String(e))))}await e.prompt([{type:"input",name:"continue",message:y.t("wizard.press_enter")}])}}async unloadConfig(o){const{confirm:i}=await e.prompt([{type:"confirm",name:"confirm",message:y.t("wizard.confirm_unload_config",{tool:c[o]?.displayName||o}),default:!1}]);if(!i)return;const n=a(y.t("wizard.unloading_config")).start();try{const a=l.holdsOurConfig(o),i=l.unloadPrivateConfig(o);if(!a)return void n.info(y.t("config.remove_nothing",{tool:c[o]?.displayName||o}));n.succeed(y.t("wizard.config_unloaded")),i.forEach(e=>console.log(t.yellow(" ! "+e.text)));const{nextAction:r}=await e.prompt([{type:"list",name:"nextAction",message:y.t("wizard.what_next"),choices:[{name:y.t("wizard.nav_exit"),value:"exit"},{name:y.t("wizard.continue_config"),value:"continue"}]}]);"exit"===r&&(this.printGoodbye(),process.exit(0))}catch(a){n.fail(y.t("wizard.config_unload_failed")),console.log(t.red(a instanceof Error?a.message:String(a))),await e.prompt([{type:"input",name:"continue",message:y.t("wizard.press_enter")}])}}async restoreConfig(o){const i=c[o]?.displayName||o,{confirm:n}=await e.prompt([{type:"confirm",name:"confirm",message:y.t("wizard.confirm_restore_config",{tool:i}),default:!1}]);if(!n)return;const r=a(y.t("wizard.restoring_config")).start();try{const e=l.restorePrivateConfig(o).filter(e=>e.result.restored);0===e.length?r.warn(y.t("wizard.restore_no_backup")):(r.succeed(y.t("wizard.config_restored",{tool:i})),e.forEach(e=>console.log(t.gray(` ${e.file} ← ${e.result.from}`))),console.log(t.yellow(" ! "+y.t("config.reopen_hint",{tool:i}))))}catch(e){r.fail(y.t("wizard.config_restore_failed")),console.log(t.red(e instanceof Error?e.message:String(e)))}await e.prompt([{type:"input",name:"continue",message:y.t("wizard.press_enter")}])}async run(){await this.showMainMenu()}async showMainMenu(){if(this.hasConfiguredApiKey())for(;;){this.resetScreen();const a=o.getApiKey()||"",i=a.length>4?"sk-****"+a.slice(-4):"****";console.log(t.gray(` 🔑 API Key: ${i}`)+t.green(" ✓")),console.log("");const n=l.getSupportedTools().map(e=>({name:"🛠️ "+e.displayName,value:{type:"tool",tool:e.name}})),r=[new e.Separator(F("── "+y.t("wizard.select_tool")+" ──")),...n,new e.Separator(t.gray("── "+y.t("wizard.settings")+" ──")),{name:"🔑 "+y.t("wizard.menu_change_api_key"),value:{type:"apikey"}},{name:"🔗 "+y.t("wizard.menu_base_url",{url:o.getBaseUrl()}),value:{type:"base"}},{name:"🌐 "+y.t("wizard.menu_config_language"),value:{type:"language"}},new e.Separator,{name:"x "+y.t("wizard.nav_exit"),value:{type:"exit"}}],{action:s}=await this.promptWithHints([{type:"list",name:"action",message:"🛠️ "+y.t("wizard.select_tool"),choices:r}]);"exit"===s.type?(this.printGoodbye(),process.exit(0)):"tool"===s.type?await this.configureTool(s.tool):"apikey"===s.type?await this.configApiKey():"base"===s.type?await this.configBaseUrl():"language"===s.type&&await this.configLanguage()}else await this.runFirstTimeSetup()}}export const wizard=Wizard.getInstance();