Open `content` in the user's editor, return the edited text (or null on failure).
(content: string, originalPath: string)
| 40 | |
| 41 | /** Open `content` in the user's editor, return the edited text (or null on failure). */ |
| 42 | function editInEditor(content: string, originalPath: string): string | null { |
| 43 | const editor = process.env.VISUAL || process.env.EDITOR; |
| 44 | if (!editor) return null; // no editor configured → caller falls back |
| 45 | try { |
| 46 | const dir = mkdtempSync(join(tmpdir(), 'qodex-edit-')); |
| 47 | const ext = extname(originalPath) || '.txt'; |
| 48 | const tmp = join(dir, `proposal${ext}`); |
| 49 | writeFileSync(tmp, content, 'utf8'); |
| 50 | // stdio:'inherit' hands the TTY to the editor; on exit Ink repaints. |
| 51 | execFileSync(editor, [tmp], { stdio: 'inherit' }); |
| 52 | return readFileSync(tmp, 'utf8'); |
| 53 | } catch (e: any) { |
| 54 | // Editor flow failed (mkdtemp/write/spawn/read). Log so it's traceable — |
| 55 | // otherwise this is indistinguishable from "no editor configured". |
| 56 | logger.warn('External editor flow failed; falling back', { editor, err: e?.message ?? String(e) }); |
| 57 | return null; |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | /** |
| 62 | * Run the approval flow for a pending edit. Returns the decision the tool acts on. |