diff --git a/src/auth/setup-ui.tsx b/src/auth/setup-ui.tsx index 15555e9..cfbab34 100644 --- a/src/auth/setup-ui.tsx +++ b/src/auth/setup-ui.tsx @@ -3,16 +3,14 @@ */ import { hostname } from "node:os"; -import { Box, Text, useApp, useInput } from "ink"; +import { Box, useApp, useInput } from "ink"; import { useState } from "react"; import { AnimatedLogo } from "../cli/components/AnimatedLogo"; import { colors } from "../cli/components/colors"; +import { Text } from "../cli/components/Text"; import { settingsManager } from "../settings-manager"; import { pollForToken, requestDeviceCode } from "./oauth"; -const upArrow = String.fromCharCode(0x2191); -const downArrow = String.fromCharCode(0x2193); - type SetupMode = "menu" | "device-code" | "auth-code" | "self-host" | "done"; interface SetupUIProps { @@ -189,9 +187,7 @@ export function SetupUI({ onComplete }: SetupUIProps) { - - Use {upArrow}/{downArrow} to navigate, Enter to select - + Use ↑/↓ to navigate, Enter to select ); } diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 2ef2c49..ff97b10 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -14,7 +14,7 @@ import type { } from "@letta-ai/letta-client/resources/agents/messages"; import type { LlmConfig } from "@letta-ai/letta-client/resources/models/models"; import type { StopReasonType } from "@letta-ai/letta-client/resources/runs/runs"; -import { Box, Static, Text } from "ink"; +import { Box, Static } from "ink"; import { useCallback, useEffect, @@ -134,7 +134,6 @@ import { PendingApprovalStub } from "./components/PendingApprovalStub"; import { PinDialog, validateAgentName } from "./components/PinDialog"; import { ProviderSelector } from "./components/ProviderSelector"; import { ReasoningMessage } from "./components/ReasoningMessageRich"; - import { formatUsageStats } from "./components/SessionStats"; // InlinePlanApproval kept for easy rollback if needed // import { InlinePlanApproval } from "./components/InlinePlanApproval"; @@ -143,6 +142,7 @@ import { SubagentGroupDisplay } from "./components/SubagentGroupDisplay"; import { SubagentGroupStatic } from "./components/SubagentGroupStatic"; import { SubagentManager } from "./components/SubagentManager"; import { SystemPromptSelector } from "./components/SystemPromptSelector"; +import { Text } from "./components/Text"; import { ToolCallMessage } from "./components/ToolCallMessageRich"; import { ToolsetSelector } from "./components/ToolsetSelector"; import { UserMessage } from "./components/UserMessageRich"; diff --git a/src/cli/components/AdvancedDiffRenderer.tsx b/src/cli/components/AdvancedDiffRenderer.tsx index c29b1bc..68ada1b 100644 --- a/src/cli/components/AdvancedDiffRenderer.tsx +++ b/src/cli/components/AdvancedDiffRenderer.tsx @@ -1,6 +1,6 @@ import { relative } from "node:path"; import * as Diff from "diff"; -import { Box, Text } from "ink"; +import { Box } from "ink"; import { useMemo } from "react"; import { ADV_DIFF_CONTEXT_LINES, @@ -10,6 +10,7 @@ import { import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; import { EditRenderer, MultiEditRenderer, WriteRenderer } from "./DiffRenderer"; +import { Text } from "./Text"; type EditItem = { old_string: string; diff --git a/src/cli/components/AgentInfoBar.tsx b/src/cli/components/AgentInfoBar.tsx index 0373115..77fdac0 100644 --- a/src/cli/components/AgentInfoBar.tsx +++ b/src/cli/components/AgentInfoBar.tsx @@ -1,10 +1,11 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import Link from "ink-link"; import { memo, useMemo } from "react"; import { DEFAULT_AGENT_NAME } from "../../constants"; import { settingsManager } from "../../settings-manager"; import { getVersion } from "../../version"; import { colors } from "./colors"; +import { Text } from "./Text"; interface AgentInfoBarProps { agentId?: string; diff --git a/src/cli/components/AgentSelector.tsx b/src/cli/components/AgentSelector.tsx index 7b17b89..90b9f3c 100644 --- a/src/cli/components/AgentSelector.tsx +++ b/src/cli/components/AgentSelector.tsx @@ -1,6 +1,6 @@ import type { Letta } from "@letta-ai/letta-client"; import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents"; -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { useCallback, useEffect, useRef, useState } from "react"; import { getClient } from "../../agent/client"; import { getModelDisplayName } from "../../agent/model"; @@ -8,6 +8,7 @@ import { settingsManager } from "../../settings-manager"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; import { MarkdownDisplay } from "./MarkdownDisplay"; +import { Text } from "./Text"; // Horizontal line character (matches approval dialogs) const SOLID_LINE = "─"; diff --git a/src/cli/components/AnimatedLogo.tsx b/src/cli/components/AnimatedLogo.tsx index 6e423ab..58137fb 100644 --- a/src/cli/components/AnimatedLogo.tsx +++ b/src/cli/components/AnimatedLogo.tsx @@ -1,106 +1,95 @@ -import { Text } from "ink"; import { useEffect, useState } from "react"; import { colors } from "./colors"; - -function fixBunEncoding(text: string): string { - if (typeof Bun === "undefined") return text; - - // Replace literal characters with Unicode codepoints - return text - .replace(/█/g, String.fromCharCode(0x2588)) // Full block - .replace(/▓/g, String.fromCharCode(0x2593)) // Dark shade - .replace(/▒/g, String.fromCharCode(0x2592)) // Medium shade - .replace(/░/g, String.fromCharCode(0x2591)); // Light shade -} +import { Text } from "./Text"; // Define animation frames - 3D rotation effect with gradient (█ → ▓ → ▒ → ░) // Each frame is ~10 chars wide, 5 lines tall - matches login dialog asciiLogo size const logoFrames = [ // 1. Front view (fully facing) - ` ██████ + ` ██████ ██ ██ ██ ██ ██ ██ ██ ██████ `, // 2. Just starting to turn right - ` ▓█████ + ` ▓█████ ▓█ ▓█ ▓█ ▓█ ▓█ ▓█ ▓█ ▓█████ `, // 3. Slight right turn - ` ▓▓████ + ` ▓▓████ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓████ `, // 4. More right (gradient deepening) - ` ░▓▓███ + ` ░▓▓███ ░▓▓ ░▓▓ ░▓▓ ░▓ ░▓▓ ░▓▓ ░▓▓ ░▓▓███ `, // 5. Even more right - ` ░░▓▓██ - ░▓▓ ░▓▓ - ░▓▓░▓░▓▓ - ░▓▓ ░▓▓ + ` ░░▓▓██ + ░▓▓ ░▓▓ + ░▓▓░▓░▓▓ + ░▓▓ ░▓▓ ░░▓▓██ `, // 6. Approaching side - ` ░▓▓█ - ░░▓░░▓ - ░░▓▓░▓ - ░░▓░░▓ + ` ░▓▓█ + ░░▓░░▓ + ░░▓▓░▓ + ░░▓░░▓ ░▓▓█ `, // 7. Almost side - ` ░▓▓▓ - ░▓░▓ - ░▓▓▓ - ░▓░▓ + ` ░▓▓▓ + ░▓░▓ + ░▓▓▓ + ░▓░▓ ░▓▓▓ `, // 8. Side view - ` ▓▓▓▓ - ▓▓▓▓ - ▓▓▓▓ - ▓▓▓▓ + ` ▓▓▓▓ + ▓▓▓▓ + ▓▓▓▓ + ▓▓▓▓ ▓▓▓▓ `, // 9. Leaving side (mirror of 7) - ` ▓▓▓░ - ▓░▓░ - ▓▓▓░ - ▓░▓░ + ` ▓▓▓░ + ▓░▓░ + ▓▓▓░ + ▓░▓░ ▓▓▓░ `, // 10. Past side (mirror of 6) - ` █▓▓░ - ▓░░▓░░ - ▓░▓▓░░ - ▓░░▓░░ + ` █▓▓░ + ▓░░▓░░ + ▓░▓▓░░ + ▓░░▓░░ █▓▓░ `, // 11. More past side (mirror of 5) - ` ██▓▓░░ - ▓▓░ ▓▓░ - ▓▓░▓░▓▓░ - ▓▓░ ▓▓░ + ` ██▓▓░░ + ▓▓░ ▓▓░ + ▓▓░▓░▓▓░ + ▓▓░ ▓▓░ ██▓▓░░ `, // 12. Returning (mirror of 4) - ` ███▓▓░ + ` ███▓▓░ ▓▓░ ▓▓░ ▓▓░ ▓░ ▓▓░ ▓▓░ ▓▓░ ███▓▓░ `, // 13. Almost front (mirror of 3) - ` ████▓▓ + ` ████▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ▓▓ ████▓▓ `, // 14. Nearly front (mirror of 2) - ` █████▓ + ` █████▓ █▓ █▓ █▓ █▓ █▓ █▓ █▓ █████▓ `, -].map(fixBunEncoding); +]; interface AnimatedLogoProps { color?: string; diff --git a/src/cli/components/ApprovalDialog.tsx b/src/cli/components/ApprovalDialog.tsx index 27d03a9..43b2584 100644 --- a/src/cli/components/ApprovalDialog.tsx +++ b/src/cli/components/ApprovalDialog.tsx @@ -1,10 +1,11 @@ // Import useInput from vendored Ink for bracketed paste support -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import RawTextInput from "ink-text-input"; import { type ComponentType, useMemo, useState } from "react"; import { type AdvancedDiffSuccess, computeAdvancedDiff } from "../helpers/diff"; import type { ApprovalRequest } from "../helpers/stream"; import { AdvancedDiffRenderer } from "./AdvancedDiffRenderer"; +import { Text } from "./Text"; type Props = { approvalRequest: ApprovalRequest; diff --git a/src/cli/components/ApprovalDialogRich.tsx b/src/cli/components/ApprovalDialogRich.tsx index 4e1ac53..5ca9718 100644 --- a/src/cli/components/ApprovalDialogRich.tsx +++ b/src/cli/components/ApprovalDialogRich.tsx @@ -1,5 +1,5 @@ // Import useInput from vendored Ink for bracketed paste support -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import type React from "react"; import { memo, useEffect, useMemo, useState } from "react"; import type { ApprovalContext } from "../../permissions/analyzer"; @@ -14,6 +14,7 @@ import type { ApprovalRequest } from "../helpers/stream"; import { AdvancedDiffRenderer } from "./AdvancedDiffRenderer"; import { colors } from "./colors"; import { PasteAwareTextInput } from "./PasteAwareTextInput"; +import { Text } from "./Text"; type Props = { approvals: ApprovalRequest[]; diff --git a/src/cli/components/ApprovalPreview.tsx b/src/cli/components/ApprovalPreview.tsx index 8ffe90d..2b67906 100644 --- a/src/cli/components/ApprovalPreview.tsx +++ b/src/cli/components/ApprovalPreview.tsx @@ -1,4 +1,4 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; import type { AdvancedDiffSuccess } from "../helpers/diff"; import { parsePatchOperations } from "../helpers/formatArgsDisplay"; @@ -7,6 +7,7 @@ import { AdvancedDiffRenderer } from "./AdvancedDiffRenderer"; import { colors } from "./colors"; import { BashPreview } from "./previews/BashPreview"; import { PlanPreview } from "./previews/PlanPreview"; +import { Text } from "./Text"; const SOLID_LINE = "─"; const DOTTED_LINE = "╌"; diff --git a/src/cli/components/AssistantMessage.tsx b/src/cli/components/AssistantMessage.tsx index fcc8b25..933a8db 100644 --- a/src/cli/components/AssistantMessage.tsx +++ b/src/cli/components/AssistantMessage.tsx @@ -1,5 +1,5 @@ -import { Text } from "ink"; import { memo } from "react"; +import { Text } from "./Text"; type AssistantLine = { kind: "assistant"; diff --git a/src/cli/components/AssistantMessageRich.tsx b/src/cli/components/AssistantMessageRich.tsx index b4f50d3..0715b60 100644 --- a/src/cli/components/AssistantMessageRich.tsx +++ b/src/cli/components/AssistantMessageRich.tsx @@ -1,7 +1,8 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { MarkdownDisplay } from "./MarkdownDisplay.js"; +import { Text } from "./Text"; // Helper function to normalize text - copied from old codebase const normalize = (s: string) => diff --git a/src/cli/components/Autocomplete.tsx b/src/cli/components/Autocomplete.tsx index 99311de..b35d309 100644 --- a/src/cli/components/Autocomplete.tsx +++ b/src/cli/components/Autocomplete.tsx @@ -1,6 +1,7 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import type { ReactNode } from "react"; import { colors } from "./colors"; +import { Text } from "./Text"; interface AutocompleteBoxProps { /** Optional header text shown at top of autocomplete */ diff --git a/src/cli/components/BashCommandMessage.tsx b/src/cli/components/BashCommandMessage.tsx index 93092ce..b6b3d66 100644 --- a/src/cli/components/BashCommandMessage.tsx +++ b/src/cli/components/BashCommandMessage.tsx @@ -1,4 +1,4 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; import { INTERRUPTED_BY_USER } from "../../constants"; import type { StreamingState } from "../helpers/accumulator"; @@ -8,6 +8,7 @@ import { CollapsedOutputDisplay } from "./CollapsedOutputDisplay"; import { colors } from "./colors.js"; import { MarkdownDisplay } from "./MarkdownDisplay.js"; import { StreamingOutputDisplay } from "./StreamingOutputDisplay"; +import { Text } from "./Text"; type BashCommandLine = { kind: "bash_command"; diff --git a/src/cli/components/BlinkDot.tsx b/src/cli/components/BlinkDot.tsx index b04e2c9..6ec52c8 100644 --- a/src/cli/components/BlinkDot.tsx +++ b/src/cli/components/BlinkDot.tsx @@ -1,7 +1,7 @@ -import { Text } from "ink"; import { memo, useEffect, useState } from "react"; import { useAnimation } from "../contexts/AnimationContext.js"; import { colors } from "./colors.js"; +import { Text } from "./Text"; /** * A blinking dot indicator for running/pending states. diff --git a/src/cli/components/CollapsedOutputDisplay.tsx b/src/cli/components/CollapsedOutputDisplay.tsx index c649ad7..558a2e3 100644 --- a/src/cli/components/CollapsedOutputDisplay.tsx +++ b/src/cli/components/CollapsedOutputDisplay.tsx @@ -1,7 +1,8 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { MarkdownDisplay } from "./MarkdownDisplay"; +import { Text } from "./Text"; const DEFAULT_COLLAPSED_LINES = 3; const PREFIX_WIDTH = 5; // " ⎿ " or " " diff --git a/src/cli/components/CommandMessage.tsx b/src/cli/components/CommandMessage.tsx index f0b5625..809512f 100644 --- a/src/cli/components/CommandMessage.tsx +++ b/src/cli/components/CommandMessage.tsx @@ -1,9 +1,10 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { BlinkDot } from "./BlinkDot.js"; import { colors } from "./colors.js"; import { MarkdownDisplay } from "./MarkdownDisplay.js"; +import { Text } from "./Text"; type CommandLine = { kind: "command"; diff --git a/src/cli/components/CompactingAnimation.tsx b/src/cli/components/CompactingAnimation.tsx index df7af0e..b09fbd9 100644 --- a/src/cli/components/CompactingAnimation.tsx +++ b/src/cli/components/CompactingAnimation.tsx @@ -1,5 +1,5 @@ -import { Text } from "ink"; import { memo, useEffect, useState } from "react"; +import { Text } from "./Text"; // Default configuration const DEFAULT_GARBAGE_CHARS = "._"; diff --git a/src/cli/components/ConversationSelector.tsx b/src/cli/components/ConversationSelector.tsx index 8528b99..957055f 100644 --- a/src/cli/components/ConversationSelector.tsx +++ b/src/cli/components/ConversationSelector.tsx @@ -1,13 +1,14 @@ import type { Letta } from "@letta-ai/letta-client"; import type { Message } from "@letta-ai/letta-client/resources/agents/messages"; import type { Conversation } from "@letta-ai/letta-client/resources/conversations/conversations"; -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { useCallback, useEffect, useRef, useState } from "react"; import { getClient } from "../../agent/client"; import { SYSTEM_REMINDER_OPEN } from "../../constants"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; import { MarkdownDisplay } from "./MarkdownDisplay"; +import { Text } from "./Text"; // Horizontal line character (matches approval dialogs) const SOLID_LINE = "─"; diff --git a/src/cli/components/DiffRenderer.tsx b/src/cli/components/DiffRenderer.tsx index fecd184..9507b57 100644 --- a/src/cli/components/DiffRenderer.tsx +++ b/src/cli/components/DiffRenderer.tsx @@ -1,8 +1,9 @@ import { relative } from "node:path"; import * as Diff from "diff"; -import { Box, Text } from "ink"; +import { Box } from "ink"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; +import { Text } from "./Text"; // Helper to format path as relative with ../ /** diff --git a/src/cli/components/EnterPlanModeDialog.tsx b/src/cli/components/EnterPlanModeDialog.tsx index bc970bf..5503e7a 100644 --- a/src/cli/components/EnterPlanModeDialog.tsx +++ b/src/cli/components/EnterPlanModeDialog.tsx @@ -1,7 +1,8 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { memo, useState } from "react"; import { useProgressIndicator } from "../hooks/useProgressIndicator"; import { colors } from "./colors"; +import { Text } from "./Text"; type Props = { onApprove: () => void; diff --git a/src/cli/components/ErrorMessage.tsx b/src/cli/components/ErrorMessage.tsx index 46cdf8f..4e2c323 100644 --- a/src/cli/components/ErrorMessage.tsx +++ b/src/cli/components/ErrorMessage.tsx @@ -1,5 +1,5 @@ -import { Text } from "ink"; import { memo } from "react"; +import { Text } from "./Text"; type ErrorLine = { kind: "error"; diff --git a/src/cli/components/ErrorMessageRich.tsx b/src/cli/components/ErrorMessageRich.tsx index da01e7f..07d5cbf 100644 --- a/src/cli/components/ErrorMessageRich.tsx +++ b/src/cli/components/ErrorMessageRich.tsx @@ -1,6 +1,7 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; +import { Text } from "./Text"; type ErrorLine = { kind: "error"; diff --git a/src/cli/components/EventMessage.tsx b/src/cli/components/EventMessage.tsx index d677f42..d6e8e0b 100644 --- a/src/cli/components/EventMessage.tsx +++ b/src/cli/components/EventMessage.tsx @@ -1,10 +1,11 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; import { COMPACTION_SUMMARY_HEADER } from "../../constants"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { BlinkDot } from "./BlinkDot.js"; import { CompactingAnimation } from "./CompactingAnimation"; import { colors } from "./colors.js"; +import { Text } from "./Text"; type EventLine = { kind: "event"; diff --git a/src/cli/components/FeedbackDialog.tsx b/src/cli/components/FeedbackDialog.tsx index fc6d9d2..09e4699 100644 --- a/src/cli/components/FeedbackDialog.tsx +++ b/src/cli/components/FeedbackDialog.tsx @@ -1,8 +1,9 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { useState } from "react"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; import { PasteAwareTextInput } from "./PasteAwareTextInput"; +import { Text } from "./Text"; const SOLID_LINE = "─"; diff --git a/src/cli/components/FileAutocomplete.tsx b/src/cli/components/FileAutocomplete.tsx index e713f77..041ce11 100644 --- a/src/cli/components/FileAutocomplete.tsx +++ b/src/cli/components/FileAutocomplete.tsx @@ -1,9 +1,9 @@ -import { Text } from "ink"; import { useEffect, useRef, useState } from "react"; import { searchFiles } from "../helpers/fileSearch"; import { useAutocompleteNavigation } from "../hooks/useAutocompleteNavigation"; import { AutocompleteBox, AutocompleteItem } from "./Autocomplete"; import { colors } from "./colors"; +import { Text } from "./Text"; import type { AutocompleteProps, FileMatch } from "./types/autocomplete"; // Extract the text after the "@" symbol where the cursor is positioned diff --git a/src/cli/components/HelpDialog.tsx b/src/cli/components/HelpDialog.tsx index d00b7c5..c85d6ec 100644 --- a/src/cli/components/HelpDialog.tsx +++ b/src/cli/components/HelpDialog.tsx @@ -1,8 +1,9 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { useCallback, useEffect, useMemo, useState } from "react"; import { getVersion } from "../../version"; import { commands } from "../commands/registry"; import { colors } from "./colors"; +import { Text } from "./Text"; const PAGE_SIZE = 10; diff --git a/src/cli/components/HooksManager.tsx b/src/cli/components/HooksManager.tsx index 032d2f0..9b16b27 100644 --- a/src/cli/components/HooksManager.tsx +++ b/src/cli/components/HooksManager.tsx @@ -1,7 +1,7 @@ // src/cli/components/HooksManager.tsx // Interactive TUI for managing hooks configuration -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { memo, useCallback, useEffect, useRef, useState } from "react"; import { type HookEvent, @@ -29,6 +29,7 @@ import { settingsManager } from "../../settings-manager"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; import { PasteAwareTextInput } from "./PasteAwareTextInput"; +import { Text } from "./Text"; // Box drawing characters const BOX_TOP_LEFT = "╭"; diff --git a/src/cli/components/InlineBashApproval.tsx b/src/cli/components/InlineBashApproval.tsx index a1b5b2b..3d6c59b 100644 --- a/src/cli/components/InlineBashApproval.tsx +++ b/src/cli/components/InlineBashApproval.tsx @@ -1,9 +1,10 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { memo, useMemo, useState } from "react"; import { useProgressIndicator } from "../hooks/useProgressIndicator"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { useTextInputCursor } from "../hooks/useTextInputCursor"; import { colors } from "./colors"; +import { Text } from "./Text"; type BashInfo = { toolName: string; diff --git a/src/cli/components/InlineEnterPlanModeApproval.tsx b/src/cli/components/InlineEnterPlanModeApproval.tsx index 1d7f21f..b0645a8 100644 --- a/src/cli/components/InlineEnterPlanModeApproval.tsx +++ b/src/cli/components/InlineEnterPlanModeApproval.tsx @@ -1,8 +1,9 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { memo, useState } from "react"; import { useProgressIndicator } from "../hooks/useProgressIndicator"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; +import { Text } from "./Text"; type Props = { onApprove: () => void; diff --git a/src/cli/components/InlineFileEditApproval.tsx b/src/cli/components/InlineFileEditApproval.tsx index 79e0005..62c19c6 100644 --- a/src/cli/components/InlineFileEditApproval.tsx +++ b/src/cli/components/InlineFileEditApproval.tsx @@ -1,4 +1,4 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { memo, useMemo, useState } from "react"; import type { AdvancedDiffSuccess } from "../helpers/diff"; import { parsePatchToAdvancedDiff } from "../helpers/diff"; @@ -8,6 +8,7 @@ import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { useTextInputCursor } from "../hooks/useTextInputCursor"; import { AdvancedDiffRenderer } from "./AdvancedDiffRenderer"; import { colors } from "./colors"; +import { Text } from "./Text"; type FileEditInfo = { toolName: string; diff --git a/src/cli/components/InlineGenericApproval.tsx b/src/cli/components/InlineGenericApproval.tsx index 6393315..68072d5 100644 --- a/src/cli/components/InlineGenericApproval.tsx +++ b/src/cli/components/InlineGenericApproval.tsx @@ -1,9 +1,10 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { memo, useMemo, useState } from "react"; import { useProgressIndicator } from "../hooks/useProgressIndicator"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { useTextInputCursor } from "../hooks/useTextInputCursor"; import { colors } from "./colors"; +import { Text } from "./Text"; type Props = { toolName: string; diff --git a/src/cli/components/InlineMarkdownRenderer.tsx b/src/cli/components/InlineMarkdownRenderer.tsx index 7afee01..2c2b62d 100644 --- a/src/cli/components/InlineMarkdownRenderer.tsx +++ b/src/cli/components/InlineMarkdownRenderer.tsx @@ -1,6 +1,6 @@ -import { Text } from "ink"; import type React from "react"; import { colors } from "./colors.js"; +import { Text } from "./Text"; interface InlineMarkdownProps { text: string; diff --git a/src/cli/components/InlinePlanApproval.tsx b/src/cli/components/InlinePlanApproval.tsx index 7c9cb51..1c056a4 100644 --- a/src/cli/components/InlinePlanApproval.tsx +++ b/src/cli/components/InlinePlanApproval.tsx @@ -1,10 +1,11 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { memo, useMemo, useState } from "react"; import { useProgressIndicator } from "../hooks/useProgressIndicator"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { useTextInputCursor } from "../hooks/useTextInputCursor"; import { colors } from "./colors"; import { MarkdownDisplay } from "./MarkdownDisplay"; +import { Text } from "./Text"; type Props = { plan: string; diff --git a/src/cli/components/InlineQuestionApproval.tsx b/src/cli/components/InlineQuestionApproval.tsx index fb4047c..c5492ee 100644 --- a/src/cli/components/InlineQuestionApproval.tsx +++ b/src/cli/components/InlineQuestionApproval.tsx @@ -1,9 +1,10 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { Fragment, memo, useMemo, useState } from "react"; import { useProgressIndicator } from "../hooks/useProgressIndicator"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { useTextInputCursor } from "../hooks/useTextInputCursor"; import { colors } from "./colors"; +import { Text } from "./Text"; interface QuestionOption { label: string; diff --git a/src/cli/components/InlineTaskApproval.tsx b/src/cli/components/InlineTaskApproval.tsx index a1af3d7..6397262 100644 --- a/src/cli/components/InlineTaskApproval.tsx +++ b/src/cli/components/InlineTaskApproval.tsx @@ -1,9 +1,10 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { memo, useMemo, useState } from "react"; import { useProgressIndicator } from "../hooks/useProgressIndicator"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { useTextInputCursor } from "../hooks/useTextInputCursor"; import { colors } from "./colors"; +import { Text } from "./Text"; type Props = { taskInfo: { diff --git a/src/cli/components/InputRich.tsx b/src/cli/components/InputRich.tsx index 70bf028..3858961 100644 --- a/src/cli/components/InputRich.tsx +++ b/src/cli/components/InputRich.tsx @@ -3,7 +3,7 @@ import { EventEmitter } from "node:events"; import { stdin } from "node:process"; import chalk from "chalk"; -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import SpinnerLib from "ink-spinner"; import { type ComponentType, @@ -30,6 +30,7 @@ import { InputAssist } from "./InputAssist"; import { PasteAwareTextInput } from "./PasteAwareTextInput"; import { QueuedMessages } from "./QueuedMessages"; import { ShimmerText } from "./ShimmerText"; +import { Text } from "./Text"; // Type assertion for ink-spinner compatibility const Spinner = SpinnerLib as ComponentType<{ type?: string }>; diff --git a/src/cli/components/MarkdownDisplay.tsx b/src/cli/components/MarkdownDisplay.tsx index 7b44edf..4e84044 100644 --- a/src/cli/components/MarkdownDisplay.tsx +++ b/src/cli/components/MarkdownDisplay.tsx @@ -1,8 +1,9 @@ -import { Box, Text, Transform } from "ink"; +import { Box, Transform } from "ink"; import type React from "react"; import stringWidth from "string-width"; import { colors, hexToBgAnsi } from "./colors.js"; import { InlineMarkdown } from "./InlineMarkdownRenderer.js"; +import { Text } from "./Text"; interface MarkdownDisplayProps { text: string; diff --git a/src/cli/components/McpConnectFlow.tsx b/src/cli/components/McpConnectFlow.tsx index 02983a6..e171f8e 100644 --- a/src/cli/components/McpConnectFlow.tsx +++ b/src/cli/components/McpConnectFlow.tsx @@ -3,9 +3,8 @@ * Flow: Select transport → Enter URL → Connect (OAuth if needed) → Enter name → Create */ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { memo, useCallback, useState } from "react"; - import { getClient } from "../../agent/client"; import { connectMcpServer, @@ -16,6 +15,7 @@ import { import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; import { PasteAwareTextInput } from "./PasteAwareTextInput"; +import { Text } from "./Text"; const SOLID_LINE = "─"; diff --git a/src/cli/components/McpSelector.tsx b/src/cli/components/McpSelector.tsx index c2e0c33..8855190 100644 --- a/src/cli/components/McpSelector.tsx +++ b/src/cli/components/McpSelector.tsx @@ -4,11 +4,12 @@ import type { StreamableHTTPMcpServer, } from "@letta-ai/letta-client/resources/mcp-servers/mcp-servers"; import type { Tool } from "@letta-ai/letta-client/resources/tools"; -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { memo, useCallback, useEffect, useState } from "react"; import { getClient } from "../../agent/client"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; +import { Text } from "./Text"; // Horizontal line character (matches approval dialogs) const SOLID_LINE = "─"; diff --git a/src/cli/components/MemfsTreeViewer.tsx b/src/cli/components/MemfsTreeViewer.tsx index 9dae8d5..da1feab 100644 --- a/src/cli/components/MemfsTreeViewer.tsx +++ b/src/cli/components/MemfsTreeViewer.tsx @@ -1,6 +1,6 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; import { join, relative } from "node:path"; -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import Link from "ink-link"; import { useMemo, useState } from "react"; import { @@ -9,6 +9,7 @@ import { } from "../../agent/memoryFilesystem"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; +import { Text } from "./Text"; // Line characters const SOLID_LINE = "─"; diff --git a/src/cli/components/MemoryDiffRenderer.tsx b/src/cli/components/MemoryDiffRenderer.tsx index 9aa264f..c6dff88 100644 --- a/src/cli/components/MemoryDiffRenderer.tsx +++ b/src/cli/components/MemoryDiffRenderer.tsx @@ -1,7 +1,8 @@ import * as Diff from "diff"; -import { Box, Text } from "ink"; +import { Box } from "ink"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; +import { Text } from "./Text"; interface MemoryDiffRendererProps { argsText: string; diff --git a/src/cli/components/MemoryTabViewer.tsx b/src/cli/components/MemoryTabViewer.tsx index fdcbc0d..94c61e1 100644 --- a/src/cli/components/MemoryTabViewer.tsx +++ b/src/cli/components/MemoryTabViewer.tsx @@ -1,11 +1,12 @@ import type { Block } from "@letta-ai/letta-client/resources/agents/blocks"; -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import Link from "ink-link"; import { useEffect, useState } from "react"; import { getClient } from "../../agent/client"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; import { MarkdownDisplay } from "./MarkdownDisplay"; +import { Text } from "./Text"; // Horizontal line character (matches approval dialogs) const SOLID_LINE = "─"; diff --git a/src/cli/components/MessageSearch.tsx b/src/cli/components/MessageSearch.tsx index 5148a6b..cc1b828 100644 --- a/src/cli/components/MessageSearch.tsx +++ b/src/cli/components/MessageSearch.tsx @@ -1,10 +1,11 @@ import type { Letta } from "@letta-ai/letta-client"; import type { MessageSearchResponse } from "@letta-ai/letta-client/resources/messages"; -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { useCallback, useEffect, useRef, useState } from "react"; import { getClient } from "../../agent/client"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; +import { Text } from "./Text"; // Horizontal line character (matches approval dialogs) const SOLID_LINE = "─"; diff --git a/src/cli/components/ModelSelector.tsx b/src/cli/components/ModelSelector.tsx index c6029f7..0a2520b 100644 --- a/src/cli/components/ModelSelector.tsx +++ b/src/cli/components/ModelSelector.tsx @@ -1,5 +1,5 @@ // Import useInput from vendored Ink for bracketed paste support -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { clearAvailableModelsCache, @@ -9,6 +9,7 @@ import { import { models } from "../../agent/model"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; +import { Text } from "./Text"; // Horizontal line character (matches approval dialogs) const SOLID_LINE = "─"; diff --git a/src/cli/components/NewAgentDialog.tsx b/src/cli/components/NewAgentDialog.tsx index 4fb27ce..b4dcadb 100644 --- a/src/cli/components/NewAgentDialog.tsx +++ b/src/cli/components/NewAgentDialog.tsx @@ -1,10 +1,11 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { useState } from "react"; import { DEFAULT_AGENT_NAME } from "../../constants"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; import { PasteAwareTextInput } from "./PasteAwareTextInput"; import { validateAgentName } from "./PinDialog"; +import { Text } from "./Text"; // Horizontal line character (matches other selectors) const SOLID_LINE = "─"; diff --git a/src/cli/components/PendingApprovalStub.tsx b/src/cli/components/PendingApprovalStub.tsx index 6622409..cd66e59 100644 --- a/src/cli/components/PendingApprovalStub.tsx +++ b/src/cli/components/PendingApprovalStub.tsx @@ -1,5 +1,6 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; +import { Text } from "./Text"; type Props = { toolName: string; diff --git a/src/cli/components/PinDialog.tsx b/src/cli/components/PinDialog.tsx index 421f1cb..1ac82d2 100644 --- a/src/cli/components/PinDialog.tsx +++ b/src/cli/components/PinDialog.tsx @@ -1,8 +1,9 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { useState } from "react"; import { DEFAULT_AGENT_NAME } from "../../constants"; import { colors } from "./colors"; import { PasteAwareTextInput } from "./PasteAwareTextInput"; +import { Text } from "./Text"; interface PinDialogProps { currentName: string; diff --git a/src/cli/components/PlanModeDialog.tsx b/src/cli/components/PlanModeDialog.tsx index d1334b2..3291655 100644 --- a/src/cli/components/PlanModeDialog.tsx +++ b/src/cli/components/PlanModeDialog.tsx @@ -1,10 +1,11 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { memo, useState } from "react"; import { resolvePlaceholders } from "../helpers/pasteRegistry"; import { useProgressIndicator } from "../hooks/useProgressIndicator"; import { colors } from "./colors"; import { MarkdownDisplay } from "./MarkdownDisplay"; import { PasteAwareTextInput } from "./PasteAwareTextInput"; +import { Text } from "./Text"; type Props = { plan: string; diff --git a/src/cli/components/PlanRenderer.tsx b/src/cli/components/PlanRenderer.tsx index 17e5201..0d53cb1 100644 --- a/src/cli/components/PlanRenderer.tsx +++ b/src/cli/components/PlanRenderer.tsx @@ -1,7 +1,8 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import type React from "react"; import { useTerminalWidth } from "../hooks/useTerminalWidth.js"; import { colors } from "./colors.js"; +import { Text } from "./Text"; interface PlanItem { step: string; diff --git a/src/cli/components/ProviderSelector.tsx b/src/cli/components/ProviderSelector.tsx index 609b5c0..e2c29ca 100644 --- a/src/cli/components/ProviderSelector.tsx +++ b/src/cli/components/ProviderSelector.tsx @@ -1,4 +1,4 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { useCallback, useEffect, useRef, useState } from "react"; import { type AuthMethod, @@ -17,6 +17,7 @@ import { } from "../../utils/aws-credentials"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; +import { Text } from "./Text"; const SOLID_LINE = "─"; diff --git a/src/cli/components/QueuedMessages.tsx b/src/cli/components/QueuedMessages.tsx index bbe9cc1..286afee 100644 --- a/src/cli/components/QueuedMessages.tsx +++ b/src/cli/components/QueuedMessages.tsx @@ -1,5 +1,6 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; +import { Text } from "./Text"; interface QueuedMessagesProps { messages: string[]; diff --git a/src/cli/components/ReasoningMessage.tsx b/src/cli/components/ReasoningMessage.tsx index 4f9fc31..6016583 100644 --- a/src/cli/components/ReasoningMessage.tsx +++ b/src/cli/components/ReasoningMessage.tsx @@ -1,5 +1,5 @@ -import { Text } from "ink"; import { memo } from "react"; +import { Text } from "./Text"; type ReasoningLine = { kind: "reasoning"; diff --git a/src/cli/components/ReasoningMessageRich.tsx b/src/cli/components/ReasoningMessageRich.tsx index 258134f..1c9d78f 100644 --- a/src/cli/components/ReasoningMessageRich.tsx +++ b/src/cli/components/ReasoningMessageRich.tsx @@ -1,7 +1,8 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { MarkdownDisplay } from "./MarkdownDisplay.js"; +import { Text } from "./Text"; // Helper function to normalize text - copied from old codebase const normalize = (s: string) => diff --git a/src/cli/components/ShimmerText.tsx b/src/cli/components/ShimmerText.tsx index 6d043ff..e1d1148 100644 --- a/src/cli/components/ShimmerText.tsx +++ b/src/cli/components/ShimmerText.tsx @@ -1,7 +1,7 @@ import chalk from "chalk"; -import { Text } from "ink"; import { memo } from "react"; import { colors } from "./colors.js"; +import { Text } from "./Text"; interface ShimmerTextProps { color?: string; diff --git a/src/cli/components/SlashCommandAutocomplete.tsx b/src/cli/components/SlashCommandAutocomplete.tsx index 66d97da..c36a354 100644 --- a/src/cli/components/SlashCommandAutocomplete.tsx +++ b/src/cli/components/SlashCommandAutocomplete.tsx @@ -1,9 +1,9 @@ -import { Text } from "ink"; import { useEffect, useMemo, useState } from "react"; import { settingsManager } from "../../settings-manager"; import { commands } from "../commands/registry"; import { useAutocompleteNavigation } from "../hooks/useAutocompleteNavigation"; import { AutocompleteBox, AutocompleteItem } from "./Autocomplete"; +import { Text } from "./Text"; import type { AutocompleteProps, CommandMatch } from "./types/autocomplete"; const VISIBLE_COMMANDS = 8; // Number of commands visible at once diff --git a/src/cli/components/StaticPlanApproval.tsx b/src/cli/components/StaticPlanApproval.tsx index 5806116..5ccf766 100644 --- a/src/cli/components/StaticPlanApproval.tsx +++ b/src/cli/components/StaticPlanApproval.tsx @@ -1,9 +1,10 @@ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { memo, useState } from "react"; import { useProgressIndicator } from "../hooks/useProgressIndicator"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { useTextInputCursor } from "../hooks/useTextInputCursor"; import { colors } from "./colors"; +import { Text } from "./Text"; type Props = { onApprove: () => void; diff --git a/src/cli/components/StatusMessage.tsx b/src/cli/components/StatusMessage.tsx index dce4c80..07019fd 100644 --- a/src/cli/components/StatusMessage.tsx +++ b/src/cli/components/StatusMessage.tsx @@ -1,7 +1,8 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; +import { Text } from "./Text"; type StatusLine = { kind: "status"; diff --git a/src/cli/components/StreamingOutputDisplay.tsx b/src/cli/components/StreamingOutputDisplay.tsx index 27f408b..ec6fb63 100644 --- a/src/cli/components/StreamingOutputDisplay.tsx +++ b/src/cli/components/StreamingOutputDisplay.tsx @@ -1,6 +1,7 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo, useEffect, useState } from "react"; import type { StreamingState } from "../helpers/accumulator"; +import { Text } from "./Text"; interface StreamingOutputDisplayProps { streaming: StreamingState; diff --git a/src/cli/components/SubagentGroupDisplay.tsx b/src/cli/components/SubagentGroupDisplay.tsx index 598eeea..8306905 100644 --- a/src/cli/components/SubagentGroupDisplay.tsx +++ b/src/cli/components/SubagentGroupDisplay.tsx @@ -15,7 +15,7 @@ * SubagentGroupStatic instead (a pure props-based snapshot with no hooks). */ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { memo, useSyncExternalStore } from "react"; import { useAnimation } from "../contexts/AnimationContext.js"; import { formatStats, getTreeChars } from "../helpers/subagentDisplay.js"; @@ -28,6 +28,7 @@ import { import { useTerminalWidth } from "../hooks/useTerminalWidth.js"; import { BlinkDot } from "./BlinkDot.js"; import { colors } from "./colors.js"; +import { Text } from "./Text"; function formatToolArgs(argsStr: string): string { try { diff --git a/src/cli/components/SubagentGroupStatic.tsx b/src/cli/components/SubagentGroupStatic.tsx index a074b31..8624ea9 100644 --- a/src/cli/components/SubagentGroupStatic.tsx +++ b/src/cli/components/SubagentGroupStatic.tsx @@ -13,11 +13,12 @@ * Shows: "Ran N subagents" with final stats (tool count, tokens). */ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; import { formatStats, getTreeChars } from "../helpers/subagentDisplay.js"; import { useTerminalWidth } from "../hooks/useTerminalWidth.js"; import { colors } from "./colors.js"; +import { Text } from "./Text"; // ============================================================================ // Types diff --git a/src/cli/components/SubagentManager.tsx b/src/cli/components/SubagentManager.tsx index f4f855c..98d4b58 100644 --- a/src/cli/components/SubagentManager.tsx +++ b/src/cli/components/SubagentManager.tsx @@ -2,7 +2,7 @@ * SubagentManager component - displays available subagents */ -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { useEffect, useState } from "react"; import { AGENTS_DIR, @@ -13,6 +13,7 @@ import { type SubagentConfig, } from "../../agent/subagents"; import { colors } from "./colors"; +import { Text } from "./Text"; interface SubagentManagerProps { onClose: () => void; diff --git a/src/cli/components/SystemPromptSelector.tsx b/src/cli/components/SystemPromptSelector.tsx index 627f399..58da2eb 100644 --- a/src/cli/components/SystemPromptSelector.tsx +++ b/src/cli/components/SystemPromptSelector.tsx @@ -1,9 +1,10 @@ // Import useInput from vendored Ink for bracketed paste support -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { useMemo, useState } from "react"; import { SYSTEM_PROMPTS } from "../../agent/promptAssets"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; +import { Text } from "./Text"; // Horizontal line character (matches approval dialogs) const SOLID_LINE = "─"; diff --git a/src/cli/components/Text.tsx b/src/cli/components/Text.tsx new file mode 100644 index 0000000..aa125d2 --- /dev/null +++ b/src/cli/components/Text.tsx @@ -0,0 +1,45 @@ +import { Text as InkText, type TextProps } from "ink"; +import type { ReactNode } from "react"; + +const isBun = typeof Bun !== "undefined"; +const decoder = new TextDecoder("utf-8", { fatal: false }); + +function fixBunEncoding(value: ReactNode): ReactNode { + if (!isBun) return value; + + if (typeof value === "string") { + // Quick check: if no non-ASCII characters, return as-is + if (!/[\x80-\xFF]/.test(value)) return value; + + const bytes: number[] = []; + + for (let i = 0; i < value.length; i++) { + const code = value.charCodeAt(i); + + // Check for 2-byte UTF-8 sequence: 0xC2 followed by 0x80-0xBF + if (code === 0xc2 && i + 1 < value.length) { + const nextCode = value.charCodeAt(i + 1); + if (nextCode >= 0x80 && nextCode <= 0xbf) { + bytes.push(0xc2, nextCode); + i++; + continue; + } + } + + bytes.push(code); + } + + return decoder.decode(new Uint8Array(bytes)); + } + + // Handle arrays of children + if (Array.isArray(value)) { + return value.map(fixBunEncoding); + } + + return value; +} + +export function Text({ children, ...props }: TextProps) { + return {fixBunEncoding(children)}; +} diff --git a/src/cli/components/TodoRenderer.tsx b/src/cli/components/TodoRenderer.tsx index a1fd0e0..d93d453 100644 --- a/src/cli/components/TodoRenderer.tsx +++ b/src/cli/components/TodoRenderer.tsx @@ -1,7 +1,8 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import type React from "react"; import { useTerminalWidth } from "../hooks/useTerminalWidth.js"; import { colors } from "./colors.js"; +import { Text } from "./Text"; interface TodoItem { content: string; diff --git a/src/cli/components/ToolCallMessageRich.tsx b/src/cli/components/ToolCallMessageRich.tsx index acdf6db..69e8912 100644 --- a/src/cli/components/ToolCallMessageRich.tsx +++ b/src/cli/components/ToolCallMessageRich.tsx @@ -1,6 +1,6 @@ // existsSync, readFileSync removed - no longer needed since plan content // is shown via StaticPlanApproval during approval, not in tool result -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; import { INTERRUPTED_BY_USER } from "../../constants"; import { clipToolReturn } from "../../tools/manager.js"; @@ -23,6 +23,7 @@ import { isTaskTool, isTodoTool, } from "../helpers/toolNameMapping.js"; +import { Text } from "./Text"; /** * Check if tool is AskUserQuestion diff --git a/src/cli/components/ToolsetSelector.tsx b/src/cli/components/ToolsetSelector.tsx index 16fef93..5cca7ff 100644 --- a/src/cli/components/ToolsetSelector.tsx +++ b/src/cli/components/ToolsetSelector.tsx @@ -1,8 +1,9 @@ // Import useInput from vendored Ink for bracketed paste support -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import { useMemo, useState } from "react"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors } from "./colors"; +import { Text } from "./Text"; // Horizontal line character (matches approval dialogs) const SOLID_LINE = "─"; diff --git a/src/cli/components/Transcript.tsx b/src/cli/components/Transcript.tsx index 01c8057..78c1eaa 100644 --- a/src/cli/components/Transcript.tsx +++ b/src/cli/components/Transcript.tsx @@ -1,4 +1,5 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; +import { Text } from "./Text"; export type Row = | { kind: "user"; text: string; id?: string } diff --git a/src/cli/components/UserMessage.tsx b/src/cli/components/UserMessage.tsx index 5cf5403..9c9101a 100644 --- a/src/cli/components/UserMessage.tsx +++ b/src/cli/components/UserMessage.tsx @@ -1,5 +1,5 @@ -import { Text } from "ink"; import { memo } from "react"; +import { Text } from "./Text"; type UserLine = { kind: "user"; diff --git a/src/cli/components/UserMessageRich.tsx b/src/cli/components/UserMessageRich.tsx index de7418b..0c8d120 100644 --- a/src/cli/components/UserMessageRich.tsx +++ b/src/cli/components/UserMessageRich.tsx @@ -1,9 +1,9 @@ -import { Text } from "ink"; import { memo } from "react"; import stringWidth from "string-width"; import { SYSTEM_REMINDER_CLOSE, SYSTEM_REMINDER_OPEN } from "../../constants"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { colors, hexToBgAnsi, hexToFgAnsi } from "./colors"; +import { Text } from "./Text"; type UserLine = { kind: "user"; diff --git a/src/cli/components/WelcomeScreen.tsx b/src/cli/components/WelcomeScreen.tsx index 82e9158..03666b4 100644 --- a/src/cli/components/WelcomeScreen.tsx +++ b/src/cli/components/WelcomeScreen.tsx @@ -1,8 +1,7 @@ import { homedir } from "node:os"; import type { Letta } from "@letta-ai/letta-client"; -import { Box, Text } from "ink"; +import { Box } from "ink"; import { useEffect, useState } from "react"; - import type { AgentProvenance } from "../../agent/create"; import { getModelDisplayName } from "../../agent/model"; import { settingsManager } from "../../settings-manager"; @@ -10,6 +9,7 @@ import { getVersion } from "../../version"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { AnimatedLogo } from "./AnimatedLogo"; import { colors } from "./colors"; +import { Text } from "./Text"; /** * Convert absolute path to use ~ for home directory diff --git a/src/cli/components/previews/BashPreview.tsx b/src/cli/components/previews/BashPreview.tsx index 06e8da1..a47ff40 100644 --- a/src/cli/components/previews/BashPreview.tsx +++ b/src/cli/components/previews/BashPreview.tsx @@ -1,7 +1,8 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; import { useTerminalWidth } from "../../hooks/useTerminalWidth"; import { colors } from "../colors"; +import { Text } from "../Text"; const SOLID_LINE = "─"; diff --git a/src/cli/components/previews/PlanPreview.tsx b/src/cli/components/previews/PlanPreview.tsx index 2fa770e..161cbac 100644 --- a/src/cli/components/previews/PlanPreview.tsx +++ b/src/cli/components/previews/PlanPreview.tsx @@ -1,8 +1,9 @@ -import { Box, Text } from "ink"; +import { Box } from "ink"; import { memo } from "react"; import { useTerminalWidth } from "../../hooks/useTerminalWidth"; import { colors } from "../colors"; import { MarkdownDisplay } from "../MarkdownDisplay"; +import { Text } from "../Text"; const SOLID_LINE = "─"; const DOTTED_LINE = "╌"; diff --git a/src/cli/profile-selection.tsx b/src/cli/profile-selection.tsx index 41ea9b7..f5ef1b5 100644 --- a/src/cli/profile-selection.tsx +++ b/src/cli/profile-selection.tsx @@ -4,11 +4,12 @@ */ import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents"; -import { Box, Text, useInput } from "ink"; +import { Box, useInput } from "ink"; import React, { useCallback, useEffect, useState } from "react"; import { getClient } from "../agent/client"; import { settingsManager } from "../settings-manager"; import { colors } from "./components/colors"; +import { Text } from "./components/Text"; import { WelcomeScreen } from "./components/WelcomeScreen"; interface ProfileOption {