From shelli
Enables persistent interactive shell sessions (REPLs, SSH, database CLIs) that survive across CLI invocations. Use for stateful commands, multi-step workflows, and long-running processes.
How this skill is triggered — by the user, by Claude, or both
Slash command
/shelli:shelliThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
shelli enables persistent interactive shell sessions (REPLs, SSH, database CLIs, etc.) that survive across CLI invocations. This skill provides comprehensive knowledge for using shelli effectively.
shelli enables persistent interactive shell sessions (REPLs, SSH, database CLIs, etc.) that survive across CLI invocations. This skill provides comprehensive knowledge for using shelli effectively.
If MCP shelli tools are available (check for shelli/create, shelli/exec, etc.), prefer using them over Bash commands. MCP tools provide structured responses and better error handling.
MCP tools map directly to CLI commands:
shelli/create → shelli createshelli/exec → shelli execshelli/send → shelli sendshelli/read → shelli readshelli/search → shelli searchshelli/list → shelli listshelli/info → shelli infoshelli/clear → shelli clearshelli/resize → shelli resizeshelli/stop → shelli stopshelli/kill → shelli killIf MCP tools are not available, use the Bash commands documented below.
Use shelli instead of regular Bash when you need:
>>>, Node >, etc.)Do NOT use shelli for:
ls, cat file.txt, git status)--tui flag and --follow mode (see TUI Applications section in Limitations)shelli create <name> [flags]
Flags:
--cmd "command": Command to run (default: user's shell)--env KEY=VALUE: Set environment variable (repeatable)--cwd /path: Set working directory--cols N: Terminal columns (default: 80)--rows N: Terminal rows (default: 24)--tui: Enable TUI mode (auto-truncate buffer on frame boundaries)--json: Output session info as JSONExamples:
shelli create myshell # default shell
shelli create pyrepl --cmd "python3" # Python REPL
shelli create node --cmd "node" # Node.js REPL
shelli create db --cmd "psql -d mydb" # PostgreSQL
shelli create server --cmd "ssh user@host" # SSH session
shelli create redis --cmd "redis-cli" # Redis CLI
shelli create dev --env "DEBUG=1" --cwd /app # with env and working dir
shelli create wide --cols 200 --rows 50 # large terminal
shelli create vim --cmd "vim" --tui # TUI mode for editors
shelli exec <name> <input> [flags]
Sends a command as literal text with newline appended, waits for output to settle or pattern match.
Input is sent exactly as provided - escape sequences like \n are NOT interpreted by shelli. They're passed to the shell as-is (the shell may interpret them, e.g., echo -e).
For precise control over escape sequences or TUI apps, use send instead.
Flags:
--settle N: Wait for N ms of silence (default: 500)--wait "pattern": Wait for regex pattern match (mutually exclusive with --settle)--timeout N: Max wait time in seconds (default: 10)--strip-ansi: Remove terminal escape codes from output--json: Output as JSON with input, output, position fieldsExamples:
# Basic execution (waits 500ms for output to settle)
shelli exec pyrepl "print('hello')"
# Longer settle for slow commands
shelli exec db "SELECT * FROM large_table;" --settle 2000
# Wait for specific prompt pattern
shelli exec myshell "ls" --wait '\$\s*$'
shelli exec pyrepl "x = 1" --wait '>>>'
# Clean output for parsing
shelli exec session "command" --strip-ansi --json
# Long-running with timeout
shelli exec session "slow_command" --settle 5000 --timeout 120
# Escape sequences passed to shell (shell interprets them)
shelli exec myshell "echo -e 'hello\nworld'"
shelli send <name> <input> [input...]
Low-level command for precise control:
Use send for:
Examples:
shelli send myshell "ls -la\n" # command with explicit newline
shelli send pyrepl "print('hi')\n" # to Python with newline
shelli send myshell "\x03" # Ctrl+C (interrupt)
shelli send myshell "\x04" # Ctrl+D (EOF)
shelli send myshell "y" # 'y' without newline
shelli send tui "hello" "\r" # TUI: type "hello", then Enter (separate writes)
shelli read <name> [flags]
Instant modes (non-blocking):
--all: All output from session startStreaming mode (for TUIs):
--follow / -f: Continuous output like tail -f--follow-ms N: Poll interval in ms (default: 100)Snapshot mode (TUI only):
--snapshot: Force full redraw via resize, wait for settle, read clean frame
--tui on create. Incompatible with --follow, --all, --wait.--settle (overrides default 300ms), --strip-ansi, --json, --head, --tail, --timeout.Blocking modes:
--wait "pattern": Wait for regex pattern match--settle N: Wait for N ms of silenceOther flags:
--timeout N: Max wait time (default: 10s)--strip-ansi: Remove ANSI escape codes--json: Output as JSON--cursor "name": Named cursor for per-consumer read tracking. Each cursor maintains its own position.Examples:
shelli read myshell # new output, instant
shelli read myshell --all # all output, instant
shelli read myshell --follow # stream continuously (Ctrl+C to stop)
shelli read pyrepl --wait ">>>" # wait for Python prompt
shelli read myshell --settle 300 # wait for 300ms silence
shelli read myshell --strip-ansi # clean output
shelli read tui-app --snapshot --strip-ansi # clean TUI frame
shelli read tui-app --snapshot --tail 10 # last 10 lines of TUI
shelli list [--json]
Shows name, PID, command, created time, running status.
shelli info <name> [--json]
Shows detailed session information: name, state, pid, command, created_at, stopped_at (if stopped), uptime, buffer size, read position, terminal dimensions.
shelli clear <name> [--json]
Truncates the output buffer and resets the read position. The session continues running.
shelli resize <name> [--cols N] [--rows N] [--json]
At least one of --cols or --rows must be specified. Omitted dimensions keep their current value.
Examples:
shelli resize myshell --cols 120 --rows 40 # set both
shelli resize myshell --cols 200 # change only width
shelli stop <name> [--json]
Terminates the process but keeps output readable. Session stays in list with state "stopped".
shelli kill <name> [--json]
Terminates the session and cleans up all resources (output and metadata).
| Sequence | Character | Description |
|---|---|---|
\x00-\xFF | Any byte | Hex byte value |
\n | LF | Newline |
\r | CR | Carriage return |
\t | Tab | Horizontal tab |
\e | ESC | Escape (ASCII 27) |
\\ | \ | Literal backslash |
\0 | NUL | Null byte |
| Sequence | Key | Effect |
|---|---|---|
\x03 | Ctrl+C | Interrupt (SIGINT) - stop running command |
\x04 | Ctrl+D | EOF - close stdin, often exits REPL |
\x1a | Ctrl+Z | Suspend (SIGTSTP) |
\x1c | Ctrl+\ | Quit (SIGQUIT) |
\x0c | Ctrl+L | Clear screen |
\x09 | Tab | Tab character (completion) |
\x7f | Backspace | Delete previous character |
\x15 | Ctrl+U | Kill line (clear current input) |
\x17 | Ctrl+W | Kill word (delete previous word) |
When using MCP tools via mcp-cli, input goes through two escaping layers:
In JSON strings, these characters MUST be escaped:
" → \"\ → \\ (CRITICAL: bare backslashes cause "Invalid escape character" errors)\n\tFor complex input, use heredoc with stdin mode to avoid the shell escaping layer:
mcp-cli call shelli/send - <<'EOF'
{
"name": "session",
"input": "print(\"Hello\\nWorld\")"
}
EOF
Note: Only JSON escaping needed inside heredoc - no shell escaping layer.
\!, \$, \@ are NOT valid JSON escapes → use \\!, \\$, \\@ or just !, $, @"say \"hi\""Simple command (inline JSON works fine):
mcp-cli call shelli/send '{"name": "sh", "input": "ls -la"}'
Python with quotes (use heredoc):
mcp-cli call shelli/send - <<'EOF'
{"name": "py", "input": "print(\"hello\")"}
EOF
SQL with quotes (use heredoc):
mcp-cli call shelli/send - <<'EOF'
{"name": "db", "input": "SELECT * FROM users WHERE name = 'O''Brien'"}
EOF
When escaping becomes unmanageable (deeply nested quotes, binary data), use input_base64:
# Original: print("Hello\nWorld")
# Base64: cHJpbnQoIkhlbGxvXG5Xb3JsZCIp
mcp-cli call shelli/send '{"name": "py", "input_base64": "cHJpbnQoIkhlbGxvXG5Xb3JsZCIp"}'
Trade-off: 33% larger payload, but eliminates all escaping complexity.
Note: input and input_base64 are mutually exclusive - use one or the other.
inputs Array for SequencesWhen sending content followed by Enter/Return or any multi-step input, always use the inputs array in a single MCP call. This is more efficient than multiple separate calls.
inputs// CORRECT - single call, two PTY writes
{"name": "session", "inputs": ["Hello Zephyr!", "\r"]}
// WRONG - wastes tokens on two calls
{"name": "session", "input": "Hello Zephyr!"} // first call
{"name": "session", "input": "\r"} // second call
| Scenario | MCP JSON |
|---|---|
| Message + Enter | {"inputs": ["your message", "\r"]} |
| Command + confirmation | {"inputs": ["rm -i file.txt", "\r", "y", "\r"]} |
| Multi-line input | {"inputs": ["line1", "\r", "line2", "\r"]} |
| Password entry | {"inputs": ["password123", "\r"]} |
The CLI already supports this pattern with multiple arguments:
shelli send session "message" "\r"
This sends "message" and "\r" as separate writes, matching the MCP inputs behavior.
Default settle time is 500ms. Adjust based on expected response time:
| Scenario | Recommended Settle |
|---|---|
| Simple REPL commands | 300-500ms |
| File operations | 500-1000ms |
| Network operations | 1000-2000ms |
| Database queries | 1000-3000ms |
| Remote SSH commands | 2000-5000ms |
Use --wait instead of --settle when you know the expected output pattern:
# Shell prompts
--wait '\$\s*$' # bash prompt ending with $
--wait '#\s*$' # root prompt ending with #
--wait '>\s*$' # generic prompt ending with >
# REPL prompts
--wait '>>>\s*$' # Python
--wait '>\s*$' # Node.js
--wait 'irb.*>\s*$' # Ruby IRB
--wait 'iex.*>\s*$' # Elixir
# Database prompts
--wait '=>\s*$' # psql
--wait 'mysql>\s*$' # MySQL
--wait 'sqlite>\s*$' # SQLite
# Custom patterns
--wait 'Password:' # Password prompt
--wait '\[y/n\]' # Yes/no prompt
--wait 'Enter.*:' # Generic input prompt
Use descriptive, collision-free names:
# Good
shelli create python-data-analysis --cmd "python3"
shelli create ssh-prod-server --cmd "ssh [email protected]"
shelli create postgres-mydb --cmd "psql -d mydb"
# Avoid
shelli create s1 --cmd "python3" # Not descriptive
shelli create test --cmd "ssh" # Too generic
shelli list before creating duplicates\x03) to interrupt, Ctrl+D (\x04) to exit# Create and wait for prompt
shelli create py --cmd "python3"
shelli read py --wait '>>>'
# Execute commands
shelli exec py "import pandas as pd" --wait '>>>'
shelli exec py "df = pd.read_csv('data.csv')" --wait '>>>'
shelli exec py "df.describe()" --strip-ansi
# Clean up
shelli kill py
# Create SSH connection
shelli create remote --cmd "ssh [email protected]"
shelli read remote --wait '\$\s*$' --timeout 30 # wait for login
# Run commands
shelli exec remote "cd /var/log" --wait '\$'
shelli exec remote "tail -n 50 app.log" --strip-ansi
shelli exec remote "grep ERROR app.log | wc -l" --strip-ansi
# Clean up
shelli exec remote "exit"
shelli kill remote
# Connect to PostgreSQL
shelli create db --cmd "psql -h localhost -U myuser -d mydb"
shelli read db --wait '=>\s*$' --timeout 10
# Run queries
shelli exec db "SELECT count(*) FROM users;" --wait '=>' --strip-ansi
shelli exec db "\\dt" --wait '=>' --strip-ansi # list tables
# Transaction workflow
shelli exec db "BEGIN;" --wait '=>'
shelli exec db "UPDATE users SET active = true WHERE id = 1;" --wait '=>'
shelli exec db "COMMIT;" --wait '=>'
# Clean up
shelli exec db "\\q"
shelli kill db
# When a command asks for confirmation
shelli send session "rm -i file.txt"
shelli read session --wait '\[y/n\]'
shelli send session "y" # Answer with newline
shelli read session --settle 500
# Password prompt (be careful with credentials)
shelli send session "sudo command"
shelli read session --wait 'Password:'
shelli send session "password" # Sends with newline
shelli read session --settle 1000
If commands timeout:
--timeout valueshelli list)shelli read <name> --all)shelli send <name> "\x03" --raw)# Interrupt current command
shelli send session "\x03" --raw
shelli read session --settle 500
# If still stuck, send EOF
shelli send session "\x04" --raw
# Force kill as last resort
shelli kill session
# Check if session exists
shelli list
# Recreate if needed
shelli create session --cmd "command"
~/.shelli/shelli.sock)--max-output)--cursor flag (or MCP cursor param) allows multiple consumers to independently track read positions on the same sessionshelli supports TUI applications using --follow mode for continuous streaming, --tui mode for reduced storage, and --snapshot for clean frame capture:
shelli create mon --cmd "btop" --tui
shelli read mon --follow # streams output, renders TUI
shelli read mon --snapshot --strip-ansi # force redraw, get clean frame
shelli resize mon --cols 150 --rows 50 # resize works too
TUI Mode (--tui flag):
When enabled, shelli uses multiple detection strategies to identify frame boundaries:
| Strategy | Trigger | Coverage |
|---|---|---|
| Screen clear | ESC[2J, ESC[?1049h, ESC c | vim, less, nano |
| Sync mode | ESC[?2026h (begin) | Claude Code, modern terminals |
| Cursor home | ESC[1;1H (with reset) | k9s, btm, htop |
| Size cap | Buffer > 100KB after frame | Fallback after frame detection |
This reduces storage from ~50MB to ~2KB for typical TUI sessions.
Use --tui for apps that frequently redraw the screen (vim, htop, btop, k9s). Do not use for apps where you need to preserve scrollback history.
What works well:
btop, htop, k9sLimited support:
vim, nano) - display works but interaction is impracticalSome TUI apps use line-based input/output and work with shelli, but may need special handling:
Example: OpenClaw TUI
OpenClaw TUI (openclaw tui) is a chat interface for AI agents. It works with shelli by sending message and Enter as separate writes:
# Step 1: Create SSH session and launch TUI
shelli create openclaw --cmd "ssh user@host"
shelli read openclaw --settle 3000
shelli send openclaw "openclaw tui\n"
shelli read openclaw --settle 3000
# Step 2: Send message then Enter as separate writes
shelli send openclaw "Hello, this is my message" "\r"
# Step 3: Wait for response
sleep 8 # Allow time for AI to respond
shelli read openclaw --strip-ansi
Why separate writes?
TUI apps often buffer input and only submit when Enter is pressed as a separate keypress event. By using multiple arguments:
"\r" sends carriage return as a separate write, triggering submitPattern for TUIs with input buffers:
# Message then Enter as separate writes
shelli send session "your message" "\r"
# If \r doesn't work, try \n
shelli send session "your message" "\n"
Debugging TUI issues:
shelli read session --all --strip-ansishelli send session "\x03"Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub schovi/shelli --plugin shelli