diff --git a/src/cli/tui/copy.ts b/src/cli/tui/copy.ts index e7e567bb..2aef3c42 100644 --- a/src/cli/tui/copy.ts +++ b/src/cli/tui/copy.ts @@ -45,7 +45,7 @@ export const COMMAND_DESCRIPTIONS = { fetch: 'Fetch access info for deployed resources.', pause: 'Pause an online eval config. Supports --arn for configs outside the project.', resume: 'Resume a paused online eval config. Supports --arn for configs outside the project.', - run: 'Run on-demand evaluation. Supports --agent-arn for agents outside the project.', + run: 'Run on-demand evaluation.', import: 'Import a runtime, memory, or starter toolkit into this project. [experimental]', telemetry: 'Manage anonymous usage analytics preferences.', update: 'Check for and install CLI updates', diff --git a/src/cli/tui/screens/create/CreateScreen.tsx b/src/cli/tui/screens/create/CreateScreen.tsx index 5b56a0ac..801dfc8d 100644 --- a/src/cli/tui/screens/create/CreateScreen.tsx +++ b/src/cli/tui/screens/create/CreateScreen.tsx @@ -237,75 +237,12 @@ export function CreateScreen({ cwd, isInteractive, onExit, onNavigate }: CreateS isActive: flow.phase === 'create-prompt', }); - // Checking phase: brief loading state + // Checking phase: instant async check — render nothing to avoid a flash before the real UI if (flow.phase === 'checking') { - return ( - - Checking for existing project... - - ); - } - - // Existing project error phase - if (flow.phase === 'existing-project-error') { - return ( - - - A project already exists at this location. - {flow.existingProjectPath && Found: {flow.existingProjectPath}} - - - Use add agent to create a new agent in the existing project. - - - - - ); + return null; } - // Input phase: ask for project name - if (flow.phase === 'input') { - return ( - - - Create a new AgentCore project - This will create a directory with your project name. - - validateFolderNotExists(name, cwd)} - onSubmit={name => { - flow.setProjectName(name); - flow.confirmProjectName(); - }} - onCancel={handleExit} - /> - - ); - } - - // Create prompt phase - if (flow.phase === 'create-prompt') { - return ( - - - - Project: {flow.projectName} - - - - Would you like to add an agent now? - - - - - - ); - } - - // Create wizard phase - use AddAgentScreen for consistent experience + // Create wizard phase - use AddAgentScreen (separate component, no header conflict) if (flow.phase === 'create-wizard') { return ( to prevent duplicate header flashes + // when Ink transitions between different mounts. + const phase = flow.phase; + const showProjectHeader = phase !== 'input' && phase !== 'existing-project-error'; + const headerContent = showProjectHeader ? ( Project: {flow.projectName} - ); - - const helpText = flow.hasError || allSuccess ? HELP_TEXT.EXIT : undefined; + ) : undefined; + + const helpText = + phase === 'existing-project-error' + ? 'Press Esc to exit' + : phase === 'input' + ? HELP_TEXT.TEXT_INPUT + : phase === 'create-prompt' + ? HELP_TEXT.NAVIGATE_SELECT + : flow.hasError || allSuccess + ? HELP_TEXT.EXIT + : undefined; return ( - + {phase === 'existing-project-error' && ( + + A project already exists at this location. + {flow.existingProjectPath && Found: {flow.existingProjectPath}} + + + Use add agent to create a new agent in the existing project. + + + + )} + + {phase === 'input' && ( + <> + + Create a new AgentCore project + This will create a directory with your project name. + + validateFolderNotExists(name, cwd)} + onSubmit={name => { + flow.setProjectName(name); + flow.confirmProjectName(); + }} + onCancel={handleExit} + /> + + )} + + {phase === 'create-prompt' && ( + <> + + + Project: {flow.projectName} + + + + Would you like to add an agent now? + + + + + + )} + + {phase === 'running' && } + {allSuccess && flow.outputDir && ( + {isInteractive ? ( @@ -352,8 +351,10 @@ export function CreateScreen({ cwd, isInteractive, onExit, onNavigate }: CreateS )} )} + {flow.hasError && ( + Project creation failed. {flow.logFilePath && ( diff --git a/src/cli/tui/screens/deploy/DeployScreen.tsx b/src/cli/tui/screens/deploy/DeployScreen.tsx index 0954f00a..6e99e0cf 100644 --- a/src/cli/tui/screens/deploy/DeployScreen.tsx +++ b/src/cli/tui/screens/deploy/DeployScreen.tsx @@ -228,13 +228,13 @@ export function DeployScreen({ ); } - // AWS target configuration phase (skip when preSynthesized - we already have context) - if (!skipPreflight && !awsConfig.isConfigured) { - return ( - - - - ); + const showAwsConfig = !skipPreflight && !awsConfig.isConfigured; + + // Brief transitional phases — render nothing to avoid a header flash before the real UI + const awsTransitional = + awsConfig.phase === 'checking' || awsConfig.phase === 'detecting' || awsConfig.phase === 'saving'; + if (showAwsConfig && awsTransitional) { + return null; } // Credentials prompt phase @@ -304,7 +304,11 @@ export function DeployScreen({ ] .filter(Boolean) .join(' · '); - const helpText = context && isInteractive ? `${toggleHints} · ${baseHelpText}` : baseHelpText; + const helpText = showAwsConfig + ? (getAwsConfigHelpText(awsConfig.phase) ?? HELP_TEXT.EXIT) + : context && isInteractive + ? `${toggleHints} · ${baseHelpText}` + : baseHelpText; const screenTitle = diffMode ? 'AgentCore Diff' : 'AgentCore Deploy'; @@ -316,94 +320,105 @@ export function DeployScreen({ const diffMaxHeight = Math.max(6, terminalRows - chromeLines); return ( - - - - {/* Toggleable ResourceGraph view */} - {showResourceGraph && context && ( - - - - )} - - {/* Show deploy status when deploying or complete */} - {showDeployStatus && ( - - - - )} - - {/* Show diff output (diff mode: always; normal mode: Ctrl+D toggle) */} - {(diffMode === true || showDiff) && isDiffLoading && ( - - Loading diff... - - )} - {(diffMode === true || showDiff) && diffSummaries.length > 0 && ( - - - - )} - - {allSuccess && deployOutput && !diffMode && ( - - {deployOutput} - - )} - - {allSuccess && diffMode && ( - - Diff complete - - )} - - {allSuccess && deployNotes.length > 0 && ( - - {deployNotes.map((note, i) => ( - - Note: {note} - - ))} - - )} - - {allSuccess && targetStatuses.length > 0 && ( - - Gateway Targets: - {targetStatuses.map(t => ( - - {' '} - {t.name}: {formatTargetStatus(t.status)} - - ))} - - )} - - {logFilePath && ( - - - - )} - - {allSuccess && !diffMode && ( - 0)} - isInteractive={isInteractive} - onSelect={step => { - if (step.command === 'invoke') { - setShowInvoke(true); - } else if (onNavigate) { - onNavigate(step.command); - } - }} - onBack={onExit} - isActive={allSuccess && !showInvoke} - /> + + {showAwsConfig ? ( + + ) : ( + <> + + + {/* Toggleable ResourceGraph view */} + {showResourceGraph && context && ( + + + + )} + + {/* Show deploy status when deploying or complete */} + {showDeployStatus && ( + + + + )} + + {/* Show diff output (diff mode: always; normal mode: Ctrl+D toggle) */} + {(diffMode === true || showDiff) && isDiffLoading && ( + + Loading diff... + + )} + {(diffMode === true || showDiff) && diffSummaries.length > 0 && ( + + + + )} + + {allSuccess && deployOutput && !diffMode && ( + + {deployOutput} + + )} + + {allSuccess && diffMode && ( + + Diff complete + + )} + + {allSuccess && deployNotes.length > 0 && ( + + {deployNotes.map((note, i) => ( + + Note: {note} + + ))} + + )} + + {allSuccess && targetStatuses.length > 0 && ( + + Gateway Targets: + {targetStatuses.map(t => ( + + {' '} + {t.name}: {formatTargetStatus(t.status)} + + ))} + + )} + + {logFilePath && ( + + + + )} + + {allSuccess && !diffMode && ( + 0)} + isInteractive={isInteractive} + onSelect={step => { + if (step.command === 'invoke') { + setShowInvoke(true); + } else if (onNavigate) { + onNavigate(step.command); + } + }} + onBack={onExit} + isActive={allSuccess && !showInvoke} + /> + )} + )} ); diff --git a/src/cli/tui/screens/home/CommandListScreen.tsx b/src/cli/tui/screens/home/CommandListScreen.tsx index f2d2f149..ad2f22c7 100644 --- a/src/cli/tui/screens/home/CommandListScreen.tsx +++ b/src/cli/tui/screens/home/CommandListScreen.tsx @@ -1,10 +1,8 @@ import { buildLogo, useLayout } from '../../context'; import type { CommandMeta } from '../../utils/commands'; -import { Box, Text, useApp } from 'ink'; +import { Box, Text, useApp, useStdout } from 'ink'; import React, { useEffect } from 'react'; -const MAX_DESC_WIDTH = 50; - function truncateDescription(desc: string, maxLen: number): string { if (desc.length <= maxLen) return desc; return desc.slice(0, maxLen - 1) + '…'; @@ -21,6 +19,9 @@ interface CommandListScreenProps { export function CommandListScreen({ commands }: CommandListScreenProps) { const { exit } = useApp(); const { contentWidth } = useLayout(); + const { stdout } = useStdout(); + const terminalWidth = stdout?.columns ?? 80; + const maxDescWidth = Math.max(20, terminalWidth - 18); const logo = buildLogo(contentWidth); // Exit after render @@ -44,7 +45,7 @@ export function CommandListScreen({ commands }: CommandListScreenProps) { Commands: {visibleCommands.map(cmd => { - const desc = truncateDescription(cmd.description, MAX_DESC_WIDTH); + const desc = truncateDescription(cmd.description, maxDescWidth); const padding = ' '.repeat(Math.max(1, 14 - cmd.title.length)); return ( diff --git a/src/cli/tui/screens/home/HelpScreen.tsx b/src/cli/tui/screens/home/HelpScreen.tsx index 84746f2d..485c263c 100644 --- a/src/cli/tui/screens/home/HelpScreen.tsx +++ b/src/cli/tui/screens/home/HelpScreen.tsx @@ -3,11 +3,9 @@ import { useLayout } from '../../context'; import { HINTS } from '../../copy'; import { useTextInput } from '../../hooks'; import type { CommandMeta } from '../../utils/commands'; -import { Box, Text, useInput } from 'ink'; +import { Box, Text, useInput, useStdout } from 'ink'; import React, { useEffect, useMemo, useRef, useState } from 'react'; -const MAX_DESC_WIDTH = 50; - function truncateDescription(desc: string, maxLen: number): string { if (desc.length <= maxLen) return desc; return desc.slice(0, maxLen - 1) + '…'; @@ -29,8 +27,18 @@ interface HelpDisplayProps { notice?: React.ReactNode; } -function CommandRow({ item, selected, maxLabelLen }: { item: DisplayItem; selected: boolean; maxLabelLen: number }) { - const desc = truncateDescription(item.command.description, MAX_DESC_WIDTH); +function CommandRow({ + item, + selected, + maxLabelLen, + maxDescWidth, +}: { + item: DisplayItem; + selected: boolean; + maxLabelLen: number; + maxDescWidth: number; +}) { + const desc = truncateDescription(item.command.description, maxDescWidth); const labelLen = item.matchedSubcommand ? item.command.title.length + 3 + item.matchedSubcommand.length : item.command.title.length; @@ -77,10 +85,13 @@ function HelpDisplay({ notice, }: HelpDisplayProps) { const { contentWidth } = useLayout(); + const { stdout } = useStdout(); + const terminalWidth = stdout?.columns ?? 80; const bottomDivider = '─'.repeat(contentWidth); const allItems = [...interactiveItems, ...cliOnlyItems]; const maxLabelLen = getMaxLabelLen(allItems); + const maxDescWidth = Math.max(20, terminalWidth - maxLabelLen - 8); const hasCliOnly = cliOnlyItems.length > 0; const showCliSection = hasCliOnly && (showCliOnly || !!query); @@ -114,6 +125,7 @@ function HelpDisplay({ item={item} selected={idx === clampedIndex} maxLabelLen={maxLabelLen} + maxDescWidth={maxDescWidth} /> ))} @@ -128,6 +140,7 @@ function HelpDisplay({ item={item} selected={interactiveCount + idx === clampedIndex} maxLabelLen={maxLabelLen} + maxDescWidth={maxDescWidth} /> ))}