Loading...
Loading...
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".
npx skill4agent add parkerhancock/dev-terminal dev-terminalcd dev-terminal && npm install && ./server.sh &Readycd dev-terminal && ./server.sh --headed &dev-terminal/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-l~/.zprofile~/.bash_profile// 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"],
});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",
},
});| Option | Type | Description |
|---|---|---|
| string | Remote hostname or IP (required) |
| number | SSH port (default: 22) |
| string | SSH username (required) |
| string | Password authentication |
| string | Private key content (not path) |
| string | Passphrase for encrypted keys |
| string | Path to SSH agent socket |
pidundefinedsnapshot()"claude-monitor""vim-edit""term1"disconnect()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();// 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 });term.key()| Category | Keys |
|---|---|
| Arrows | |
| Control | |
| Ctrl+X | |
| Function | |
| Navigation | |
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# 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();
EOFcd 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();
EOFsleep()snap.aliveterm.clear()snap.lines