import { Box, Text, useInput } from '@hermes/ink' import { useState } from 'react' import { isMac } from '../lib/platform.js' import type { Theme } from '../theme.js' import type { ApprovalReq, ClarifyReq, ConfirmReq } from '../types.js' import { TextInput } from './textInput.js' const OPTS = ['once', 'session', 'always', 'deny'] as const const LABELS = { always: 'Always allow', deny: 'Deny', once: 'Allow once', session: 'Allow this session' } as const const CMD_PREVIEW_LINES = 10 export function ApprovalPrompt({ onChoice, req, t }: ApprovalPromptProps) { const [sel, setSel] = useState(0) useInput((ch, key) => { if (key.upArrow && sel > 0) { setSel(s => s - 1) } if (key.downArrow && sel < OPTS.length - 1) { setSel(s => s + 1) } const n = parseInt(ch, 10) if (n >= 1 && n <= OPTS.length) { onChoice(OPTS[n - 1]!) return } if (key.return) { onChoice(OPTS[sel]!) } }) const rawLines = req.command.split('\n') const shown = rawLines.slice(0, CMD_PREVIEW_LINES) const overflow = rawLines.length - shown.length return ( ⚠ approval required · {req.description} {shown.map((line, i) => ( {line || ' '} ))} {overflow > 0 ? ( … +{overflow} more line{overflow === 1 ? '' : 's'} (full text above) ) : null} {OPTS.map((o, i) => ( {sel === i ? '▸ ' : ' '} {i + 1}. {LABELS[o]} ))} ↑/↓ select · Enter confirm · 1-4 quick pick · Ctrl+C deny ) } export function ClarifyPrompt({ cols = 80, onAnswer, onCancel, req, t }: ClarifyPromptProps) { const [sel, setSel] = useState(0) const [custom, setCustom] = useState('') const [typing, setTyping] = useState(false) const choices = req.choices ?? [] const heading = ( ask {req.question} ) useInput((ch, key) => { if (key.escape) { typing && choices.length ? setTyping(false) : onCancel() return } if (typing || !choices.length) { return } if (key.upArrow && sel > 0) { setSel(s => s - 1) } if (key.downArrow && sel < choices.length) { setSel(s => s + 1) } if (key.return) { sel === choices.length ? setTyping(true) : choices[sel] && onAnswer(choices[sel]!) } const n = parseInt(ch) if (n >= 1 && n <= choices.length) { onAnswer(choices[n - 1]!) } }) if (typing || !choices.length) { return ( {heading} {'> '} Enter send · Esc {choices.length ? 'back' : 'cancel'} ·{' '} {isMac ? 'Cmd+C copy · Cmd+V paste · Ctrl+C cancel' : 'Ctrl+C cancel'} ) } return ( {heading} {[...choices, 'Other (type your answer)'].map((c, i) => ( {sel === i ? '▸ ' : ' '} {i + 1}. {c} ))} ↑/↓ select · Enter confirm · 1-{choices.length} quick pick · Esc/Ctrl+C cancel ) } export function ConfirmPrompt({ onCancel, onConfirm, req, t }: ConfirmPromptProps) { const [sel, setSel] = useState(0) useInput((ch, key) => { const lower = ch.toLowerCase() if (key.escape || (key.ctrl && lower === 'c') || lower === 'n') { return onCancel() } if (lower === 'y') { return onConfirm() } if (key.upArrow) { setSel(0) } if (key.downArrow) { setSel(1) } if (key.return) { sel === 0 ? onCancel() : onConfirm() } }) const accent = req.danger ? t.color.error : t.color.warn const rows = [ { color: t.color.text, label: req.cancelLabel ?? 'No' }, { color: req.danger ? t.color.error : t.color.text, label: req.confirmLabel ?? 'Yes' } ] return ( {req.danger ? '⚠' : '?'} {req.title} {req.detail ? ( {req.detail} ) : null} {rows.map((row, i) => ( {sel === i ? '▸ ' : ' '} {row.label} ))} ↑/↓ select · Enter confirm · Y/N quick · Esc cancel ) } interface ApprovalPromptProps { onChoice: (s: string) => void req: ApprovalReq t: Theme } interface ClarifyPromptProps { cols?: number onAnswer: (s: string) => void onCancel: () => void req: ClarifyReq t: Theme } interface ConfirmPromptProps { onCancel: () => void onConfirm: () => void req: ConfirmReq t: Theme }