@@ -112,7 +112,22 @@ export default class Install extends Command {
112
112
}
113
113
114
114
private async installMCPServer ( configPath : string , serverName : string , client : string ) : Promise < void > {
115
- let config : any = { }
115
+ interface ConfigType {
116
+ experimental ?: {
117
+ modelContextProtocolServers ?: Array < {
118
+ transport : {
119
+ args : string [ ] ;
120
+ command : string ;
121
+ env : Record < string , string > ;
122
+ type : string ;
123
+ } ;
124
+ } > ;
125
+ useTools ?: boolean ;
126
+ } ;
127
+ mcpServers ?: Record < string , unknown > ;
128
+ }
129
+
130
+ let config : ConfigType = { }
116
131
117
132
try {
118
133
// Check if file exists
@@ -128,7 +143,7 @@ export default class Install extends Command {
128
143
// Create empty config
129
144
config = { }
130
145
} else {
131
- throw error // Re-throw if it's not a missing file error
146
+ throw error
132
147
}
133
148
}
134
149
@@ -140,7 +155,7 @@ export default class Install extends Command {
140
155
const finalArgs = [ ...serverConfig . args ]
141
156
if ( serverConfig . runtimeArgs ) {
142
157
const runtimeArg = serverConfig . runtimeArgs
143
- let answer : any
158
+ let answer : string | string [ ]
144
159
145
160
// Special case for filesystem-ref server
146
161
let defaultValue = runtimeArg . default
@@ -152,26 +167,35 @@ export default class Install extends Command {
152
167
153
168
if ( runtimeArg . multiple ) {
154
169
// First get the default path
155
- answer = await inquirer . input ( {
170
+ const initialAnswer = await inquirer . input ( {
156
171
default : Array . isArray ( defaultValue ) ? defaultValue . join ( ', ' ) : defaultValue ,
157
172
message : runtimeArg . description ,
158
173
} )
159
- const paths = answer . split ( ',' ) . map ( ( s : string ) => s . trim ( ) )
174
+ const paths = initialAnswer . split ( ',' ) . map ( ( s : string ) => s . trim ( ) )
160
175
161
- // Keep asking for additional paths
162
- while ( true ) {
163
- const additionalPath = await inquirer . input ( {
176
+ // Keep asking for additional paths until empty input
177
+ const getAdditionalPaths = async ( ) : Promise < string [ ] > => {
178
+ const additionalPaths : string [ ] = [ ]
179
+ let input = await inquirer . input ( {
164
180
default : "" ,
165
181
message : "Add another allowed directory path? (press Enter to finish)" ,
166
182
} )
167
183
168
- if ( ! additionalPath . trim ( ) ) {
169
- break
184
+ while ( input . trim ( ) ) {
185
+ additionalPaths . push ( input . trim ( ) )
186
+ // eslint-disable-next-line no-await-in-loop
187
+ input = await inquirer . input ( {
188
+ default : "" ,
189
+ message : "Add another allowed directory path? (press Enter to finish)" ,
190
+ } )
170
191
}
171
192
172
- paths . push ( additionalPath . trim ( ) )
193
+ return additionalPaths
173
194
}
174
195
196
+ const additionalPaths = await getAdditionalPaths ( )
197
+ paths . push ( ...additionalPaths )
198
+
175
199
answer = paths
176
200
} else {
177
201
answer = await inquirer . input ( {
@@ -193,6 +217,7 @@ export default class Install extends Command {
193
217
const answers : Record < string , string > = { }
194
218
195
219
for ( const [ key , value ] of Object . entries ( envVars ) ) {
220
+ // eslint-disable-next-line no-await-in-loop
196
221
const answer = await inquirer . input ( {
197
222
message : value . description ,
198
223
validate ( input : string ) {
@@ -238,8 +263,8 @@ export default class Install extends Command {
238
263
239
264
// Find if server already exists in the array
240
265
const existingServerIndex = config . experimental . modelContextProtocolServers . findIndex (
241
- ( s : any ) => s . transport . command === serverConfig . command &&
242
- JSON . stringify ( s . transport . args ) === JSON . stringify ( finalArgs )
266
+ ( s ) => s . transport . command === serverConfig . command &&
267
+ JSON . stringify ( s . transport . args ) === JSON . stringify ( finalArgs )
243
268
)
244
269
245
270
if ( existingServerIndex >= 0 ) {
@@ -274,7 +299,7 @@ export default class Install extends Command {
274
299
}
275
300
}
276
301
277
- private async installOnWindows ( serverName : string , client : string ) : Promise < void > {
302
+ private async installOnWindows ( _serverName : string , _client : string ) : Promise < void > {
278
303
// TODO: Implement Windows-specific installation logic
279
304
throw new Error ( 'Windows installation not implemented yet' )
280
305
}
@@ -297,73 +322,23 @@ export default class Install extends Command {
297
322
throw new Error ( `Unknown client: ${ client } ` )
298
323
}
299
324
325
+ const sleep = ( ms : number ) => new Promise ( resolve => { setTimeout ( resolve , ms ) } )
326
+
300
327
try {
301
328
const { platform} = process
302
329
if ( platform === 'darwin' ) {
303
330
if ( client === 'continue' ) {
304
- try {
305
- // First, find VS Code's installation location
306
- const findVSCode = await execAsync ( 'mdfind "kMDItemCFBundleIdentifier == \'com.microsoft.VSCode\'" | head -n1' )
307
- const vscodePath = findVSCode . stdout . trim ( )
308
-
309
- if ( vscodePath ) {
310
- const electronPath = path . join ( vscodePath , 'Contents/MacOS/Electron' )
311
- // Check if VS Code is running using the found path
312
- const vscodeProcesses = await execAsync ( `pgrep -fl "${ electronPath } "` )
313
- if ( vscodeProcesses . stdout . trim ( ) . length > 0 ) {
314
- // Use pkill with full path to ensure we only kill VS Code's Electron
315
- await execAsync ( `pkill -f "${ electronPath } "` )
316
- await new Promise ( resolve => setTimeout ( resolve , 2000 ) )
317
- await execAsync ( `open -a "Visual Studio Code"` )
318
- this . log ( `✨ Continue (VS Code) has been restarted` )
319
- return
320
- }
321
- }
322
- } catch {
323
- // VS Code not found or error in detection, try JetBrains
324
- try {
325
- const jetbrainsProcesses = await execAsync ( 'pgrep -fl "IntelliJ IDEA.app"' )
326
- if ( jetbrainsProcesses . stdout . trim ( ) . length > 0 ) {
327
- await execAsync ( `killall "idea"` )
328
- await new Promise ( resolve => setTimeout ( resolve , 2000 ) )
329
- await execAsync ( `open -a "IntelliJ IDEA"` )
330
- this . log ( `✨ Continue (IntelliJ IDEA) has been restarted` )
331
- return
332
- }
333
- } catch {
334
- // JetBrains not found
335
- }
336
- }
337
-
338
- throw new Error ( 'Could not detect running IDE (VS Code or JetBrains) for Continue' )
331
+ await this . restartContinueClient ( )
339
332
} else {
340
333
// For other clients like Claude, use the normal process
341
334
await execAsync ( `killall "${ processName } "` )
342
- await new Promise ( resolve => setTimeout ( resolve , 2000 ) )
335
+ await sleep ( 2000 )
343
336
await execAsync ( `open -a "${ processName } "` )
344
337
this . log ( `✨ ${ this . clientDisplayNames [ client ] } has been restarted` )
345
338
}
346
339
} else if ( platform === 'win32' ) {
347
340
if ( client === 'continue' ) {
348
- try {
349
- const vscodeProcess = await execAsync ( 'tasklist /FI "IMAGENAME eq Code.exe" /FO CSV /NH' )
350
- if ( vscodeProcess . stdout . includes ( 'Code.exe' ) ) {
351
- await execAsync ( 'taskkill /F /IM "Code.exe" && start "" "Visual Studio Code"' )
352
- this . log ( `✨ VS Code has been restarted` )
353
- return
354
- }
355
-
356
- const jetbrainsProcess = await execAsync ( 'tasklist /FI "IMAGENAME eq idea64.exe" /FO CSV /NH' )
357
- if ( jetbrainsProcess . stdout . includes ( 'idea64.exe' ) ) {
358
- await execAsync ( 'taskkill /F /IM "idea64.exe" && start "" "IntelliJ IDEA"' )
359
- this . log ( `✨ IntelliJ IDEA has been restarted` )
360
- return
361
- }
362
- } catch {
363
- // Process detection failed
364
- }
365
-
366
- throw new Error ( 'Could not detect running IDE (VS Code or JetBrains) for Continue' )
341
+ await this . restartContinueClientWindows ( )
367
342
} else {
368
343
// For other clients
369
344
await execAsync ( `taskkill /F /IM "${ processName } .exe" && start "" "${ processName } .exe"` )
@@ -386,6 +361,68 @@ export default class Install extends Command {
386
361
}
387
362
}
388
363
364
+ private async restartContinueClient ( ) : Promise < void > {
365
+ const sleep = ( ms : number ) => new Promise ( resolve => { setTimeout ( resolve , ms ) } )
366
+
367
+ try {
368
+ // First, find VS Code's installation location
369
+ const findVSCode = await execAsync ( 'mdfind "kMDItemCFBundleIdentifier == \'com.microsoft.VSCode\'" | head -n1' )
370
+ const vscodePath = findVSCode . stdout . trim ( )
371
+
372
+ if ( vscodePath ) {
373
+ const electronPath = path . join ( vscodePath , 'Contents/MacOS/Electron' )
374
+ // Check if VS Code is running using the found path
375
+ const vscodeProcesses = await execAsync ( `pgrep -fl "${ electronPath } "` )
376
+ if ( vscodeProcesses . stdout . trim ( ) . length > 0 ) {
377
+ // Use pkill with full path to ensure we only kill VS Code's Electron
378
+ await execAsync ( `pkill -f "${ electronPath } "` )
379
+ await sleep ( 2000 )
380
+ await execAsync ( `open -a "Visual Studio Code"` )
381
+ this . log ( `✨ Continue (VS Code) has been restarted` )
382
+ return
383
+ }
384
+ }
385
+ } catch {
386
+ // VS Code not found or error in detection, try JetBrains
387
+ try {
388
+ const jetbrainsProcesses = await execAsync ( 'pgrep -fl "IntelliJ IDEA.app"' )
389
+ if ( jetbrainsProcesses . stdout . trim ( ) . length > 0 ) {
390
+ await execAsync ( `killall "idea"` )
391
+ await sleep ( 2000 )
392
+ await execAsync ( `open -a "IntelliJ IDEA"` )
393
+ this . log ( `✨ Continue (IntelliJ IDEA) has been restarted` )
394
+ return
395
+ }
396
+ } catch {
397
+ // JetBrains not found
398
+ }
399
+ }
400
+
401
+ throw new Error ( 'Could not detect running IDE (VS Code or JetBrains) for Continue' )
402
+ }
403
+
404
+ private async restartContinueClientWindows ( ) : Promise < void > {
405
+ try {
406
+ const vscodeProcess = await execAsync ( 'tasklist /FI "IMAGENAME eq Code.exe" /FO CSV /NH' )
407
+ if ( vscodeProcess . stdout . includes ( 'Code.exe' ) ) {
408
+ await execAsync ( 'taskkill /F /IM "Code.exe" && start "" "Visual Studio Code"' )
409
+ this . log ( `✨ VS Code has been restarted` )
410
+ return
411
+ }
412
+
413
+ const jetbrainsProcess = await execAsync ( 'tasklist /FI "IMAGENAME eq idea64.exe" /FO CSV /NH' )
414
+ if ( jetbrainsProcess . stdout . includes ( 'idea64.exe' ) ) {
415
+ await execAsync ( 'taskkill /F /IM "idea64.exe" && start "" "IntelliJ IDEA"' )
416
+ this . log ( `✨ IntelliJ IDEA has been restarted` )
417
+ return
418
+ }
419
+ } catch {
420
+ // Process detection failed
421
+ }
422
+
423
+ throw new Error ( 'Could not detect running IDE (VS Code or JetBrains) for Continue' )
424
+ }
425
+
389
426
private async validateServer ( serverName : string ) : Promise < MCPServerType > {
390
427
const server = servers . find ( s => s . id === serverName )
391
428
if ( ! server ) {
0 commit comments