Skip to content
Back to skills

Terminal Control

BSecurity

Drive a real macOS / Linux / Windows terminal from this conversation — spawn shells, split panes, type commands, read scrollback. Activates whenever the user wants you to "run X", "open a shell and ...", "split the pane", or "look at what's on the terminal", and the Unterm MCP server is available.

  • 14 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 23, 2026
ai-agentsgoshellgitapi

Works with

  • terminal
  • cli
  • api
  • mcp

Security analysis

B85/100
  • highPerforms destructive filesystem operations

Pro shows the line behind each finding and how to fix it

Scanned September 23, 2026

npx -y skills add zhitongblog/unterm --skill terminal-control --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Terminal Control?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Terminal Control
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/zhitongblog-terminal-control/badge)](https://www.skillsdirectory.com/skills/zhitongblog-terminal-control)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: terminal-control
description: Drive a real macOS / Linux / Windows terminal from this conversation — spawn shells, split panes, type commands, read scrollback. Activates whenever the user wants you to "run X", "open a shell and ...", "split the pane", or "look at what's on the terminal", and the Unterm MCP server is available.
license: MIT
---

# terminal-control

You have access to a real, running terminal via the Unterm MCP server.
Treat it like a remote pair-programming buddy with hands: you can open
panes, type into them, read what came back, and chain commands.

## Before you call anything

Run `meta.surface` once at the start of any session that needs the terminal.
It returns the full live API (every MCP method with its param schema, every
`unterm-cli` subcommand, every keybinding) — your single source of truth.

If `instance.list` returns more than one entry, the user has multiple
Unterm windows open. Pick the right one by `cwd` or `title`, then pass
`{ "instance": "<id>" }` on subsequent calls (or connect to that instance's
port directly — see `~/.unterm/instances/<id>.json` for host + token).

## The 8 methods you'll reach for

| Method | When |
| --- | --- |
| `session.list` | "what panes are open?" |
| `session.create` | "open a new pane in /foo" — pass `{cwd, command?}` |
| `session.split` | "split the current pane right/down with X running" |
| `session.focus` | always call this after `session.create`/`split` so the user sees the new pane |
| `session.input` | type into a pane char-by-char (preserves shell history); pass the literal newline `"\n"` to submit |
| `exec.run_wait` | run a command and block for `{exit_code, stdout, stderr}` — strongly prefer over `session.input` when you don't need the user to see the prompt |
| `screen.scrollback_text` | dump the entire pane history + viewport as one string — feed yourself for context instead of begging for a screenshot |
| `capture.window` | take a real screenshot (returns a file path) — use sparingly; `screen.scrollback_text` is cheaper and parses natively |

## Don't do this

- **Don't run destructive commands without confirming**. `rm -rf`, force-pushes,
  `DROP TABLE`, deleting branches, killing PIDs — ask first, then run via
  `exec.run_wait` so the exit code is unambiguous.
- **Don't type into an existing pane the user is actively using**. Use
  `session.create` or `session.split` to make your own.
- **Don't loop on `screen.scrollback_text`** — call once, parse, decide. The
  scrollback is bounded; polling adds no signal.
- **Don't bypass the audit banner**. First write to a fresh pane triggers a
  user prompt; that's by design — don't try to suppress it.

## Recipe — start a coding session in a new pane

```jsonc
// 1. open a pane at the project root
{ "method": "session.create",
  "params": { "cwd": "/Users/me/repo" } }
// → { "id": 7, "pid": 41208 }

// 2. focus it so the user sees what you're doing
{ "method": "session.focus", "params": { "id": 7 } }

// 3. run the build; block on exit
{ "method": "exec.run_wait",
  "params": { "id": 7, "command": "cargo test" } }
// → { "exit_code": 0, "stdout": "...", "stderr": "...", "duration_ms": 4321 }

// 4. dump full scrollback into your context if you need to reason about output
{ "method": "screen.scrollback_text", "params": { "id": 7 } }
```

## Recipe — side-by-side reviewer

```jsonc
// split the active pane right; new pane gets a fresh shell
{ "method": "session.split",
  "params": { "direction": "right", "cwd": "/Users/me/repo" } }
// → { "id": 9, ... }
{ "method": "session.focus", "params": { "id": 9 } }
{ "method": "session.input",
  "params": { "id": 9, "input": "git diff main\n" } }
```

## Recipe — handing a pane to the user

If the user needs to type their own commands (e.g. login, paste a secret),
**stop typing** and tell them which pane id to use. Don't poll
`screen.scrollback_text` waiting for them — you have no idea when they're
done. Ask them to ping you when ready.

## When the user asks "what's running on my terminal"

Don't guess. Call `session.list` first to enumerate panes, then
`screen.scrollback_text` on each pane of interest. Summarise concisely:
which panes are alive, what command each was last running, any obvious
errors at the tail.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…