/** * Main Application using custom renderer * Replaces Ink-based App */ import { StatusInfo } from './components/Status'; import { SelectItem } from './components/SelectScreen'; export interface Message { role: 'user' | 'assistant' | 'system' | 'welcome'; content: string; } export interface ConfirmOptions { title: string; message: string[]; confirmLabel?: string; cancelLabel?: string; extraOption?: { label: string; onSelect: () => void; }; onConfirm: () => void; onCancel?: () => void; } export type { HunkPickerItem, HunkPickerOptions } from './components/HunkPicker'; import { type HunkPickerOptions } from './components/HunkPicker'; /** * Options for the interactive hunk picker — see components/HunkPicker.ts. * (`onComplete` fires once with the accepted [path, hunkIndex] pairs so the * caller can apply them via `applyHunksToFiles`.) */ export interface AppOptions { onSubmit: (message: string) => Promise; onCommand: (command: string, args: string[]) => void; onExit: () => void; onStopAgent?: () => void; onImagePaste?: (imageData: string) => Promise; getStatus: () => StatusInfo; hasWriteAccess?: () => boolean; hasProjectContext?: () => boolean; /** Project root for `@mention` autocomplete suggestions. Falls back to cwd. */ getProjectRoot?: () => string; } export declare class App { private screen; private input; private editor; private messages; private streamingContent; private isStreaming; private isLoading; private options; private scrollOffset; /** Messages that arrived while the user was scrolled up — drives the * status bar's "↓ N new" badge; 0 whenever the view is at the bottom. */ private unseenWhileScrolled; private notification; private notificationIsWarn; private notificationTimeout; private pendingRender; private spinnerFrame; private spinnerInterval; private isAgentRunning; private agentIteration; private agentMaxIterations; private agentActions; private agentThinking; private agentWaitingForAI; private agentLog; /** Process uptime shown in the persistent footer. */ private appStartedAt; /** Start of the current agent run; unlike app uptime, resets per task. */ private agentStartedAt; private pasteDialog; private codeBlockCounter; private messageCache; private helpOpen; private helpScrollIndex; private statusOpen; private settingsState; private autocomplete; private mention; private confirmOpen; private confirmOptions; private confirmSelection; private hunkPicker; private menuOpen; private menuTitle; /** Filtered view shown to the user; derived from `menuItemsAll` + `menuFilter`. */ private menuItems; /** Full unfiltered list captured on `showSelect`. */ private menuItemsAll; private menuFilter; private menuIndex; private menuCurrentValue; private menuCallback; private settingsOpen; private permissionOpen; private permissionIndex; private permissionPath; private permissionIsProject; private permissionCallback; private sessionPickerOpen; private sessionPickerIndex; private sessionPickerItems; private sessionPickerCallback; private sessionPickerDeleteMode; private sessionPickerDeleteCallback; private searchOpen; private searchQuery; private searchResults; private searchIndex; private searchCallback; private exportOpen; private exportIndex; private exportCallback; private logoutOpen; private logoutIndex; private logoutProviders; private logoutCallback; private showIntro; private introPhase; private introProgress; private introInterval; private introCallback; private isMultilineMode; private loginOpen; private loginStep; /** Overrides the masked step's heading when that screen is reused for a * secret that is not a provider API key — a Telegram bot token, say. * Empty for an ordinary login. */ private secretPrompt; private loginProviders; private loginProviderIndex; private loginApiKey; private loginError; private loginCallback; private static readonly GLITCH_CHARS; private static readonly COMMANDS; constructor(options: AppOptions); /** * Start the application */ start(): void; /** * Stop the application */ stop(): void; /** * Add a message. Autoscrolls only when the user is already at the * bottom — if they scrolled up to read something, new messages must * not yank the view away; the status bar shows a "↓ N new" badge * instead (cleared when they return to the bottom). */ addMessage(message: Message): void; setMessages(messages: Message[]): void; clearMessages(): void; /** * Get all messages (for API history) */ getMessages(): Array<{ role: 'user' | 'assistant' | 'system'; content: string; }>; /** * Scroll to a specific message by index */ scrollToMessage(messageIndex: number): void; /** * Get messages without system messages (for API) */ getChatHistory(): Array<{ role: 'user' | 'assistant'; content: string; }>; /** * Start streaming */ startStreaming(): void; /** * Add streaming chunk */ addStreamChunk(chunk: string): void; /** * End streaming */ endStreaming(): void; /** * Set loading state */ setLoading(loading: boolean): void; /** * Start spinner animation */ private startSpinner; /** * Stop spinner animation */ private stopSpinner; /** * Set agent running state */ setAgentRunning(running: boolean): void; /** * Update agent progress */ updateAgentProgress(iteration: number, action?: { type: string; target: string; result: string; }): void; setAgentMaxIterations(max: number): void; /** * Set agent thinking text */ setAgentThinking(text: string): void; setAgentWaitingForAI(waiting: boolean): void; addAgentLog(entry: string): void; /** * Paste from system clipboard (Ctrl+V) */ private pasteFromClipboard; /** * Handle paste detection - call this when large text is pasted */ handlePaste(text: string): void; /** * Handle paste info key events */ private handlePasteInfoKey; /** * Show notification */ notify(message: string, duration?: number): void; /** * Show warning toast (orange) — replaces itself if called repeatedly. * Used for API errors / retries so they don't pollute the chat. */ notifyWarn(message: string, duration?: number): void; /** * Show list selection (inline menu below status bar) */ showList(title: string, items: string[], callback: (index: number) => void): void; /** * Show settings (inline, below status bar) */ showSettings(): void; /** * Show confirmation dialog */ showConfirm(options: ConfirmOptions): void; /** * Take the confirmation down without answering it. * * For a question that was settled somewhere else — today that means a phone * answered it over Telegram. Neither callback fires: the decision has already * been made and taken, and running `onCancel` here would deny a tool the user * just approved. * * A no-op when nothing is open, so the caller can dismiss unconditionally * rather than racing to check first. */ dismissConfirm(reason?: string): void; /** * Show the interactive hunk picker (`/apply --interactive`). * The caller passes pre-built items + an `onComplete` callback. */ showHunkPicker(options: HunkPickerOptions): void; /** * Show permission dialog (inline, below status bar) */ showPermission(projectPath: string, isProject: boolean, callback: (level: 'none' | 'read' | 'write') => void): void; /** * Show session picker (inline, below status bar) */ showSessionPicker(sessions: Array<{ name: string; messageCount: number; createdAt: string; }>, callback: (sessionName: string | null) => void, deleteCallback?: (sessionName: string) => void): void; /** * Show search screen */ showSearch(query: string, results: Array<{ role: string; messageIndex: number; matchedText: string; }>, callback: (messageIndex: number) => void): void; /** * Show export screen */ showExport(callback: (format: 'md' | 'json' | 'txt') => void): void; /** * Show logout picker */ showLogoutPicker(providers: Array<{ id: string; name: string; isCurrent: boolean; }>, callback: (providerId: string | 'all' | null) => void): void; /** * Start intro animation */ startIntro(callback: () => void): void; /** * Skip intro animation */ private skipIntro; /** * Finish intro animation */ private finishIntro; /** * Show inline login dialog */ showLogin(providers: Array<{ id: string; name: string; description?: string; subscribeUrl?: string; }>, callback: (result: { providerId: string; apiKey: string; } | null) => void): void; /** * Ask for one secret, masked, with no provider step. * * The login screen already takes a credential without echoing it, and a second * implementation of that is a second place to get masking wrong. This reuses * it and replaces only the heading: "Enter API Key for Z.AI" is the wrong * sentence for a Telegram bot token, and a prompt naming the wrong thing is * how someone pastes the wrong thing. */ showSecret(prompt: string, callback: (secret: string | null) => void): void; /** * Reinitialize screen (after external screen takeover) */ reinitScreen(): void; /** * Show inline menu (renders below status bar) */ showSelect(title: string, items: SelectItem[], currentValue: string, callback: (item: SelectItem) => void): void; /** * Recompute the visible menu list from the current filter string. * Matches across key/label/description, case-insensitive. Keeps the * current value highlighted if it survives the filter; otherwise * cursor returns to the top. */ private applyMenuFilter; /** * Handle keyboard input */ private handleKey; /** * Handle chat screen keys */ private handleChatKey; /** * Update autocomplete suggestions */ private updateAutocomplete; /** * Replace the in-progress `@query` (from `mentionAtStart` to the * cursor) with the selected mention's path. Keeps the `@` prefix and * positions the cursor right after the inserted path so the user can * keep typing the rest of the message. */ private applyMentionSelection; /** * Handle inline status keys */ private handleInlineStatusKey; /** * Handle help screen keys */ private handleInlineHelpKey; /** * Handle inline settings keys */ private handleInlineSettingsKey; /** * Handle search screen keys */ private handleSearchKey; /** * Handle export screen keys */ private handleExportKey; /** * Handle logout picker keys */ private handleLogoutKey; /** * Handle login keys */ private handleLoginKey; /** * Handle inline menu keys */ private handleMenuKey; /** * Handle permission dialog keys */ private handleInlinePermissionKey; private handleInlineSessionPickerKey; private handleInlineConfirmKey; /** * Handle keys in the interactive hunk picker. * y / Enter / → accept this hunk, advance * n / ← skip this hunk, advance * a accept this + all remaining, finish * q / Esc finish without accepting this hunk * ↑ / ↓ navigate (preview only — no decision) */ private handleHunkPickerKey; /** * Submit the current input buffer (used by Enter and Escape-in-multiline) */ private submitInput; /** * Handle command */ private handleCommand; /** * Render current screen */ scheduleRender(): void; render(): void; /** * Render chat screen */ private renderChat; private shouldRenderAgentTimeline; private renderPersistentHeader; private renderAgentTimelineScreen; private renderAgentContextRail; private renderAgentKeyHints; private timelineStatusStyle; private currentActionType; private currentAgentTask; /** * Render inline confirmation dialog below status bar */ private renderInlineConfirm; private renderInlineHunkPicker; /** * Render input line */ private renderInput; /** * Render inline menu below status bar */ private renderInlineMenu; /** * Render inline settings below status bar */ private renderInlineSettings; /** * Render inline help below status bar */ private renderInlineStatus; private renderInlineHelp; /** * Render inline autocomplete below status bar */ private renderInlineAutocomplete; /** * Render inline `@mention` file picker below the status bar. * * Mirrors the layout of `renderInlineAutocomplete` (separator → title → * items → footer) but shows file paths with their parent directory as * the description, and a `@` prefix instead of `/`. */ private renderInlineMentionPicker; /** * Render inline permission dialog */ private renderInlinePermission; /** * Render inline session picker */ private renderInlineSessionPicker; /** * Render inline paste info below status bar */ private renderInlinePasteInfo; /** * Render inline agent progress below status bar (LiveCodeStream style) */ private renderInlineAgentProgress; /** * Get color for action type */ /** * Render status bar */ /** * @param canScroll false when the caller renders instead of the transcript * rather than above it, so there is nothing on screen for PgDn to move. */ private renderStatusBar; /** * Get visible messages (including streaming) */ private getVisibleMessages; /** * Render inline search screen */ private renderInlineSearch; /** * Render inline export screen */ private renderInlineExport; /** * Render inline logout picker */ private renderInlineLogout; /** * Render inline login dialog */ private renderInlineLogin; /** * Render intro animation */ private renderIntro; /** * Get decrypted logo for intro animation */ private getDecryptedLogo; }