@@ -16,8 +16,15 @@ import {
1616} from "@inquirer/core" ;
1717import { readFileSync , existsSync } from "node:fs" ;
1818import { homedir } from "os" ;
19+ import { spawnSync } from "node:child_process" ;
1920import type { AgentFile } from "./cli" ;
2021
22+ /** Result from file selector - either a path to run or edit */
23+ export interface FileSelectorResult {
24+ action : "run" | "edit" ;
25+ path : string ;
26+ }
27+
2128// Extended key event type (runtime has more properties than type declares)
2229interface ExtendedKeyEvent extends KeypressEvent {
2330 sequence ?: string ;
@@ -202,7 +209,7 @@ export interface FileSelectorConfig {
202209/**
203210 * Interactive file selector with preview pane
204211 */
205- export const fileSelector = createPrompt < string , FileSelectorConfig > (
212+ export const fileSelector = createPrompt < FileSelectorResult , FileSelectorConfig > (
206213 ( config , done ) => {
207214 const { files, pageSize = 15 } = config ;
208215 const prefix = usePrefix ( { status : "idle" , theme : makeTheme ( { } ) } ) ;
@@ -226,7 +233,15 @@ export const fileSelector = createPrompt<string, FileSelectorConfig>(
226233 const extKey = key as ExtendedKeyEvent ;
227234 if ( isEnterKey ( key ) ) {
228235 if ( currentFile ) {
229- done ( currentFile . path ) ;
236+ done ( { action : "run" , path : currentFile . path } ) ;
237+ }
238+ return ;
239+ }
240+
241+ // Ctrl+E to edit the file in $EDITOR
242+ if ( key . ctrl && key . name === "e" ) {
243+ if ( currentFile ) {
244+ done ( { action : "edit" , path : currentFile . path } ) ;
230245 }
231246 return ;
232247 }
@@ -385,13 +400,60 @@ export const fileSelector = createPrompt<string, FileSelectorConfig>(
385400 // Help line
386401 outputLines . push ( "" ) ;
387402 outputLines . push (
388- `\x1b[90m↑↓ navigate PgUp/PgDn scroll preview Enter select Esc clear filter\x1b[0m`
403+ `\x1b[90m↑↓ navigate PgUp/PgDn scroll preview Enter run Ctrl+E edit Esc clear filter\x1b[0m`
389404 ) ;
390405
391406 return outputLines . join ( "\n" ) ;
392407 }
393408) ;
394409
410+ /**
411+ * Open a file in the user's $EDITOR
412+ * Returns true if successful, false if editor not configured or failed
413+ */
414+ function openInEditor ( filePath : string ) : boolean {
415+ const editor = process . env . EDITOR || process . env . VISUAL ;
416+
417+ if ( ! editor ) {
418+ console . error (
419+ "\x1b[33mNo $EDITOR environment variable set.\x1b[0m\n" +
420+ "Set it in your shell config (e.g., ~/.bashrc or ~/.zshrc):\n" +
421+ " export EDITOR=vim\n" +
422+ " export EDITOR=nano\n" +
423+ " export EDITOR=\"code --wait\"\n"
424+ ) ;
425+ return false ;
426+ }
427+
428+ try {
429+ // Parse editor command (may include flags like "code --wait")
430+ const parts = editor . split ( / \s + / ) ;
431+ const cmd = parts [ 0 ] ! ;
432+ const args = [ ...parts . slice ( 1 ) , filePath ] ;
433+
434+ const result = spawnSync ( cmd , args , {
435+ stdio : "inherit" ,
436+ shell : false ,
437+ } ) ;
438+
439+ if ( result . error ) {
440+ console . error (
441+ `\x1b[31mFailed to open editor "${ editor } ":\x1b[0m ${ result . error . message } \n` +
442+ "Check that your $EDITOR is installed and in your PATH."
443+ ) ;
444+ return false ;
445+ }
446+
447+ return result . status === 0 ;
448+ } catch ( error ) {
449+ console . error (
450+ `\x1b[31mFailed to open editor "${ editor } ":\x1b[0m ${ error } \n` +
451+ "Check that your $EDITOR is installed and in your PATH."
452+ ) ;
453+ return false ;
454+ }
455+ }
456+
395457/**
396458 * Show interactive file picker with preview and return selected file path
397459 */
@@ -402,15 +464,28 @@ export async function showFileSelectorWithPreview(
402464 return undefined ;
403465 }
404466
405- try {
406- const selected = await fileSelector ( {
407- message : "Select an agent to run:" ,
408- files,
409- pageSize : 15 ,
410- } ) ;
411- return selected ;
412- } catch {
413- // User cancelled (Ctrl+C) or other error
414- return undefined ;
467+ // Loop to allow editing and returning to selector
468+ while ( true ) {
469+ try {
470+ const result = await fileSelector ( {
471+ message : "Select an agent to run:" ,
472+ files,
473+ pageSize : 15 ,
474+ } ) ;
475+
476+ if ( result . action === "edit" ) {
477+ // Open in editor, then return to selector
478+ openInEditor ( result . path ) ;
479+ // Clear file content cache so preview reflects edits
480+ fileContentCache . clear ( ) ;
481+ continue ;
482+ }
483+
484+ // action === "run"
485+ return result . path ;
486+ } catch {
487+ // User cancelled (Ctrl+C) or other error
488+ return undefined ;
489+ }
415490 }
416491}
0 commit comments