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
}