Agent skill

dev-terminal

Terminal/PTY automation with persistent sessions. Use to run and interact with TUI applications, debug terminal apps, automate CLI workflows, or any terminal interaction. Trigger phrases include "run the TUI", "start the app", "debug the terminal", "interact with", "send keys", "what's on screen".

Stars 1
Forks 0

Install this agent skill to your Project

npx add-skill https://github.com/parkerhancock/dev-terminal/tree/main/skills/dev-terminal

SKILL.md

Dev Terminal Skill

Terminal automation that maintains PTY sessions across script executions. Run TUI applications, send keystrokes, capture screen output, and debug terminal apps - all with persistent state.

Setup

Start the server in a background terminal:

bash
cd dev-terminal && npm install && ./server.sh &

Wait for the Ready message before running scripts.

Headed Mode (Optional)

For visual debugging, start with browser UI:

bash
cd dev-terminal && ./server.sh --headed &

This opens a browser window showing all terminals in real-time. Useful for:

  • Watching AI actions as they happen
  • Manual intervention if needed
  • Debugging TUI interactions

Writing Scripts

Run scripts inline using heredocs from the dev-terminal/ directory:

bash
cd dev-terminal && npx tsx <<'EOF'
import { connect, sleep } from "./src/client.js";

const client = await connect();

// Create or get a named terminal
const term = await client.terminal("my-app", {
  command: "python",
  args: ["-m", "my_module"],
  cols: 120,
  rows: 40,
});

// Wait for app to start
await sleep(1000);

// Get screen snapshot
const snap = await term.snapshot();
console.log("=== SCREEN ===");
console.log(snap.text);

client.disconnect();
EOF

Shell Defaults

By default, terminals use:

  • Shell: User's default shell ($SHELL env var, e.g., zsh on macOS, bash on Linux)
  • Mode: Login shell (-l flag) - loads full profile (~/.zprofile, ~/.bash_profile)

This means your shell aliases, PATH, and environment are available.

Override the shell:

typescript
// Use a specific shell
const term = await client.terminal("my-term", {
  command: "bash",
  args: ["-l"], // keep as login shell
});

// Non-login shell (only loads ~/.bashrc, not ~/.bash_profile)
const term = await client.terminal("my-term", {
  command: "bash",
  args: [], // no -l flag
});

// Run a command directly (not a shell)
const term = await client.terminal("my-term", {
  command: "python",
  args: ["-m", "my_app"],
});

SSH Remote Terminals

Connect to remote servers via SSH. The API is identical to local terminals.

typescript
import { connect } from "./src/client.js";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";

const client = await connect();

// SSH with private key
const term = await client.terminal("remote-server", {
  ssh: {
    host: "192.168.1.100",
    username: "deploy",
    privateKey: fs.readFileSync(path.join(os.homedir(), ".ssh/id_rsa"), "utf8"),
  },
});

// SSH with password
const term = await client.terminal("remote-server", {
  ssh: {
    host: "example.com",
    username: "admin",
    password: "secret",
  },
});

// SSH with agent (uses SSH_AUTH_SOCK)
const term = await client.terminal("remote-server", {
  ssh: {
    host: "example.com",
    username: "admin",
    agent: process.env.SSH_AUTH_SOCK,
  },
});

// SSH with custom port and encrypted key
const term = await client.terminal("remote-server", {
  ssh: {
    host: "example.com",
    port: 2222,
    username: "admin",
    privateKey: fs.readFileSync("/path/to/key", "utf8"),
    passphrase: "key-passphrase",
  },
});

SSH Options:

Option Type Description
host string Remote hostname or IP (required)
port number SSH port (default: 22)
username string SSH username (required)
password string Password authentication
privateKey string Private key content (not path)
passphrase string Passphrase for encrypted keys
agent string Path to SSH agent socket

Notes:

  • SSH terminals don't have a pid (it's undefined)
  • All Terminal methods work the same (write, key, snapshot, etc.)
  • The remote shell is determined by the server, not local settings

Key Principles

  1. Small scripts: Each script does ONE thing (start app, check screen, send key)
  2. Observe output: Always check snapshot() to see current state
  3. Descriptive names: Use "claude-monitor", "vim-edit", not "term1"
  4. Terminals persist: disconnect() leaves terminals running for next script

Workflow Loop

  1. Write a script to perform one action
  2. Run it and observe the screen output
  3. Evaluate - what's displayed? Did it work?
  4. Decide - send more input or task complete?
  5. Repeat until done

Client API

typescript
const client = await connect();

// Get or create named terminal (uses default shell as login shell)
const term = await client.terminal("name");

// With options
const term = await client.terminal("name", {
  command: "python", // override shell/command
  args: ["-m", "my_app"], // override args (clears default -l flag)
  cols: 120,
  rows: 40,
  cwd: "/path/to/dir",
  env: { MY_VAR: "value" },
});

// List all terminal names
const names = await client.list();

// Close/kill a terminal
await client.close("name");

// Disconnect (terminals persist)
client.disconnect();

Terminal Methods

typescript
// Send raw input
await term.write("hello");

// Send special keys
await term.key("enter");
await term.key("up");
await term.key("ctrl+c");

