[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-82807":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":8,"htmlUrl":8,"language":9,"languages":8,"totalLinesOfCode":8,"stars":10,"forks":11,"watchers":12,"openIssues":13,"contributorsCount":13,"subscribersCount":13,"size":13,"stars1d":14,"stars7d":15,"stars30d":16,"stars90d":13,"forks30d":13,"starsTrendScore":17,"compositeScore":18,"rankGlobal":8,"rankLanguage":8,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":20,"hasPages":20,"topics":22,"createdAt":8,"pushedAt":8,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":13,"starSnapshotCount":13,"syncStatus":26,"lastSyncTime":27,"discoverSource":28},82807,"terminal-control","kitlangton\u002Fterminal-control","kitlangton",null,"Rust",214,9,82,0,1,65,132,14,3,"MIT License",false,"main",[],"2026-06-12 02:04:28","# Terminal Control\n\nControl, inspect, test, and capture real terminal applications for agents and TUI review.\n\n[![crates.io](https:\u002F\u002Fimg.shields.io\u002Fcrates\u002Fv\u002Fterminal-control.svg)](https:\u002F\u002Fcrates.io\u002Fcrates\u002Fterminal-control)\n[![CI](https:\u002F\u002Fgithub.com\u002Fkitlangton\u002Fterminal-control\u002Factions\u002Fworkflows\u002Fci.yml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fkitlangton\u002Fterminal-control\u002Factions\u002Fworkflows\u002Fci.yml)\n\n![OpenCode answering a playful terminal request](https:\u002F\u002Fraw.githubusercontent.com\u002Fkitlangton\u002Fterminal-control\u002Fmain\u002Fdocs\u002Fscreenshots\u002Fopencode-haikus.png)\n\nSaved from one live OpenCode session using `start`, `send`, and `save`.\n\n## Install\n\nRequires Rust 1.93 or newer. Video export also requires `ffmpeg`.\n\n```bash\ncargo install terminal-control\ntermctrl --help\n```\n\nInstall the current repository head instead of the latest crate release:\n\n```bash\ncargo install --locked --git https:\u002F\u002Fgithub.com\u002Fkitlangton\u002Fterminal-control terminal-control\n```\n\n## Show A Terminal Screen\n\nRun a program in a PTY and print its visible terminal state:\n\n```bash\ntermctrl show --cols 100 --rows 32 -- my-terminal-app\n```\n\nShow is the routine observation command: it prints visible text to standard output and creates no files. Request a different stdout-readable representation explicitly:\n\n```bash\ntermctrl show --format json -- my-terminal-app\ntermctrl show --format svg -- my-terminal-app\n```\n\nWait for an application to mount, then interact before reading its screen:\n\n```bash\ntermctrl show --cols 100 --rows 32 --wait-for \"Commands\" \\\n  -s ctrl-p text:model enter -- my-terminal-app\n```\n\nOpenTUI applications such as OpenCode require the opt-in host handshake:\n\n```bash\ntermctrl show --host opentui --cols 112 --rows 34 \\\n  --wait-for \"\u002Fconnect\" -- opencode\n```\n\n## Save Evidence\n\nWrite only the artifact formats you request:\n\n```bash\ntermctrl save --format png --out captures\u002Fhome.png -- my-terminal-app\ntermctrl save --format png --format txt --out captures\u002Fmodel -- my-terminal-app\n```\n\nThe second command writes `captures\u002Fmodel.png` and `captures\u002Fmodel.txt`. ANSI stream artifacts can contain sensitive terminal data and are only produced when explicitly requested with `--format ansi`.\n\n## Control A Live TUI\n\nUse a named session when several observations or interactions should target the same running application:\n\n```bash\ntermctrl start demo --host opentui --cols 112 --rows 34 -- opencode\ntermctrl status demo\ntermctrl wait demo \"\u002Fconnect\" --timeout 5000\ntermctrl show demo\ntermctrl send demo text:\u002Fconnect enter\ntermctrl resize demo --cols 132 --rows 38\ntermctrl wait demo \"Connect a provider\" --timeout 5000\ntermctrl show demo\ntermctrl save demo --format png --out captures\u002Fprovider.png\ntermctrl stop demo\n```\n\n`status` reports `running` or `exited`, the effective working directory, command, viewport, and recording path. An exited session retains its final screen for `show` until it is stopped. `list` distinguishes unavailable stale sockets from incompatible older session protocols.\n\n`send` accepts `ctrl-a` through `ctrl-z`, keys such as `enter`, `escape`, arrows, `tab`, `shift-tab`, `backspace`, `delete`, `home`, `end`, `page-up`, and `page-down`, plus typed input as `text:\u003Cvalue>`. Use `ctrl-c` to interrupt work or pipe exact prompt bytes with `--stdin`:\n\n```bash\nprintf '%s' 'Summarize the active view.' | termctrl send demo --stdin\n```\n\n`resize` controls the terminal viewport and records geometry changes in `.termctrl` timelines when recording is enabled. A session whose retained ANSI transcript has already been truncated cannot be resized because its current screen cannot be replayed at a new size safely.\n\nFor normal-screen tools and long-running log processes, inspect retained scrollback directly:\n\n```bash\ntermctrl logs demo\ntermctrl logs demo --ansi > captures\u002Fdemo-output.ansi\n```\n\nFull-screen alternate-screen TUIs do not provide useful terminal logs; read their visible screen with `show` or retain a recording timeline instead. Status exposes `logs_truncated` after raw retained ANSI reaches `--max-bytes`; the session continues running and retains its most recent transcript bytes.\n\nRestart a single named owner safely when deploying updated code:\n\n```bash\ntermctrl restart demo\n```\n\n`restart NAME` reuses the prior command, effective working directory, viewport, host profile, color policy, and recording path by default. Supply options or a replacement command only when deliberately changing the launch.\n\n## Record A Video\n\nRecord a session timeline and replay it as an MP4:\n\n```bash\ntermctrl start demo --record captures\u002Fdemo.termctrl \\\n  --host opentui --cols 112 --rows 34 -- opencode\ntermctrl wait demo \"Ask anything\"\ntermctrl send demo --pace-ms 35 'text:Write a short terminal haiku. End with the uppercase form of done.' enter\ntermctrl wait demo \"DONE\" --timeout 60000\ntermctrl save demo --format png --out captures\u002Fanswer.png\ntermctrl stop demo\n\ntermctrl video captures\u002Fdemo.termctrl --hide-cursor --out captures\u002Fdemo.mp4\n```\n\nVideo export trims startup frames before non-whitespace text by default, while still preserving recordings that only paint terminal backgrounds. Use `--include-startup` to keep all startup frames, or `--max-idle-ms 600` to intentionally shorten quiet gaps.\n\nRecordings are JSON Lines files containing terminal output, client input, and automatic host input; they can include prompts or secrets. Treat them as sensitive artifacts.\n\n## Sources And Formats\n\nRepeat `--format` to export only what you need:\n\n```bash\ntermctrl save --format png --format txt --out captures\u002Fhome -- my-terminal-app\n```\n\nRead a current visible screen directly for agent inspection, or select JSON\u002FANSI\u002FSVG explicitly:\n\n```bash\ntermctrl show demo\ntermctrl show demo --format json\n```\n\nFor commands whose useful output is piped, use `--pipe`. Pipe reads force color by default; pass `--color never` for plain output:\n\n```bash\ntermctrl save --pipe --format png --format txt --cols 100 --rows 16 \\\n  --out captures\u002Flog -- my-command\n```\n\nOne-off `show` and `save` operations own disposable command processes: once the visible screen is read or saved, the launched process tree is terminated. Use `start` for long-running applications.\n\nRender an existing ANSI\u002FVT terminal stream without launching a process. An `.ansi` file is a conventionally named byte stream of terminal output and escape sequences, not a separate container format:\n\n```bash\nprintf '\\033[44;97m terminal output \\033[0m\\n' | termctrl show --input -\nprintf '\\033[44;97m terminal output \\033[0m\\n' | termctrl save --input - --format png --out captures\u002Fstdin.png\n```\n\n## Rust Library And Formats\n\nThe crate also exposes the shot engine, live sessions, and artifact model to Rust callers. The CLI is built on the same `terminal_control::shot`, `terminal_control::session`, `terminal_control::frame`, `terminal_control::render`, and `terminal_control::recording` modules:\n\n```rust\nlet shot = terminal_control::shot::from_ansi(b\"\\x1b[32mready\\x1b[0m\".to_vec(), 1, 20, 1024)?;\nassert_eq!(shot.frame.text(), \"ready\");\nlet svg = terminal_control::render::svg(&shot.frame, &terminal_control::render::Options::default());\n```\n\nA library session keeps one PTY-backed application in process for fast test interaction without repeatedly invoking the CLI:\n\n```rust\nuse std::time::Duration;\n\nlet mut session = terminal_control::session::Session::start(\n    &[\"my-terminal-app\".to_owned()],\n    None,\n    None,\n    &terminal_control::shot::Options::default(),\n)?;\nsession.wait_for_text(\"Ready\", Duration::from_secs(5))?;\nlet status = session.status()?;\nsession.send(b\"help\\r\")?;\nsession.wait_for_idle(Duration::from_millis(250), Duration::from_secs(5))?;\nlet capture = session.capture(Duration::from_millis(250), Duration::from_secs(5))?;\nlet shot = capture.shot;\nlet exit = session.wait_for_exit(Duration::from_secs(5))?;\nsession.stop()?;\n```\n\nStructured output is versioned for external tools:\n\n- A `save --format json` capture is a `Frame` object with `version: 1`, described by `schemas\u002Fframe-v1.schema.json`.\n- A `.termctrl` recording is JSON Lines: its first line is a versioned header and subsequent lines are timed output, input, or resize entries, each described by `schemas\u002Frecording-entry-v1.schema.json`.\n- Recording byte arrays contain the original terminal or input bytes as integers from `0` to `255`; recordings can contain sensitive text or input.\n\n`session::Session` is the embedded lifecycle interface; the flat named-session CLI commands and the external driver are adapters over the same implementation.\n\n## External Driver\n\nExternal agent tooling can keep multiple embedded sessions alive through a versioned JSON Lines protocol over stdin\u002Fstdout:\n\n```bash\ntermctrl driver\n```\n\nThe driver writes a `hello` message with protocol and Terminal Control versions, then accepts typed operations including `launch`, `status`, `send`, `waitForText`, `waitForIdle`, `waitForExit`, `capture`, `logs`, `recording`, `resize`, `stop`, and `shutdown`. It is intended for clients such as a TypeScript TUI test or agent-control library, while the shell-facing flat commands remain convenient for individual workflows.\n\n```json\n{\"type\":\"hello\",\"protocolVersion\":1,\"terminalControlVersion\":\"\u003Cinstalled-version>\"}\n{\"id\":1,\"method\":\"launch\",\"sessionId\":\"app\",\"params\":{\"command\":[\"my-terminal-app\"],\"cols\":100,\"rows\":30,\"inheritEnv\":false,\"env\":{\"TERM\":\"xterm-256color\"}}}\n{\"id\":2,\"method\":\"waitForText\",\"sessionId\":\"app\",\"params\":{\"text\":\"Ready\",\"timeoutMs\":5000}}\n{\"id\":3,\"method\":\"send\",\"sessionId\":\"app\",\"params\":{\"input\":[{\"type\":\"text\",\"value\":\"help\"},{\"type\":\"key\",\"value\":\"enter\"}]}}\n{\"id\":4,\"method\":\"capture\",\"sessionId\":\"app\",\"params\":{\"settleMs\":250,\"deadlineMs\":5000}}\n```\n\nA driver `capture` response contains a structured visible frame, derived `text`, and a completion `reason`: `idle`, `deadline`, `exited`, or `outputclosed`. Raw ANSI is omitted by default; request `includeAnsi: true` for retained transcript bytes or `includeSvg: true` for rendered visual evidence. A test client should normally require `idle` or `exited` instead of accepting a deadline fallback as a stable snapshot. Driver input is intentionally exact: text, raw bytes, known key values, and single-letter control input are supported without claiming unimplemented key chords.\n\n## TypeScript Client\n\n`@kitlangton\u002Fterminal-control` exposes the driver as isolated typed test sessions. It deliberately separates the visible screen from readable retained logs and the exact ANSI\u002FVT transcript. Its npm distribution includes an optional native package for macOS or GNU\u002FLinux on arm64 or x64, so consumers do not need a Rust toolchain or separate `termctrl` installation.\n\nAfter the initial npm publication:\n\n```bash\nbun add -d @kitlangton\u002Fterminal-control vitest\n```\n\n```ts\nimport { TerminalControl } from \"@kitlangton\u002Fterminal-control\"\n\nawait using terminal = await TerminalControl.make({\n  artifacts: {\n    directory: \".termctrl-artifacts\",\n    onFailure: true,\n    includeTranscript: false,\n    includeRecording: true,\n  },\n})\nawait using session = await terminal.launch({\n  command: [\"\u002Fabsolute\u002Fpath\u002Fto\u002Fmy-terminal-app\"],\n  viewport: { cols: 100, rows: 30 },\n  inheritEnv: false,\n  env: { TERM: \"xterm-256color\", HOME: \"\u002Ftmp\u002Ftest-home\" },\n  record: \"on-failure\",\n})\n\nawait session.screen.waitForText(\u002FReady\u002F)\nawait session.keyboard.type(\"help\")\nawait session.keyboard.press(\"Enter\")\n\nconst text = await session.screen.text()\nconst frame = await session.screen.frame()\nconst logs = await session.logs.text()\nconst transcript = await session.transcript.ansi()\n\nexpect(text).toMatchSnapshot()\nexpect(frame).toMatchSnapshot()\n\nconst exit = await session.waitForExit({ timeoutMs: 5_000 })\nexpect(exit).toMatchObject({ reason: \"exited\", exit: { code: 0 } })\n```\n\nWhen working directly from this repository before installing the npm artifacts, pass `binaryPath: \".\u002Ftarget\u002Frelease\u002Ftermctrl\"` or set `TERMCTRL_BINARY`.\n\n`session.screen.text()` and `session.screen.frame()` wait for a settled capture and reject deadline or output-closed fallback by default. A test that intentionally needs an intermediate frame can request it explicitly:\n\n```ts\nconst capture = await session.screen.capture({ allowIncomplete: true })\nconsole.log(capture.reason, capture.text, capture.frame)\n```\n\nThis makes ordinary text or frame snapshots stable by default while retaining explicit access to live, incomplete terminal state.\n\nKeyboard presses are typed as the sequences Terminal Control encodes exactly, such as `\"Enter\"`, `\"ArrowDown\"`, or `\"Control+C\"`. Use `session.keyboard.write(bytes)` when a test deliberately needs exact terminal bytes outside that supported key set.\n\nVitest users can add a screen-aware assertion that writes configured artifacts on failure. Standard `toMatchSnapshot()` and `toMatchInlineSnapshot()` remain the simplest snapshot format because visible text is reviewable in source control:\n\n```ts\nimport { expect } from \"vitest\"\nimport { extendTerminalControlMatchers } from \"@kitlangton\u002Fterminal-control\u002Fvitest\"\n\nextendTerminalControlMatchers(expect)\n\nawait expect(session).toHaveScreenText(\"Ready\\n\\nChoose an option\")\nawait expect(session.screen.text()).resolves.toMatchInlineSnapshot()\n```\n\n`session.writeArtifacts(name)` and failing `toHaveScreenText(...)` assertions can write `screen.txt`, `screen.json`, `screen.svg`, `logs.txt`, and `metadata.json`. Environment variable values are redacted in metadata. `transcript.ansi` and `recording.termctrl` are opt-in because terminal streams and typed input may contain secrets. Wrap ordinary snapshot assertions when evidence should be saved on any thrown assertion:\n\n```ts\nawait session.withArtifactsOnFailure(\"settings-snapshot\", async () => {\n  await expect(session.screen.text()).resolves.toMatchSnapshot()\n})\n```\n\nEnable a recording with `record: true` or `record: \"on-failure\"`; a test may explicitly save it before disposing the session:\n\n```ts\nawait session.resize({ cols: 120, rows: 40 })\nawait session.saveRecording(\"artifacts\u002Fnavigation.termctrl\")\n```\n\n## Agent Skill\n\nThis repository publishes an agent skill that teaches coding agents to inspect and drive terminal applications through `termctrl` instead of guessing at interactive state. Install it from the repository with the Skills CLI:\n\n```bash\nnpx skills add kitlangton\u002Fterminal-control --skill terminal-control\n```\n\nThe skill covers one-off visible reads, named live-session workflows, OpenTUI startup, explicit evidence capture, recording handling, and the sensitivity of terminal transcripts and input.\n\n### npm Release\n\nThe npm workspace publishes `@kitlangton\u002Fterminal-control` with fixed-version platform packages: `@kitlangton\u002Fterminal-control-darwin-arm64`, `@kitlangton\u002Fterminal-control-darwin-x64`, `@kitlangton\u002Fterminal-control-linux-arm64-gnu`, and `@kitlangton\u002Fterminal-control-linux-x64-gnu`. The client is compiled to ESM JavaScript with declarations; each native package receives the release Rust executable during the `npm release` workflow.\n\nFor subsequent user-facing npm changes, create a Changeset with `bunx changeset`, commit the generated release metadata, and apply version changes before running the workflow. Run the workflow with `publish: false` to assemble packages only, or `publish: true` to publish assembled tarballs after its clean Bun and Node\u002FVitest consumer validation passes.\n\nThe publish job is prepared for npm trusted publishing through GitHub Actions OIDC. In npm package settings, configure `kitlangton\u002Fterminal-control` and workflow `npm-release.yml` as the trusted publisher for the client and each platform package before using `publish: true`.\n\n## Notes\n\n- Persistent sessions use owner-only local Unix sockets and are supported on macOS and Linux.\n- `--host opentui` answers startup probes needed by current OpenTUI applications; Kitty graphics are reported unavailable because the current renderer does not decode image payloads.\n- The current renderer uses a pure-Rust `vt100` terminal backend and exports PNG, SVG, JSON, text, and raw ANSI stream artifacts.\n- Run `termctrl \u003Ccommand> --help` for dimensions, timing, color, rendering, and output options.\n","Terminal Control 是一个用于控制、检查、测试和捕获真实终端应用程序的工具，适用于代理和服务端界面审查。它使用 Rust 语言开发，支持多种格式输出如 JSON、SVG 和 PNG 等，并可通过命令行与终端应用进行交互。核心功能包括启动会话、发送输入、调整窗口大小以及保存屏幕快照等。该项目特别适合需要对终端应用进行自动化测试或记录的场景，例如开发者希望在 CI\u002FCD 流程中集成 TUI 应用的测试步骤，或是安全审计人员需要收集终端活动证据时。MIT 许可证下开源，安装简单且文档详尽，要求 Rust 1.93 或更高版本。",2,"2026-06-11 04:09:19","CREATED_QUERY"]