feat(tui): add interactive prompts workflow to showroom

Approval prompt and clarify prompt rendered as static mocks (no useInput)
so they work with the snap() capture pipeline. Shows:
- user asking for npm install
- approval prompt with double-bordered warning box, 4 options
- user asking to deploy
- clarify prompt with region selection
- deployment result panel

Static components match real prompt visuals: same borders, colors,
selection indicators, and footer hints.
This commit is contained in:
Brooklyn Nicholson
2026-04-26 01:15:42 -05:00
parent e58308c680
commit 1e499a7136
2 changed files with 281 additions and 0 deletions

View File

@@ -436,6 +436,181 @@ const voiceMode = async () => {
}
}
// --- Static prompt mocks (no useInput, safe for snap()) ---
const ApprovalPromptStatic = ({
command,
description,
selected = 0,
theme
}: {
command: string
description: string
selected?: number
theme: Theme
}) => {
const labels = ['Allow once', 'Allow this session', 'Always allow', 'Deny']
const lines = command.split('\n').slice(0, 5)
return (
<Box borderColor={theme.color.warn} borderStyle="double" flexDirection="column" paddingX={1}>
<Text bold color={theme.color.warn}>
approval required · {description}
</Text>
<Box flexDirection="column" paddingLeft={1}>
{lines.map((line, i) => (
<Text color={theme.color.cornsilk} key={i}>
{line || ' '}
</Text>
))}
</Box>
<Text />
{labels.map((label, i) => (
<Text key={label}>
<Text bold={i === selected} color={i === selected ? theme.color.warn : theme.color.dim} inverse={i === selected}>
{i === selected ? '▸ ' : ' '}
{i + 1}. {label}
</Text>
</Text>
))}
<Text color={theme.color.dim}>/ select · Enter confirm · 1-4 quick pick · Ctrl+C deny</Text>
</Box>
)
}
const ClarifyPromptStatic = ({
choices,
question,
selected = 0,
theme
}: {
choices: string[]
question: string
selected?: number
theme: Theme
}) => (
<Box flexDirection="column">
<Text bold>
<Text color={theme.color.amber}>ask</Text>
<Text color={theme.color.cornsilk}> {question}</Text>
</Text>
{[...choices, 'Other (type your answer)'].map((c, i) => (
<Text key={i}>
<Text bold={i === selected} color={i === selected ? theme.color.label : theme.color.dim} inverse={i === selected}>
{i === selected ? '▸ ' : ' '}
{i + 1}. {c}
</Text>
</Text>
))}
<Text color={theme.color.dim}>
/ select · Enter confirm · 1-{choices.length + 1} quick pick · Esc cancel
</Text>
</Box>
)
const interactivePrompts = async () => {
// User asks for something that triggers approval
const userAsk = await snap(
<Msg role="user" text="Run npm install express in the project root." />
)
const assistantExplains = await snap(
<Msg
role="assistant"
text="I'll install express. The package manager needs approval — here's the command."
/>
)
// Approval prompt
const approval = await snap(
<ApprovalPromptStatic
command={'npm install express\nadded 58 packages in 3.2s\n\n+ express@5.1.0'}
description="install dependency"
theme={t}
/>,
180
)
// After approval, user asks something ambiguous
const userClarify = await snap(
<Msg role="user" text="Deploy this to staging." />
)
const assistantAsks = await snap(
<Msg role="assistant" text="Which environment should I target?" />
)
// Clarify prompt
const clarify = await snap(
<ClarifyPromptStatic
choices={['staging-us-east', 'staging-eu-west', 'staging-ap-south']}
question="Which region?"
theme={t}
/>,
180
)
const confirmResult = await snap(
<Panel
sections={[
{
rows: [
['target', 'staging-us-east'],
['branch', 'main'],
['preview', 'https://pr-128.railway.app']
]
}
]}
t={t}
title="deployment queued"
/>,
180
)
return {
composer: 'deploy this to staging',
timeline: [
{ ansi: userAsk, at: 200, id: 'ask', type: 'frame' },
{ ansi: assistantExplains, at: 1200, id: 'explain', type: 'frame' },
{ ansi: approval, at: 2600, id: 'approval', type: 'frame' },
{ at: 2900, duration: 1500, target: 'approval', type: 'spotlight' },
{
at: 3100,
duration: 2000,
position: 'right',
target: 'approval',
text: 'Approval prompts gate dangerous commands. Four options: allow once, session, always, deny.',
type: 'caption'
},
{ at: 5400, duration: 400, text: '1', type: 'compose' },
{ at: 5900, duration: 500, text: '', type: 'compose' },
{ ansi: userClarify, at: 6600, id: 'clarify-ask', type: 'frame' },
{ ansi: assistantAsks, at: 7600, id: 'clarify-reply', type: 'frame' },
{ ansi: clarify, at: 8800, id: 'clarify', type: 'frame' },
{ at: 9100, duration: 1500, target: 'clarify', type: 'spotlight' },
{
at: 9300,
duration: 2000,
position: 'right',
target: 'clarify',
text: 'Clarify prompts handle ambiguous requests — numbered choices or free text.',
type: 'caption'
},
{ at: 11600, duration: 400, text: '1', type: 'compose' },
{ ansi: confirmResult, at: 12200, id: 'result', type: 'frame' },
{ at: 12500, duration: 1300, target: 'result', type: 'highlight' }
],
title: 'Hermes TUI · Interactive Prompts',
viewport: { cols: COLS, rows: ROWS }
}
}
const main = async () => {
console.log('recording workflows…')
@@ -447,6 +622,7 @@ const main = async () => {
'subagent-trail.json',
'slash-commands.json',
'voice-mode.json',
'interactive-prompts.json',
'ink-frames.json'
]) {
try {
@@ -460,6 +636,7 @@ const main = async () => {
writeWorkflow('subagent-trail', await subagentTrail())
writeWorkflow('slash-commands', await slashCommands())
writeWorkflow('voice-mode', await voiceMode())
writeWorkflow('interactive-prompts', await interactivePrompts())
console.log('done')
}

View File

@@ -0,0 +1,104 @@
{
"composer": "deploy this to staging",
"timeline": [
{
"ansi": "\u001b[?25l\u001b[?2026h\r\n\u001b[2CRun\u001b[1Cnpm\u001b[1Cinstall\u001b[1Cexpress\u001b[1Cin\u001b[1Cthe\u001b[1Cproject\u001b[1Croot.\r\n\r\n\u001b[?2026l\u001b[?2026h\u001b[?25h\u001b[?2026l\u001b[?25h",
"at": 200,
"id": "ask",
"type": "frame"
},
{
"ansi": "\u001b[?25l\u001b[?2026h┊\u001b[2CI'll\u001b[1Cinstall\u001b[1Cexpress.\u001b[1CThe\u001b[1Cpackage\u001b[1Cmanager\u001b[1Cneeds\u001b[1Capproval\u001b[1C—\u001b[1Chere's\u001b[1Cthe\r\n\u001b[3Ccommand.\r\n\u001b[?2026l\u001b[?2026h\u001b[?25h\u001b[?2026l\u001b[?25h",
"at": 1200,
"id": "explain",
"type": "frame"
},
{
"ansi": "\u001b[?25l\u001b[?2026h╔══════════════════════════════════════════════════════════════════════════════╗\r\n║\u001b[1C⚠\u001b[1Capproval\u001b[1Crequired\u001b[1C·\u001b[1Cinstall\u001b[1Cdependency\u001b[36C║\r\n║\u001b[2Cnpm\u001b[1Cinstall\u001b[1Cexpress\u001b[57C║\r\n║\u001b[2Cadded\u001b[1C58\u001b[1Cpackages\u001b[1Cin\u001b[1C3.2s\u001b[51C║\r\n║\u001b[78C║\r\n║\u001b[2C+\u001b[1Cexpress@5.1.0\u001b[61C║\r\n║\u001b[1C▸\u001b[1C1.\u001b[1CAllow\u001b[1Conce\u001b[62C║\r\n║\u001b[3C2.\u001b[1CAllow\u001b[1Cthis\u001b[1Csession\u001b[54C║\r\n║\u001b[3C3.\u001b[1CAlways\u001b[1Callow\u001b[60C║\r\n║\u001b[3C4.\u001b[1CDeny\u001b[68C║\r\n║\u001b[1C↑/↓\u001b[1Cselect\u001b[1C·\u001b[1CEnter\u001b[1Cconfirm\u001b[1C·\u001b[1C1-4\u001b[1Cquick\u001b[1Cpick\u001b[1C·\u001b[1CCtrl+C\u001b[1Cdeny\u001b[20C║\r\n╚══════════════════════════════════════════════════════════════════════════════╝\r\n\u001b[?2026l\u001b[?2026h\u001b[?25h\u001b[?2026l\u001b[?25h",
"at": 2600,
"id": "approval",
"type": "frame"
},
{
"at": 2900,
"duration": 1500,
"target": "approval",
"type": "spotlight"
},
{
"at": 3100,
"duration": 2000,
"position": "right",
"target": "approval",
"text": "Approval prompts gate dangerous commands. Four options: allow once, session, always, deny.",
"type": "caption"
},
{
"at": 5400,
"duration": 400,
"text": "1",
"type": "compose"
},
{
"at": 5900,
"duration": 500,
"text": "",
"type": "compose"
},
{
"ansi": "\u001b[?25l\u001b[?2026h\r\n\u001b[2CDeploy\u001b[1Cthis\u001b[1Cto\u001b[1Cstaging.\r\n\r\n\u001b[?2026l\u001b[?2026h\u001b[?25h\u001b[?2026l\u001b[?25h",
"at": 6600,
"id": "clarify-ask",
"type": "frame"
},
{
"ansi": "\u001b[?25l\u001b[?2026h┊\u001b[2CWhich\u001b[1Cenvironment\u001b[1Cshould\u001b[1CI\u001b[1Ctarget?\r\n\u001b[?2026l\u001b[?2026h\u001b[?25h\u001b[?2026l\u001b[?25h",
"at": 7600,
"id": "clarify-reply",
"type": "frame"
},
{
"ansi": "\u001b[?25l\u001b[?2026hask\u001b[1CWhich\u001b[1Cregion?\r\n▸\u001b[1C1.\u001b[1Cstaging-us-east\r\n\u001b[2C2.\u001b[1Cstaging-eu-west\r\n\u001b[2C3.\u001b[1Cstaging-ap-south\r\n\u001b[2C4.\u001b[1COther\u001b[1C(type\u001b[1Cyour\u001b[1Canswer)\r\n↑/↓\u001b[1Cselect\u001b[1C·\u001b[1CEnter\u001b[1Cconfirm\u001b[1C·\u001b[1C1-4\u001b[1Cquick\u001b[1Cpick\u001b[1C·\u001b[1CEsc\u001b[1Ccancel\r\n\u001b[?2026l\u001b[?2026h\u001b[?25h\u001b[?2026l\u001b[?25h",
"at": 8800,
"id": "clarify",
"type": "frame"
},
{
"at": 9100,
"duration": 1500,
"target": "clarify",
"type": "spotlight"
},
{
"at": 9300,
"duration": 2000,
"position": "right",
"target": "clarify",
"text": "Clarify prompts handle ambiguous requests — numbered choices or free text.",
"type": "caption"
},
{
"at": 11600,
"duration": 400,
"text": "1",
"type": "compose"
},
{
"ansi": "\u001b[?25l\u001b[?2026h╭──────────────────────────────────────────────────────────────────────────────╮\r\n│\u001b[78C│\r\n│\u001b[30Cdeployment\u001b[1Cqueued\u001b[31C│\r\n│\u001b[78C│\r\n│\u001b[2Ctarget\u001b[14Cstaging-us-east\u001b[41C│\r\n│\u001b[2Cbranch\u001b[14Cmain\u001b[52C│\r\n│\u001b[2Cpreview\u001b[13Chttps://pr-128.railway.app\u001b[30C│\r\n│\u001b[78C│\r\n╰──────────────────────────────────────────────────────────────────────────────╯\r\n\u001b[?2026l\u001b[?2026h\u001b[?25h\u001b[?2026l\u001b[?25h",
"at": 12200,
"id": "result",
"type": "frame"
},
{
"at": 12500,
"duration": 1300,
"target": "result",
"type": "highlight"
}
],
"title": "Hermes TUI · Interactive Prompts",
"viewport": {
"cols": 80,
"rows": 16
}
}