Shell Tool
Execute short-lived shell commands in the workflow workspace. The backend uses
Nushell when nu is installed. Otherwise it passes
the command to the host shell (cmd.exe on Windows or the platform
/bin/sh-style shell on POSIX). Shell-specific syntax is therefore
conditional on the installed runner.
External binaries on PATH (npm, node, python, git, etc.) are
available to whichever runner is selected.
For portable calls, prefer one external command with ordinary arguments, such
as git status or python -V. Use file_read, file_modify, or fs_search
for filesystem work; they avoid runner-specific parsing.
Runner-dependent syntax
The node rejects space-delimited && and || before selecting a runner, so
those operators are unsupported even when the host shell would normally
accept them. Prefer separate tool calls. If the environment is known to have
Nushell, its equivalents include:
try { npm install } catch { print 'install failed'; exit 1 }
open README.md --raw | lines | first 20
Do not send Nushell pipelines, Bash substitutions, PowerShell cmdlets, or
cmd.exe builtins unless the selected environment is known to support that
syntax. GNU utilities such as sed, awk, and grep are not generally
available on Windows.
shell_execute Tool
Schema
| Field |
Type |
Required |
Description |
| command |
string |
Yes |
Command interpreted by Nushell when installed, otherwise by the host shell |
| timeout |
int |
No |
Seconds (default 30, max 600) |
Response
{
"stdout": "command output",
"exit_code": 0,
"truncated": false,
"command": "ls"
}
| Exit code |
Meaning |
| 0 |
Success |
| 124 |
Timed out |
| non-zero |
Failure |
Nushell reference (only when nu is installed)
| Bash / cmd.exe form |
Nushell form |
cmd1 && cmd2 (and-then) |
cmd1; cmd2 (unconditional sequential — see below for short-circuit) |
| `cmd1 |
|
$VAR substitution |
$env.VAR |
`cmd` or $(cmd) |
(cmd) (parens, no dollar) |
cmd > file.txt |
cmd | save file.txt |
cmd >> file.txt |
cmd | save --append file.txt |
cmd 2>&1 |
cmd | complete | get stdout (all output is captured anyway) |
if [ -f x.txt ]; then ... |
if ('x.txt' | path exists) { ... } |
for f in *.py; do ... |
glob '*.py' | each { |f| ... } |
* glob in argv (auto-expand) |
wrap in quotes or use glob |
~/path |
('~/path' | path expand) |
sed -n '1,N p' file |
open file --raw | lines | first N (prefer file_read with limit) |
head -n N file |
open file --raw | lines | first N |
tail -n N file |
open file --raw | lines | last N |
sed -i 's/a/b/' file |
use file_modify (edit op) — not the shell |
grep 'pat' file |
open file --raw | find 'pat' (prefer fs_search) |
grep -r 'pat' src/ |
use fs_search (grep mode) |
wc -l file |
open file --raw | lines | length |
find . -name '*.py' |
glob '**/*.py' |
xargs cmd |
each { |x| cmd $x } |
Short-circuit "and-then" when Nushell is active
The node explicitly rejects the usual space-delimited && form. With
Nushell available, use:
# Short-circuit using try/catch
try { npm install } catch { print 'install failed'; exit 1 }
ls -la
# Or inspect an external command's exit code
let r = (do { npm install } | complete)
if $r.exit_code == 0 { ls -la } else { print $r.stderr }
Common tasks when Nushell is active
| Task |
Command |
| Show current dir |
pwd (nu builtin) |
| List files |
ls (returns a table — pipe further) |
| List recursively |
ls **/* |
| Read file |
open README.md (text/json/csv auto-parsed) or cat README.md |
| Write to file |
'hello' | save -f output.txt |
| Append |
'more' | save --append output.txt |
| Find files by name |
glob '**/*.py' |
| Search content |
rg 'pattern' . (if ripgrep on PATH) or open file.txt | find 'pattern' |
| Copy / move / delete |
cp a b, mv a b, rm a |
| Make folder |
mkdir new |
| Run npm / node / python |
npm install, node app.js, python -V (via PATH) |
| Capture command output into a var |
let v = (npm -v | str trim) |
| Conditional on a binary existing |
if (which git | is-empty) { print 'no git' } |
Workspace and paths
- The cwd is the per-workflow workspace; relative paths resolve there.
- Filesystem operations elsewhere on this tool (read/write/edit via
file_*) are workspace-contained and reject ../~ traversal. Shell execute() itself retains historical host-shell behavior and is not path-restricted, so prefer file_read / file_modify / fs_search for actual filesystem work.
Use the right tool
| Need |
Tool |
Why |
| List / search / one-shot file ops |
shell_execute |
Fast, in-workspace |
| Reading or editing a specific file |
file_read / file_modify |
Path-sandboxed, no shell parsing surprises |
Long-running processes (dev servers, watchers, npm run dev) |
process_manager |
Streams output, restartable, doesn't tie up the agent |
| Recursive code search |
fs_search |
grep mode, structured results |
Guidelines
- Never use
&& or ||. The node pre-flight rejects their common
space-delimited forms before invoking either runner, and their behavior is
not portable even without surrounding spaces.
- Prefer one simple command per call. Chaining, variables, redirection, pipelines, and builtins depend on whether Nushell or the host shell was selected.
- Use dedicated filesystem tools.
file_read, file_modify, and fs_search are workspace-contained and avoid shell parsing differences.
- Treat Nushell syntax as conditional. The reference above applies only when
nu is installed.
- Short-lived only.
shell_execute always awaits completion — a small timeout does not make it run in the background, it just kills the command after N seconds. If the command runs longer than ~30s, opens a port, watches files, or is described as "dev server / watcher / daemon" (npm run dev, vite, tsx watch, python -m http.server, ...), use process_manager instead. Trying to "fire and forget" with timeout=2 will kill the process the moment the port comes up.
- The timeout range is 1–600 seconds. Use
process_manager instead of raising it for persistent processes.
1---2name: shell-skill3description: Execute short-lived shell commands inside the per-workflow workspace. Uses Nushell when installed and otherwise the host shell. External tools on PATH (npm, node, python, git, ...) are available.4---56# Shell Tool78Execute short-lived shell commands in the workflow workspace. The backend uses9[Nushell](https://www.nushell.sh/) when `nu` is installed. Otherwise it passes10the command to the host shell (`cmd.exe` on Windows or the platform11`/bin/sh`-style shell on POSIX). Shell-specific syntax is therefore12conditional on the installed runner.1314External binaries on `PATH` (`npm`, `node`, `python`, `git`, etc.) are15available to whichever runner is selected.1617For portable calls, prefer one external command with ordinary arguments, such18as `git status` or `python -V`. Use `file_read`, `file_modify`, or `fs_search`19for filesystem work; they avoid runner-specific parsing.2021## Runner-dependent syntax2223The node rejects space-delimited `&&` and `||` before selecting a runner, so24those operators are unsupported even when the host shell would normally25accept them. Prefer separate tool calls. If the environment is known to have26Nushell, its equivalents include:2728```nu29try { npm install } catch { print 'install failed'; exit 1 }30open README.md --raw | lines | first 2031```3233Do not send Nushell pipelines, Bash substitutions, PowerShell cmdlets, or34`cmd.exe` builtins unless the selected environment is known to support that35syntax. GNU utilities such as `sed`, `awk`, and `grep` are not generally36available on Windows.3738## shell_execute Tool3940### Schema4142| Field | Type | Required | Description |43|-------|------|----------|-------------|44| command | string | Yes | Command interpreted by Nushell when installed, otherwise by the host shell |45| timeout | int | No | Seconds (default 30, max 600) |4647### Response4849```json50{51 "stdout": "command output",52 "exit_code": 0,53 "truncated": false,54 "command": "ls"55}56```5758| Exit code | Meaning |59|---|---|60| 0 | Success |61| 124 | Timed out |62| non-zero | Failure |6364## Nushell reference (only when `nu` is installed)6566| Bash / cmd.exe form | Nushell form |67|---|---|68| `cmd1 && cmd2` (and-then) | `cmd1; cmd2` *(unconditional sequential — see below for short-circuit)* |69| `cmd1 || cmd2` (or-else) | `try { cmd1 } catch { cmd2 }` |70| `$VAR` substitution | `$env.VAR` |71| `` `cmd` `` or `$(cmd)` | `(cmd)` *(parens, no dollar)* |72| `cmd > file.txt` | `cmd \| save file.txt` |73| `cmd >> file.txt` | `cmd \| save --append file.txt` |74| `cmd 2>&1` | `cmd \| complete \| get stdout` *(all output is captured anyway)* |75| `if [ -f x.txt ]; then ...` | `if ('x.txt' \| path exists) { ... }` |76| `for f in *.py; do ...` | `glob '*.py' \| each { \|f\| ... }` |77| `*` glob in argv (auto-expand) | wrap in quotes or use `glob` |78| `~/path` | `('~/path' \| path expand)` |79| `sed -n '1,N p' file` | `open file --raw \| lines \| first N` *(prefer `file_read` with `limit`)* |80| `head -n N file` | `open file --raw \| lines \| first N` |81| `tail -n N file` | `open file --raw \| lines \| last N` |82| `sed -i 's/a/b/' file` | use `file_modify` (edit op) — not the shell |83| `grep 'pat' file` | `open file --raw \| find 'pat'` *(prefer `fs_search`)* |84| `grep -r 'pat' src/` | use `fs_search` (grep mode) |85| `wc -l file` | `open file --raw \| lines \| length` |86| `find . -name '*.py'` | `glob '**/*.py'` |87| `xargs cmd` | `each { \|x\| cmd $x }` |8889### Short-circuit "and-then" when Nushell is active9091The node explicitly rejects the usual space-delimited `&&` form. With92Nushell available, use:9394```nu95# Short-circuit using try/catch96try { npm install } catch { print 'install failed'; exit 1 }97ls -la9899# Or inspect an external command's exit code100let r = (do { npm install } | complete)101if $r.exit_code == 0 { ls -la } else { print $r.stderr }102```103104## Common tasks when Nushell is active105106| Task | Command |107|---|---|108| Show current dir | `pwd` *(nu builtin)* |109| List files | `ls` *(returns a table — pipe further)* |110| List recursively | `ls **/*` |111| Read file | `open README.md` *(text/json/csv auto-parsed)* or `cat README.md` |112| Write to file | `'hello' \| save -f output.txt` |113| Append | `'more' \| save --append output.txt` |114| Find files by name | `glob '**/*.py'` |115| Search content | `rg 'pattern' .` *(if ripgrep on PATH)* or `open file.txt \| find 'pattern'` |116| Copy / move / delete | `cp a b`, `mv a b`, `rm a` |117| Make folder | `mkdir new` |118| Run npm / node / python | `npm install`, `node app.js`, `python -V` *(via PATH)* |119| Capture command output into a var | `let v = (npm -v \| str trim)` |120| Conditional on a binary existing | `if (which git \| is-empty) { print 'no git' }` |121122## Workspace and paths123124- The cwd is the per-workflow workspace; relative paths resolve there.125- Filesystem operations elsewhere on this tool (read/write/edit via `file_*`) are workspace-contained and reject `..`/`~` traversal. Shell `execute()` itself retains historical host-shell behavior and is **not** path-restricted, so prefer `file_read` / `file_modify` / `fs_search` for actual filesystem work.126127## Use the right tool128129| Need | Tool | Why |130|---|---|---|131| List / search / one-shot file ops | **shell_execute** | Fast, in-workspace |132| Reading or editing a specific file | **file_read** / **file_modify** | Path-sandboxed, no shell parsing surprises |133| Long-running processes (dev servers, watchers, `npm run dev`) | **process_manager** | Streams output, restartable, doesn't tie up the agent |134| Recursive code search | **fs_search** | grep mode, structured results |135136## Guidelines1371381. **Never use `&&` or `||`.** The node pre-flight rejects their common139 space-delimited forms before invoking either runner, and their behavior is140 not portable even without surrounding spaces.1412. **Prefer one simple command per call.** Chaining, variables, redirection, pipelines, and builtins depend on whether Nushell or the host shell was selected.1423. **Use dedicated filesystem tools.** `file_read`, `file_modify`, and `fs_search` are workspace-contained and avoid shell parsing differences.1434. **Treat Nushell syntax as conditional.** The reference above applies only when `nu` is installed.1445. **Short-lived only.** `shell_execute` *always* awaits completion — a small `timeout` does **not** make it run in the background, it just kills the command after N seconds. If the command runs longer than ~30s, opens a port, watches files, or is described as "dev server / watcher / daemon" (`npm run dev`, `vite`, `tsx watch`, `python -m http.server`, ...), use **`process_manager`** instead. Trying to "fire and forget" with `timeout=2` will kill the process the moment the port comes up.1456. **The timeout range is 1–600 seconds.** Use `process_manager` instead of raising it for persistent processes.