| name | shell-skill |
| description | Execute short-lived shell commands inside the per-workflow workspace. The shell is Nushell (cross-platform, same syntax on Windows/macOS/Linux). External tools on PATH (npm, node, python, git, ...) are available. |
| allowed-tools | shell |
| metadata | {"author":"machina","version":"4.1","category":"execution"} |
Shell Tool (Nushell)
Execute short-lived shell commands in the workflow workspace. The shell is Nushell — the same grammar runs on Windows, macOS, and Linux. Do not write cmd.exe, PowerShell, or Bash idioms; they will fail or behave wrong.
External binaries on PATH (npm, node, python, git, pwd, etc.) are available — Nu invokes them as external commands automatically.
GNU coreutils (sed, awk, head, tail, grep, cut, sort, uniq, wc, tr, xargs, find) are NOT in PATH on Windows and will fail with Command not found. Use Nushell builtins or switch tools — see the table below.
Check the host before reaching for external tools
Don't burn turns on trial-and-error (sed not found → retry with awk → awk not found → ...). Detect the host once at the start of a task that needs platform-sensitive binaries, then branch:
# `$nu.os-info.name` is "windows" | "linux" | "macos" | "android" | ...
let os = $nu.os-info.name
Then either:
- Skip the shell entirely. For reading / editing / searching files, the right answer almost always is
file_read / file_modify / fs_search — they are path-sandboxed and platform-agnostic by construction.
- Use Nu builtins.
open, lines, first, last, find, glob, length, save, cp, mv, rm, mkdir exist on every host.
- Probe before invoking external CLIs.
if (which sed | is-empty) { ... } else { sed ... } — but at that point, file_read / file_modify is shorter and works everywhere.
Rule of thumb: if your command starts with sed / awk / head / tail / grep, stop and reach for file_read or fs_search instead. You almost never need a host check — you need the dedicated tool.
shell_execute Tool
Schema
| Field | Type | Required | Description |
|---|
| command | string | Yes | Nushell command (single or ;-chained) |
| timeout | int | No | Seconds (default 30, max 300) |
Response
{
"stdout": "command output",
"exit_code": 0,
"truncated": false,
"command": "ls"
}
| Exit code | Meaning |
|---|
| 0 | Success |
| 124 | Timed out |
| non-zero | Failure |
Critical: Nushell ≠ Bash
| Bash / cmd.exe (do NOT use) | Nushell (correct) |
|---|
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" (the && replacement)
The user log showed pwd && ls -la failing — the parser explicitly rejects &&. Use one of:
# A: just sequential, doesn't short-circuit on failure
pwd; ls -la
# B: short-circuit using exit code via try/catch
try { npm install } catch { print 'install failed'; exit 1 }
ls -la
# C: explicit conditional on the previous command's success
let r = (do { npm install } | complete)
if $r.exit_code == 0 { ls -la } else { print $r.stderr }
Use A for "run these in order regardless of outcome", B/C when you must stop on failure.
Common tasks (cross-platform, Nushell)
| 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_*) honour virtual_mode=True and reject ../~ traversal. Shell execute() itself is not path-restricted (deepagents documents this), 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
&&, ||, backticks, $VAR, or > redirection. Use the Nushell equivalent on the right side of the table above.
- Never invoke GNU coreutils (
sed, awk, head, tail, grep, find, wc, cut, sort, uniq, xargs, tr). They are not on Windows. For peeking at a file, use file_read (line-numbered, offset/limit aware); for searching, use fs_search (grep mode).
- Check the host first when an external CLI is unavoidable.
let os = $nu.os-info.name once, then gate by windows / linux / macos. Don't loop "try → fail → retry".
- One command (or
;-chain) per call. No multi-line scripts; if you need control flow, use if / try / each inline.
- 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.
- Nu syntax is host-agnostic. Builtins (
ls, open, glob, lines, save, cp, ...) work the same everywhere. You only need the host check for external tools.
- Quote glob patterns (
'*.py') so Nu's glob builtin expands them, not the caller.
- Capture command exit code with
do { … } | complete if you need to branch on success/failure.