Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Required reading whenever any shell_* tool is available. Teaches the foreground/background dichotomy (terminal_exec auto-promotes past 30s, returns a job_id you poll with terminal_job_logs), the standard envelope shape (exit_code, stdout, stdout_truncated_bytes, output_handle, semantic_status, warning, auto_backgrounded, job_id), output handle pagination via terminal_output_get, when to read semantic_status instead of raw exit_code (grep/rg/find/diff/test exit 1 is NOT an error), the destructive
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 75% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 244% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 60% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 137% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 101% | 0% |
These tools give you a real terminal: foreground exec with smart envelopes, background jobs with offset-based log streaming, persistent PTY shells, and filesystem search. Bash-only on POSIX.
The terminal is your file system: reading, writing, editing, and searching files all go through terminal-tools. Reach for a higher-level tool only where it clearly fits (browser, web search). Terminal tools default their cwd/path to your session workdir when you omit it — relative paths Just Work; pass an absolute path to operate elsewhere.
terminal_exec("cat PATH") (page large output with terminal_output_get)terminal_exec("sed -i ...") / awk, or rewrite the whole file with a heredocterminal_exec("cat > PATH <<'EOF' ... EOF")terminal_rg (content / regex grep) and terminal_glob (find files by name)gcu-tools.browser_* for rendered pages — NOT terminal_exec("curl ...")hive_tools.web_search — NOT scrapingEvery spawn-style call (terminal_exec, the auto-promoted job state) returns this shape:
jsonc{ "exit_code": 0, // null when auto-backgrounded or pre-spawn error "stdout": "...", // decoded, truncated to max_output_kb (default 256 KB) "stderr": "...", "stdout_truncated_bytes": 0, // > 0 means more is in output_handle "stderr_truncated_bytes": 0, "runtime_ms": 42, "pid": 12345, "output_handle": null, // "out_<hex>" when truncated — paginate with terminal_output_get "timed_out": false, "semantic_status": "ok", // "ok" | "signal" | "error" — read THIS, not just exit_code "semantic_message": null, // e.g. "No matches found" for grep exit 1 "warning": null, // e.g. "may force-remove files" for rm -rf "auto_backgrounded": false, "job_id": null, // set when auto_backgrounded=true "shell_kind": "bash" // interpreter that ran it: "bash" | "powershell" | "cmd" | "direct" }
terminal_exec runs commands in the foreground until the auto-background budget (default 30s) elapses. Past that point, the process is silently transferred to a background job and the call returns immediately with:
jsonc{ "auto_backgrounded": true, "exit_code": null, "job_id": "job_<hex>", ... }
When you see auto_backgrounded: true, pivot to polling. The job is still running:
terminal_job_logs(job_id, since_offset=0, wait_until_exit=true, wait_timeout_sec=60)
→ blocks server-side until the job exits or the timeout, returns logs + statusYou're not failing — you're freed up to do other work while the long task runs.
To force pure-foreground (kill on timeout_sec), pass auto_background_after_sec=0. Use this when you genuinely don't want a background job (small commands where promotion would surprise you).
semantic_status, not raw exit_codeSeveral common commands use exit 1 for legitimate non-error states:
| Command | exit 0 | exit 1 | |---|---|---| | grep / rg | matches found | no matches (not an error) | | find | success | some dirs unreadable (informational) | | diff | identical | files differ (informational) | | test / [ | true | false (informational) |
For these, semantic_status will be "ok" even when exit_code == 1, with semantic_message describing why ("No matches found"). For everything else, semantic_status defaults to "ok" on 0 and "error" on nonzero.
Rule: always check semantic_status first. Only fall back to exit_code when you need the exact number (e.g. distinguishing make errors).
The envelope's warning field is set when the command matches a known destructive pattern (rm -rf, git push --force, git reset --hard, DROP TABLE, kubectl delete, terraform destroy, etc.). The command still ran — the warning is informational. Use it as a "did I mean to do that?" prompt before trusting subsequent steps that depend on the side effect.
If a warning appears unexpectedly, stop and verify: was the destructive action intended, or did a path/glob slip in?
When stdout_truncated_bytes > 0 or stderr_truncated_bytes > 0, the inline output was capped at max_output_kb (default 256 KB). The full bytes are stashed under output_handle for 5 minutes. Paginate with:
terminal_output_get(output_handle, since_offset=0, max_kb=64)
→ { data, offset, next_offset, eof, expired }Track next_offset across calls. If expired: true, re-run the command (the handle's TTL has lapsed).
The store has a 64 MB cap with LRU eviction. For huge outputs, prefer terminal_job_start + terminal_job_logs polling (4 MB ring buffer per stream, infinite total throughput).
On POSIX, terminal_exec and terminal_pty_open always invoke /bin/bash (on Windows see the section below). The user's $SHELL is ignored. Explicit shell="/bin/zsh" is rejected with a clear error. This is a deliberate security stance, not aesthetic — zsh has command/builtin classes (zmodload, =cmd expansion, zpty, ztcp, zf_*) that bypass bash-shaped checks. The terminal-tools-pty-sessions skill explains the implications for PTY sessions specifically.
ZDOTDIR and ZSH_* env vars are stripped before exec to prevent zsh dotfiles leaking in. Bash dotfiles still apply when invoked interactively (e.g. PTY sessions use bash --norc --noprofile to keep things predictable).
shell_kind before assuming bashOn Windows the shell is resolved in priority order: Git Bash → PowerShell → cmd. Which one ran your command is reported in the envelope's shell_kind field. Bash is only available if Git for Windows is installed; otherwise you land in PowerShell (or cmd as the floor). Read shell_kind and adapt — bash idioms silently break in the others:
| You wrote | bash | powershell | cmd | |---|---|---|---| | cat / ls | ✓ | ✓ (aliases) | ✗ (type / dir) | | grep / sed / GNU find | ✓ | ✗ | ✗ | | a && b | ✓ | ✗ in PS 5.1 (use ;) | ✓ | | 2>/dev/null | ✓ | 2>$null | 2>nul | | single-quoted 'args' | ✓ | ✓ | ✗ (use "...") |
Practical rule: if shell_kind != "bash", prefer commands that are portable (a bare program name + args, e.g. node x.js, python -m pip install ...) or write the PowerShell/cmd-native form. Don't assume coreutils. PTY sessions (terminal_pty_*) are POSIX-only and return an "unsupported on Windows" error.
Paths under shell_kind: "bash" on Windows (Git Bash): backslashes are escape characters, so a Windows path passed verbatim gets mangled (cat C:\Users\me\x → bash reads C:Usersmex). Use forward slashes (C:/Users/me/x, which Git Bash accepts) or the MSYS form (/c/Users/me/x). Quoting a backslash path in single quotes also preserves it (cat 'C:\Users\me\x').
Pipes (|), redirects (>, <, >>), conditionals (&&, ||, ;), and globs (*, ?, [) are detected automatically. You can pass them with the default shell=False and the runtime will transparently route through /bin/bash -c and surface auto_shell: true in the envelope:
terminal_exec("ps aux | sort -k3 -rn | head -40")
→ { exit_code: 0, stdout: "...", auto_shell: true, ... }For simple argv commands (no metacharacters) shell=False is faster and direct-execs the binary. For commands with shell features but no metacharacters that the detector catches (rare — exotic bash builtins, here-strings), pass shell=True explicitly:
terminal_exec("set -e; complicated bash logic", shell=True)Quoted strings work either way — the detector uses shlex.split which handles "quoted args with spaces" correctly.
| Need | Tool | |---|---| | One-shot command, ≤30s | terminal_exec | | One-shot command, might be longer | terminal_exec (auto-promotes) | | Long-running job from the start | terminal_job_start | | State across calls (cd, env, REPL) | terminal_pty_open + terminal_pty_run | | Search file contents (any path) | terminal_rg | | Find files by name/glob (any path) | terminal_glob | | Retrieve truncated output | terminal_output_get | | Tree / stat / du | terminal_exec("ls -la"/"stat foo"/"du -sh path") | | HTTP / DNS / ping / archives | terminal_exec("curl ..."/"dig ..."/"tar xzf ...") |
See references/exit_codes.md for the full POSIX + signal-induced + semantic catalog.
Other measured skills in the registry, with their headline benchmark lift.