1111 * where the app executable exists before any MCP config file is created.
1212 */
1313
14- import { existsSync , readFileSync , writeFileSync , renameSync , mkdirSync , readdirSync } from 'fs'
14+ import { existsSync , readFileSync , writeFileSync , renameSync , mkdirSync } from 'fs'
15+ import { execFileSync } from 'child_process'
1516import { dirname , join } from 'path'
1617import log from 'electron-log'
1718import type { ClientAdapter } from './types'
@@ -37,20 +38,42 @@ const resolveExistingConfigPath = (): string | undefined =>
3738 configPathCandidates ( ) . find ( ( path ) => existsSync ( path ) )
3839
3940/**
40- * Detects Codex installed via Microsoft Store by checking package executable path .
41+ * Detects Codex installed via Microsoft Store by querying AppX registration .
4142 */
42- const hasCodexWindowsStoreExecutable = ( ) : boolean => {
43+ const hasCodexWindowsStorePackage = ( ) : boolean => {
4344 if ( process . platform !== 'win32' ) return false
4445
45- const programFiles = process . env [ 'ProgramFiles' ] ?? 'C:\\Program Files'
46- const windowsAppsDir = join ( programFiles , 'WindowsApps' )
47- if ( ! existsSync ( windowsAppsDir ) ) return false
48-
4946 try {
50- return readdirSync ( windowsAppsDir )
51- . filter ( ( entry ) => entry . startsWith ( 'OpenAI.Codex_' ) )
52- . some ( ( entry ) => existsSync ( join ( windowsAppsDir , entry , 'app' , 'resources' , 'codex.exe' ) ) )
53- } catch {
47+ const defaultPowerShellPath = 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'
48+ const systemRoot = process . env [ 'SystemRoot' ] ?? 'C:\\Windows'
49+ const powerShellPath = join (
50+ systemRoot ,
51+ 'System32' ,
52+ 'WindowsPowerShell' ,
53+ 'v1.0' ,
54+ 'powershell.exe' ,
55+ )
56+ const executable = existsSync ( powerShellPath ) ? powerShellPath : defaultPowerShellPath
57+ const command = [
58+ '$pkg = Get-AppxPackage -Name OpenAI.Codex -ErrorAction SilentlyContinue |' ,
59+ 'Select-Object -First 1 Name, Status;' ,
60+ 'if ($null -eq $pkg) { "" } else { $pkg | ConvertTo-Json -Compress }' ,
61+ ] . join ( ' ' )
62+
63+ const raw = execFileSync ( executable , [ '-NoProfile' , '-NonInteractive' , '-Command' , command ] , {
64+ encoding : 'utf-8' ,
65+ timeout : 2000 ,
66+ windowsHide : true ,
67+ stdio : [ 'ignore' , 'pipe' , 'ignore' ] ,
68+ } ) . trim ( )
69+
70+ if ( raw . length === 0 ) return false
71+
72+ const parsed = JSON . parse ( raw ) as { Status ?: string }
73+ return parsed . Status ?. toLowerCase ( ) === 'ok'
74+ } catch ( err ) {
75+ const message = err instanceof Error ? err . message : String ( err )
76+ log . debug ( `[codex-gui] appx detection failed: ${ message } ` )
5477 return false
5578 }
5679}
@@ -59,7 +82,7 @@ const hasCodexWindowsStoreExecutable = (): boolean => {
5982 * Checks common Codex GUI installation locations.
6083 * Intentionally avoids generic PATH aliases to keep CLI and GUI detection separate.
6184 */
62- const isCodexGuiInstalled = ( ) : boolean => {
85+ const hasCodexGuiExecutable = ( ) : boolean => {
6386 if ( process . platform !== 'win32' ) return false
6487
6588 const localAppData = process . env [ 'LOCALAPPDATA' ] ?? ''
@@ -76,7 +99,16 @@ const isCodexGuiInstalled = (): boolean => {
7699 join ( programFilesX86 , 'OpenAI Codex' , 'Codex.exe' ) ,
77100 ]
78101
79- return hasCodexWindowsStoreExecutable ( ) || candidates . some ( ( path ) => existsSync ( path ) )
102+ return candidates . some ( ( path ) => existsSync ( path ) )
103+ }
104+
105+ type CodexGuiDetectionSource = 'config' | 'appx' | 'exe' | 'none'
106+
107+ const detectInstallSource = ( hasConfig : boolean ) : CodexGuiDetectionSource => {
108+ if ( hasConfig ) return 'config'
109+ if ( hasCodexWindowsStorePackage ( ) ) return 'appx'
110+ if ( hasCodexGuiExecutable ( ) ) return 'exe'
111+ return 'none'
80112}
81113
82114export const codexGuiAdapter : ClientAdapter = {
@@ -86,7 +118,8 @@ export const codexGuiAdapter: ClientAdapter = {
86118
87119 detect ( ) : Promise < ClientDetectionResult > {
88120 const existingConfig = resolveExistingConfigPath ( )
89- const installed = existingConfig !== undefined || isCodexGuiInstalled ( )
121+ const source = detectInstallSource ( existingConfig !== undefined )
122+ const installed = source !== 'none'
90123 let serverCount = 0
91124
92125 if ( existingConfig ) {
@@ -98,7 +131,9 @@ export const codexGuiAdapter: ClientAdapter = {
98131 }
99132 }
100133
101- log . debug ( `[codex-gui] detect: installed=${ installed } , servers=${ serverCount } ` )
134+ log . debug (
135+ `[codex-gui] detect: installed=${ installed } , source=${ source } , servers=${ serverCount } ` ,
136+ )
102137
103138 return Promise . resolve ( {
104139 installed,
0 commit comments