// Send a line (adds Enter)
await term.writeLine("ls -la");

// Get screen state
const snap = await term.snapshot();
console.log(snap.text); // Plain text (no ANSI codes)
console.log(snap.lines); // Array of lines
console.log(snap.alive); // Process still running?

// Get SVG rendering (for visual analysis)
const svgSnap = await term.snapshot({ format: "svg" });
console.log(svgSnap.svg); // SVG string

// Resize
await term.resize(80, 24);

// Clear buffer
await term.clear();

// Wait for text to appear
const found = await term.waitForText("Ready", { timeout: 5000 });

// Wait for process to exit
const exitCode = await term.waitForExit({ timeout: 10000 });

Special Keys

Use term.key() with these names:

Category Keys
Arrows up, down, left, right
Control enter, tab, escape, backspace, delete
Ctrl+X ctrl+c, ctrl+d, ctrl+z, ctrl+l, ctrl+a, ctrl+e, ctrl+k, ctrl+u, ctrl+w, ctrl+r
Function f1 - f12
Navigation home, end, pageup, pagedown, insert

Example: Debug a TUI App

bash
cd dev-terminal && npx tsx <<'EOF'
import { connect, sleep } from "./src/client.js";

const client = await connect();

// Start the TUI
const term = await client.terminal("claude-monitor", {
  command: "../.venv/bin/python",
  args: ["-m", "claude_monitor"],
  cols: 120,
  rows: 40,
  cwd: "..",
});

// Wait for it to render
await sleep(2000);

// Capture screen
const snap = await term.snapshot();
console.log("=== SCREEN OUTPUT ===");
console.log(snap.text);
console.log("=== ALIVE:", snap.alive, "===");

client.disconnect();
EOF

Example: Interactive Session

bash
# Script 1: Start app
cd dev-terminal && npx tsx <<'EOF'
import { connect, sleep } from "./src/client.js";
const client = await connect();
const term = await client.terminal("my-tui", {
  command: "htop",
});
await sleep(1000);
const snap = await term.snapshot();
console.log(snap.text);
client.disconnect();
EOF

# Script 2: Send keys (terminal persists!)
cd dev-terminal && npx tsx <<'EOF'
import { connect, sleep } from "./src/client.js";
const client = await connect();
const term = await client.terminal("my-tui"); // Reconnect to existing
await term.key("down");
await term.key("down");
await sleep(500);
const snap = await term.snapshot();
console.log(snap.text);
client.disconnect();
EOF

# Script 3: Quit
cd dev-terminal && npx tsx <<'EOF'
import { connect } from "./src/client.js";
const client = await connect();
const term = await client.terminal("my-tui");
await term.write("q");
client.disconnect();
EOF

Error Recovery

If something goes wrong, check the terminal state:

bash
cd dev-terminal && npx tsx <<'EOF'
import { connect } from "./src/client.js";

const client = await connect();

// List all terminals
const terminals = await client.list();
console.log("Active terminals:", terminals);

// Check specific terminal
if (terminals.includes("my-app")) {
  const term = await client.terminal("my-app");
  const snap = await term.snapshot();
  console.log("Alive:", snap.alive);
  console.log("Exit code:", snap.exitCode);
  console.log("Last output:", snap.lines.slice(-20).join("\n"));
}

client.disconnect();
EOF

Tips

  • TUI apps need time: Use sleep() after starting to let them render
  • Check alive status: TUI might crash - check snap.alive
  • Clear for fresh state: Use term.clear() before important snapshots
  • Large output: snap.lines gives the last ~120 lines (3x terminal height)

Expand your agent's capabilities with these related and highly-rated skills.

parkerhancock/browser-canvas

canvas

5 1
Explore
parkerhancock/browser-canvas

canvas

Render interactive UIs in a browser window using file operations. Supports two modes: React (App.jsx) for rapid prototyping with pre-built components, and Vanilla (index.html) for standards-based, portable artifacts. Use when users ask to: show forms, render charts/graphs, create dashboards, display data tables, build visual interfaces, or show any UI component. Trigger phrases: "show me", "render", "display", "create a form/chart/table/dashboard".

5 1
Explore
parkerhancock/ip_tools

ip_research

IP data research tools for patents, trademarks, and applications. Use when: - Looking up patents by number (US, EP, WO, JP, etc.) - Searching patent databases by keyword, assignee, inventor, or classification - Getting patent family, citation, or legal status information - Checking USPTO application status, file wrapper, or PTAB proceedings - Downloading bulk USPTO data products - Finding patent assignments or ownership history - Fetching USPTO publication full-text data

8 1
Explore
mattpocock/skills

edit-article

Edit and improve articles by restructuring sections, improving clarity, and tightening prose. Use when user wants to edit, revise, or improve an article draft.

111,310 9,758
Explore
mattpocock/skills

obsidian-vault

Search, create, and manage notes in the Obsidian vault with wikilinks and index notes. Use when user wants to find, create, or organize notes in Obsidian.

111,310 9,758
Explore
mattpocock/skills

handoff

Compact the current conversation into a handoff document for another agent to pick up.

111,310 9,758
Explore

Didn't find tool you were looking for?

Be as detailed as possible for better results