fix: <Text> encoding non-ascii characters in Bun (#760)

This commit is contained in:
Kainoa Kanter
2026-01-31 19:43:13 -08:00
committed by GitHub
parent 0714a8f254
commit 2404014f3d
73 changed files with 214 additions and 128 deletions

View File

@@ -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) {
</Text>
</Box>
<Text> </Text>
<Text dimColor>
Use {upArrow}/{downArrow} to navigate, Enter to select
</Text>
<Text dimColor>Use / to navigate, Enter to select</Text>
</Box>
);
}

View File

@@ -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";

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 = "─";

View File

@@ -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;

View File

@@ -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;

View File

@@ -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[];

View File

@@ -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 = "╌";

View File

@@ -1,5 +1,5 @@
import { Text } from "ink";
import { memo } from "react";
import { Text } from "./Text";
type AssistantLine = {
kind: "assistant";

View File

@@ -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) =>

View File

@@ -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 */

View File

@@ -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";

View File

@@ -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.

View File

@@ -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 " "

View File

@@ -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";

View File

@@ -1,5 +1,5 @@
import { Text } from "ink";
import { memo, useEffect, useState } from "react";
import { Text } from "./Text";
// Default configuration
const DEFAULT_GARBAGE_CHARS = "._";

View File

@@ -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 = "─";

View File

@@ -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 ../
/**

View File

@@ -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;

View File

@@ -1,5 +1,5 @@
import { Text } from "ink";
import { memo } from "react";
import { Text } from "./Text";
type ErrorLine = {
kind: "error";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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 = "─";

View File

@@ -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

View File

@@ -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;

View File

@@ -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 = "╭";

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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: {

View File

@@ -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 }>;

View File

@@ -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;

View File

@@ -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 = "─";

View File

@@ -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 = "─";

View File

@@ -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 = "─";

View File

@@ -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;

View File

@@ -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 = "─";

View File

@@ -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 = "─";

View File

@@ -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 = "─";

View File

@@ -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 = "─";

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 = "─";

View File

@@ -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[];

View File

@@ -1,5 +1,5 @@
import { Text } from "ink";
import { memo } from "react";
import { Text } from "./Text";
type ReasoningLine = {
kind: "reasoning";

View File

@@ -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) =>

View File

@@ -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;

View File

@@ -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

View File

@@ -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;

View File

@@ -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";

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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

View File

@@ -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;

View File

@@ -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 = "─";

View File

@@ -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 <InkText {...props}>{fixBunEncoding(children)}</InkText>;
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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 = "─";

View File

@@ -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 }

View File

@@ -1,5 +1,5 @@
import { Text } from "ink";
import { memo } from "react";
import { Text } from "./Text";
type UserLine = {
kind: "user";

View File

@@ -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";

View File

@@ -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

View File

@@ -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 = "─";

View File

@@ -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 = "╌";

View File

@@ -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 {