Agent skill
script-kit-terminal
Terminal integration for Script Kit GPUI using Alacritty terminal emulator and portable-pty. Use when working with terminal/, term_prompt.rs, PTY handling, terminal rendering, escape sequences, selection, scrollback, or ANSI colors.
Install this agent skill to your Project
npx add-skill https://github.com/johnlindquist/script-kit-next/tree/main/.opencode/skill/script-kit-terminal
SKILL.md
Script Kit Terminal Integration
Embedded terminal functionality using Alacritty's terminal emulator backend and portable-pty for cross-platform PTY support. Enables await term("command") in Script Kit scripts.
Architecture
PTY Output -> VTE Parser -> Terminal Grid -> GPUI Render
^ |
|___ keyboard input ________|
Core Components
| Module | Purpose |
|---|---|
terminal/mod.rs |
Module exports, TerminalEvent enum |
terminal/pty.rs |
Cross-platform PTY via portable-pty |
terminal/alacritty.rs |
Alacritty term wrapper, escape parsing |
terminal/theme_adapter.rs |
Theme-to-terminal color mapping |
term_prompt.rs |
GPUI component, keyboard/mouse handling |
Integration Layer (App Shell)
| Module | Purpose |
|---|---|
render_prompts/term.rs |
Renders terminal WITH app shell (header/footer) |
prompt_handler.rs |
Creates TermPrompt entity from SDK messages |
app_execute.rs |
Creates TermPrompt for quick terminal commands |
window_resize.rs |
Layout constants: MAX_HEIGHT, FOOTER_HEIGHT |
Height Calculation (CRITICAL)
Terminal content height must account for the app shell footer:
use crate::window_resize::layout::{MAX_HEIGHT, FOOTER_HEIGHT};
// WRONG - terminal bottom will be cut off by footer
let term_height = MAX_HEIGHT; // 700px
// CORRECT - subtract footer height
let term_height = MAX_HEIGHT - px(FOOTER_HEIGHT); // 700px - 30px = 670px
The TermPrompt::with_height() receives content_height which it uses to calculate terminal rows via resize_if_needed(). If this doesn't account for the footer, the bottom ~2 rows are hidden.
TerminalHandle (alacritty.rs)
Main terminal emulator wrapper. Thread-safe via Arc<Mutex<>>.
Creation
// Default shell
let term = TerminalHandle::new(cols, rows)?;
// Specific command (sends to interactive shell)
let term = TerminalHandle::with_command("htop", 80, 24)?;
// Custom scrollback
let term = TerminalHandle::with_scrollback(80, 24, 10_000)?;
Processing Loop
// Non-blocking - reads from background thread channel
let (had_output, events) = terminal.process();
// had_output: true if grid content changed
// events: Vec<TerminalEvent> (Bell, Title, Exit)
Key Methods
| Method | Purpose |
|---|---|
input(&[u8]) |
Send keyboard bytes to PTY |
resize(cols, rows) |
Resize terminal and PTY |
content() |
Get TerminalContent snapshot for rendering |
scroll(delta) |
Scroll display (positive=up into history) |
scroll_to_bottom() |
Jump to latest output |
display_offset() |
Current scroll position (0=bottom) |
is_running() |
Check if child process alive |
is_bracketed_paste_mode() |
Check paste mode for proper paste handling |
Selection API
terminal.start_selection(col, row); // Simple drag
terminal.start_semantic_selection(col, row); // Word (double-click)
terminal.start_line_selection(col, row); // Line (triple-click)
terminal.update_selection(col, row); // Extend
terminal.selection_to_string(); // Get text
terminal.clear_selection();
PtyManager (pty.rs)
Cross-platform PTY using portable-pty.
Platform Support
- macOS/Linux: Native PTY via
/dev/ptmx - Windows: ConPTY (Windows 10 1809+)
Key Features
- Background reader thread for non-blocking I/O
- Automatic shell detection (
$SHELL/%COMSPEC%) - Environment setup:
TERM=xterm-256color,COLORTERM=truecolor - Resize sends SIGWINCH to child process
let mut pty = PtyManager::with_size(cols, rows)?;
pty.write_all(b"ls -la\n")?;
pty.resize(100, 50)?;
TermPrompt (term_prompt.rs)
GPUI component for terminal UI.
Creation
TermPrompt::with_height(
id: String,
command: Option<String>, // Optional initial command
focus_handle: FocusHandle,
on_submit: SubmitCallback, // Callback on exit/escape
theme: Arc<Theme>,
config: Arc<Config>,
content_height: Option<Pixels>, // Explicit height (GPUI entities don't inherit flex)
) -> anyhow::Result<Self>
Refresh Timer
Runs at 60fps (REFRESH_INTERVAL_MS = 16). Handles:
- Polling
terminal.process() - Auto-scroll when at bottom
- Bell flash timeout
- Title updates
Keyboard Handling
| Key | Action |
|---|---|
| Escape | Cancel/close |
| Ctrl+C | SIGINT (0x03) or copy if selection |
| Cmd+C | Copy selection, or SIGINT if none |
| Cmd+V | Paste (with bracketed paste mode support) |
| Shift+PageUp/Down | Scroll history |
| Shift+Home/End | Scroll to top/bottom |
| Arrow keys | Send escape sequences (\x1b[A/B/C/D) |
Cell Dimensions
const BASE_FONT_SIZE: f32 = 14.0;
const LINE_HEIGHT_MULTIPLIER: f32 = 1.3;
const BASE_CELL_WIDTH: f32 = 8.5; // Conservative for Menlo
// Scaled to config font size
fn cell_width(&self) -> f32 {
BASE_CELL_WIDTH * (self.font_size() / BASE_FONT_SIZE)
}
Render Optimization
Batches consecutive cells with same styling to reduce element count from ~2400 (80x30) to ~50-100 per frame.
ThemeAdapter (theme_adapter.rs)
Maps Script Kit theme to terminal colors.
Color Mapping
| Theme Property | Terminal Use |
|---|---|
background.main |
Terminal background |
text.primary |
Default foreground |
accent.selected |
Cursor |
accent.selected_subtle |
Selection background |
terminal.* |
All 16 ANSI colors |
Focus Dimming
adapter.update_for_focus(false); // Dim colors 30% toward gray
adapter.update_for_focus(true); // Restore original
TerminalEvent
pub enum TerminalEvent {
Output(String), // Content for rendering
Bell, // BEL character (\x07)
Title(String), // OSC title change
Exit(i32), // Process exit code
}
TerminalContent
Snapshot for rendering:
pub struct TerminalContent {
pub lines: Vec<String>, // Plain text
pub styled_lines: Vec<Vec<TerminalCell>>, // Per-cell styling
pub cursor_line: usize,
pub cursor_col: usize,
pub selected_cells: Vec<(usize, usize)>, // Selection coordinates
}
CellAttributes
Bitflags for text styling:
const BOLD = 0b0000_0000_0000_0001;
const ITALIC = 0b0000_0000_0000_0010;
const UNDERLINE = 0b0000_0000_0000_0100;
const DOUBLE_UNDERLINE = 0b0000_0000_0000_1000;
const UNDERCURL = 0b0000_0000_0001_0000;
const STRIKEOUT = 0b0000_0000_1000_0000;
const INVERSE = 0b0000_0001_0000_0000;
const DIM = 0b0000_0100_0000_0000;
256-Color Palette
Resolved in resolve_indexed_color():
- 0-15: ANSI colors from theme
- 16-231: 6x6x6 color cube
- 232-255: 24 grayscale shades
Common Patterns
Send Control Characters
// Ctrl+C (SIGINT)
terminal.input(&[0x03])?;
// Ctrl+D (EOF)
terminal.input(&[0x04])?;
// Ctrl+Z (SIGTSTP)
terminal.input(&[0x1A])?;
Escape Sequences
// Arrow keys
terminal.input(b"\x1b[A")?; // Up
terminal.input(b"\x1b[B")?; // Down
terminal.input(b"\x1b[C")?; // Right
terminal.input(b"\x1b[D")?; // Left
// Function keys
terminal.input(b"\x1bOP")?; // F1
terminal.input(b"\x1b[15~")?; // F5
Bracketed Paste
if terminal.is_bracketed_paste_mode() {
let wrapped = format!("\x1b[200~{}\x1b[201~", text);
terminal.input(wrapped.as_bytes())?;
} else {
terminal.input(text.as_bytes())?;
}
Thread Safety
TerminalHandle.state:Arc<Mutex<TerminalState>>- Background PTY reader thread communicates via
mpsc::channel reader_stop_flag:AtomicBoolfor clean shutdown- Safe to call
content()from render thread while processing I/O
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
Generate Component Documentation
Based on existing docs styles and specific API implementations, and referencing same name stories, generate comprehensive documentation for the new component.
Generate Component Story
Generate a comprehensive story for a new component for as example.
new-component
How to write a new component of GPUI Component.
troubleshooting
Diagnose and fix common Script Kit issues. Use when the user reports bugs, crashes, missing features, or unexpected behavior in Script Kit GPUI.
script-authoring
Create and manage TypeScript scripts for Script Kit. Use when the user wants to write a new script, edit an existing script, or understand Script Kit's SDK and metadata system.
agents
Create mdflow-backed agent files for Script Kit. Use when the user wants to create AI agents, configure agent backends (Claude, Gemini, Codex), or manage agent metadata.
Didn't find tool you were looking for?