Files
claude-code/packages/@ant/ink/docs/12-terminal-integration.md
claude-code-best 2fb1c9dcd8 feat: 工具层及 mcp 大重构 (#252)
* feat: 第一版大重构

* fix: 修复类型问题

* chore: 更新版本到 1.3.2

* Add brave as alternative WebSearchTool

* fix: 修正顺序

* fix: 修复对穷鬼模式的 auto dream 和 session memory 越过

* feat: 穷鬼模式去除 session-summary

* feat: 创建 builtin-tools 包,搬运所有工具实现

将 src/tools/ 下的全部 60 个工具目录迁移至 packages/builtin-tools/src/tools/,
内部导入路径已更新为 src/ alias 模式。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: 更新 src/ 中所有工具引用至 builtin-tools 包,删除 src/tools/

- src/tools.ts 及 178 个 src/ 文件的 import 路径从 ./tools/ 改为 builtin-tools/tools/
- 删除 src/tools/ 整个目录(已迁移至 packages/builtin-tools/)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: 添加 builtin-tools 路径别名至 tsconfig,更新 bun.lock

- tsconfig.json 新增 builtin-tools/* 和 builtin-tools 路径映射
- 新增 packages/builtin-tools/src 至 include

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: 为 builtin-tools、mcp-client、agent-tools 添加 @claude-code-best 作用域前缀

所有包名及 import 路径统一添加 @claude-code-best/ 前缀:
- builtin-tools → @claude-code-best/builtin-tools
- mcp-client → @claude-code-best/mcp-client
- agent-tools → @claude-code-best/agent-tools

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 修复 node 环境没有 bun 的问题

---------

Co-authored-by: Eric-Guo <eric.guocz@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 09:52:05 +08:00

8.0 KiB

Chapter 12: Terminal Integration

This chapter covers terminal-specific features: alternate screen, mouse tracking, clipboard, notifications, and terminal querying.

Alternate Screen

Enter a fullscreen alternate screen buffer (like vim, less, htop).

import { AlternateScreen } from '@anthropic/ink'

<AlternateScreen mouseTracking={true}>
  <Box flexDirection="column" height="100%">
    <Text>Fullscreen content</Text>
  </Box>
</AlternateScreen>

Props

Prop Type Default Description
children ReactNode - Content
mouseTracking boolean true Enable SGR mouse tracking

Behavior

On mount:

  1. Enters DEC 1049 alternate screen buffer
  2. Hides cursor
  3. Enables mouse tracking (if mouseTracking=true)
  4. Constrains rendering height to terminal rows

On unmount:

  1. Exits alternate screen buffer
  2. Shows cursor
  3. Disables mouse tracking
  4. Restores original terminal content

Mouse Tracking Modes

When enabled:

  • Mode 1003 -- Button press/release + motion (hover)
  • Mode 1006 -- SGR extended mouse format (coordinates > 223)
  • Wheel events -- Scroll up/down

External Editor Handoff

The Ink instance supports pausing for an external editor:

// Pause Ink, run external command, resume
ink.enterAlternateScreen()  // Save state
// ... external editor runs ...
ink.reassertTerminalModes()  // Restore on resume

This is triggered by Ctrl+Z (SIGTSTP) and SIGCONT.

Mouse Events

Click Events

<Box onClick={(event) => {
  console.log(`Clicked at col=${event.x}, row=${event.y}`)
  event.stopImmediatePropagation()
}}>
  <Text>Clickable area</Text>
</Box>

Multi-Click

Double-click selects a word, triple-click selects a line. Handled by the App component:

// App prop
onMultiClick: (col: number, row: number, count: 2 | 3) => void

Hover Events

<Box
  onMouseEnter={() => setHovered(true)}
  onMouseLeave={() => setHovered(false)}
>
  <Text>{hovered ? 'Hovered!' : 'Hover me'}</Text>
</Box>

Hover uses mouseenter/mouseleave semantics (no bubbling between children).

Drag-to-Select

In alt-screen mode, click-drag creates a text selection:

// App prop
onSelectionDrag: (col: number, row: number) => void

Clipboard

OSC 52 Clipboard

import { setClipboard } from '@anthropic/ink'

await setClipboard('Copied text')

Copy Selection

const { copySelection } = useSelection()
const text = copySelection()  // Copies to clipboard and clears highlight

Copy Without Clear

const { copySelectionNoClear } = useSelection()
const text = copySelectionNoClear()  // Copies but keeps highlight

Terminal Notifications

Send desktop notifications from the terminal.

import { useTerminalNotification } from '@anthropic/ink'

function MyComponent() {
  const { notifyBell, progress } = useTerminalNotification()

  // Terminal bell (audible/system notification)
  notifyBell()

  // Progress bar in terminal title/tab
  progress('running', 65)    // 65% complete
  progress('completed')       // Done
  progress('error')           // Error state
  progress('indeterminate')   // Unknown progress
  progress(null)              // Clear
}

Terminal-Specific Notifications

const { notifyITerm2, notifyKitty, notifyGhostty } = useTerminalNotification()

// iTerm2
notifyITerm2({ message: 'Build complete', title: 'My App' })

// Kitty
notifyKitty({ message: 'Build complete', title: 'My App', id: 1 })

// Ghostty
notifyGhostty({ message: 'Build complete', title: 'My App' })

Terminal Queries

Background Color (OSC 11)

Used for auto-theme detection:

import { getTerminalBackground } from '@anthropic/ink'
const bg = await getTerminalBackground()
// e.g., 'rgb:0000/0000/0000' (dark) or 'rgb:ffff/ffff/ffff' (light)

Terminal Version (XTVERSION)

import { isXtermJs, setXtversionName, getXtversionName } from '@anthropic/ink'

Feature Detection

import { supportsHyperlinks } from '@anthropic/ink'

if (supportsHyperlinks()) {
  // OSC 8 hyperlinks supported
}

import { supportsExtendedKeys } from '@anthropic/ink'

if (supportsExtendedKeys()) {
  // Kitty keyboard protocol / modifyOtherKeys available
}

Terminal Focus

Track terminal window focus/unfocus:

import { useTerminalFocus } from '@anthropic/ink'

const isFocused = useTerminalFocus()

Low-level API:

import { getTerminalFocused, subscribeTerminalFocus } from '@anthropic/ink'

getTerminalFocused()  // boolean
subscribeTerminalFocus((focused: boolean) => {
  // Called on focus change
})

Uses DECSET 1004 focus reporting.

Terminal Title

Set the terminal window title:

import { useTerminalTitle } from '@anthropic/ink'

useTerminalTitle('My App - Dashboard')

Clear:

useTerminalTitle(null)

Terminal I/O Sequences

Low-level ANSI sequence constants for advanced use.

Cursor Control

import {
  SHOW_CURSOR,
  HIDE_CURSOR,
  CURSOR_HOME,
} from '@anthropic/ink'

// cursorPosition(row, col) -- Move cursor to absolute position
// cursorMove(dx, dy) -- Move cursor relative

Screen Control

import {
  ENTER_ALT_SCREEN,
  EXIT_ALT_SCREEN,
  ERASE_SCREEN,
} from '@anthropic/ink'

Mouse Control

import {
  ENABLE_MOUSE_TRACKING,
  DISABLE_MOUSE_TRACKING,
} from '@anthropic/ink'

Keyboard Protocols

import {
  ENABLE_KITTY_KEYBOARD,
  DISABLE_KITTY_KEYBOARD,
  ENABLE_MODIFY_OTHER_KEYS,
  DISABLE_MODIFY_OTHER_KEYS,
} from '@anthropic/ink'

Clipboard & Tab Status

import {
  CLEAR_ITERM2_PROGRESS,
  CLEAR_TAB_STATUS,
  CLEAR_TERMINAL_TITLE,
  wrapForMultiplexer,
} from '@anthropic/ink'

wrapForMultiplexer wraps OSC sequences for tmux compatibility.

Terminal Compatibility

Supported Terminals

Terminal Features
iTerm2 Full support (hyperlinks, notifications, progress)
Kitty Full support (keyboard protocol, notifications)
Ghostty Full support
WezTerm Full support
Alacritty Most features
Windows Terminal Most features
Apple Terminal 256-color fallback
xterm.js (VS Code) Detected and special-cased
tmux Wrapped sequences via wrapForMultiplexer
Screen Basic support

Feature Degradation

The framework gracefully degrades:

  • No true color → Falls back to ANSI 16-color themes
  • No OSC 52 → Clipboard operations silently fail
  • No mouse tracking → Click/hover events are no-ops
  • No extended keys → Standard escape sequences used
  • No bracketed paste → Paste detected by timing heuristic

Synchronized Output

import { isSynchronizedOutputSupported } from '@anthropic/ink'

if (isSynchronizedOutputSupported()) {
  // BSU/ESU for tear-free rendering
}

Uses DECSET 2026 synchronized output to prevent partial frame display.

Bracketed Paste

Uses DECSET 2004 to distinguish paste events from rapid typing. Automatically enabled by the App component.

Text Selection (Alt-Screen)

Selection State

type SelectionState = {
  anchor: Point | null        // Drag start
  focus: Point | null         // Current position
  isDragging: boolean
  anchorSpan: { lo: Point; hi: Point; kind: 'word' | 'line' } | null
  scrolledOffAbove: string[]  // Text scrolled out above
  scrolledOffBelow: string[]  // Text scrolled out below
}

Selection Operations

  • Click-drag -- Free-form selection
  • Double-click -- Word selection
  • Triple-click -- Line selection
  • Shift+Arrow -- Extend selection from keyboard
  • Drag-to-scroll -- Auto-scroll when dragging near edges

noSelect Regions

Exclude areas from selection (gutters, line numbers):

<Box noSelect={true}>
  <Text>1 </Text>
</Box>
<Box>
  <Text>code here</Text>  {/* Only this is selectable */}
</Box>

Soft-Wrap Awareness

Selection correctly handles text that was wrapped across multiple rows:

  • Wrapped lines are joined when copied
  • Trailing whitespace is trimmed
  • The softWrap bitmap tracks which rows are continuations