@@ -32,12 +32,13 @@ class GateService: ObservableObject {
3232
3333 func runPokeLogin( ) {
3434 let fullPath = shellPath ( )
35+ let npxBin = findNpx ( )
3536 let proc = Process ( )
3637 proc. executableURL = URL ( fileURLWithPath: " /bin/zsh " )
37- proc. arguments = [ " -c " , " npx -y poke@latest login" ]
38+ proc. arguments = [ " -c " , " \( npxBin ) -y poke@latest login " ]
3839 proc. environment = [ " HOME " : NSHomeDirectory ( ) , " PATH " : fullPath]
3940 try ? proc. run ( )
40- appendLog ( " Launched poke login — check your browser. " )
41+ appendLog ( " Launched poke login (npx: \( npxBin ) ) — check your browser." )
4142 }
4243
4344 func autoStartIfNeeded( ) {
@@ -90,19 +91,82 @@ class GateService: ObservableObject {
9091 }
9192 }
9293
93- private func shellPath( ) -> String {
94- let loginShell = ProcessInfo . processInfo. environment [ " SHELL " ] ?? " /bin/zsh "
95- let pathProc = Process ( )
96- let pathPipe = Pipe ( )
97- pathProc. executableURL = URL ( fileURLWithPath: loginShell)
98- pathProc. arguments = [ " -ilc " , " echo $PATH " ]
99- pathProc. standardOutput = pathPipe
100- pathProc. standardError = FileHandle . nullDevice
101- pathProc. environment = [ " HOME " : NSHomeDirectory ( ) ]
102- try ? pathProc. run ( )
103- pathProc. waitUntilExit ( )
104- let data = pathPipe. fileHandleForReading. readDataToEndOfFile ( )
105- return String ( data: data, encoding: . utf8) ? . trimmingCharacters ( in: . whitespacesAndNewlines) ?? " /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin "
94+ func shellPath( ) -> String {
95+ let home = NSHomeDirectory ( )
96+ let fallback = " /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin "
97+
98+ // Try multiple shells/strategies to get PATH
99+ let strategies : [ ( String , [ String ] ) ] = [
100+ ( " /bin/zsh " , [ " -ilc " , " echo $PATH " ] ) ,
101+ ( " /bin/zsh " , [ " -lc " , " echo $PATH " ] ) ,
102+ ( " /bin/bash " , [ " -lc " , " echo $PATH " ] ) ,
103+ ]
104+
105+ for (shell, args) in strategies {
106+ let proc = Process ( )
107+ let pipe = Pipe ( )
108+ proc. executableURL = URL ( fileURLWithPath: shell)
109+ proc. arguments = args
110+ proc. standardOutput = pipe
111+ proc. standardError = FileHandle . nullDevice
112+ proc. environment = [ " HOME " : home]
113+ do {
114+ try proc. run ( )
115+ proc. waitUntilExit ( )
116+ if proc. terminationStatus == 0 {
117+ let data = pipe. fileHandleForReading. readDataToEndOfFile ( )
118+ if let path = String ( data: data, encoding: . utf8) ?
119+ . trimmingCharacters ( in: . whitespacesAndNewlines) ,
120+ !path. isEmpty {
121+ return path
122+ }
123+ }
124+ } catch {
125+ continue
126+ }
127+ }
128+
129+ // Fallback: build PATH from common locations
130+ var paths = fallback. split ( separator: " : " ) . map ( String . init)
131+
132+ let commonDirs = [
133+ " \( home) /.nvm/versions/node " ,
134+ " \( home) /.volta/bin " ,
135+ " \( home) /.fnm/aliases/default/bin " ,
136+ " \( home) /.local/bin " ,
137+ " \( home) /.cargo/bin " ,
138+ " /opt/homebrew/bin " ,
139+ " /usr/local/bin " ,
140+ ]
141+
142+ for dir in commonDirs {
143+ if FileManager . default. fileExists ( atPath: dir) {
144+ if dir. contains ( " .nvm " ) {
145+ // Find the latest node version in nvm
146+ if let versions = try ? FileManager . default. contentsOfDirectory ( atPath: dir) {
147+ if let latest = versions. sorted ( ) . last {
148+ let binPath = " \( dir) / \( latest) /bin "
149+ if !paths. contains ( binPath) { paths. insert ( binPath, at: 0 ) }
150+ }
151+ }
152+ } else if !paths. contains ( dir) {
153+ paths. insert ( dir, at: 0 )
154+ }
155+ }
156+ }
157+
158+ return paths. joined ( separator: " : " )
159+ }
160+
161+ private func findNpx( ) -> String {
162+ let path = shellPath ( )
163+ for dir in path. split ( separator: " : " ) {
164+ let npxPath = " \( dir) /npx "
165+ if FileManager . default. isExecutableFile ( atPath: npxPath) {
166+ return npxPath
167+ }
168+ }
169+ return " npx "
106170 }
107171
108172 private func launchProcess( ) {
@@ -112,15 +176,17 @@ class GateService: ObservableObject {
112176 appendLog ( " Starting poke-gate… " )
113177
114178 let fullPath = shellPath ( )
179+ let npxBin = findNpx ( )
180+
181+ appendLog ( " Using npx at: \( npxBin) " )
115182
116183 let proc = Process ( )
117184 let pipe = Pipe ( )
118185
119186 proc. executableURL = URL ( fileURLWithPath: " /bin/zsh " )
120- proc. arguments = [ " -c " , " npx -y poke-gate --verbose" ]
187+ proc. arguments = [ " -c " , " \( npxBin ) -y poke-gate --verbose " ]
121188 proc. environment = ProcessInfo . processInfo. environment. merging (
122189 [
123- " POKE_API_KEY " : resolveToken ( ) ?? " " ,
124190 " PATH " : fullPath,
125191 ] ,
126192 uniquingKeysWith: { _, new in new }
0 commit comments