# Exec leaves
A leaf is a command node with `exec` and no nested `commands`. Exactly one **source** form should be set; `argv` is either the full command or extra arguments, depending on the form.
## Plain argv
```yaml
commands:
hello:
about: Say hello
exec:
argv: ["echo", "hello"]
diff:
about: Git diff with forwarded paths and flags
exec:
argv: ["git", "diff"]
passthrough: true
```
```bash
jan diff -- src/foo.rs
jan diff -w --cached
```
Without `passthrough: true`, trailing CLI arguments are rejected.
For inlined POSIX shells (`bash`/`zsh`/`sh` + `-c`/`-lc` + script body), jan inserts a `$0` placeholder before passthrough args so `$1` / `"$@"` behave like a normal script. Prefer the dedicated `exec.bash` / `exec.sh` / `exec.zsh` fields when you can.
`argv[0]` is resolved on the **PATH jan itself inherited**, before spec `path` / `dependencies` prepends apply. A spec directory cannot substitute its own `bash` or `python3` for the system one. An `argv[0]` containing `/` is used verbatim.
## Local file (`exec.file`)
```yaml
commands:
local-tool:
about: Run a script from the tree
exec:
file: bin/tool.sh # relative to jan use root
argv: [bash] # optional interpreter prefix
sha256: <64-hex> # optional local pin
passthrough: true
```
Without `argv`, jan runs the file path directly (executable bit set on Unix). With `argv`, the script path is appended after the prefix (e.g. `bash bin/tool.sh`).
## Remote URL (`exec.url`)
```yaml
commands:
show-docs:
about: Run a verified remote script
exec:
url: https://example.com/show.py
sha256: <64-hex> # required
argv: ["python3"]
ttl: 86400 # optional cache TTL seconds (default 86400)
passthrough: true
```
Fetched into `~/.cache/jan/objects/`, verified, then executed. HTTPS only unless `--allow-http` / `JAN_ALLOW_HTTP`. See [Include and remote](include.md).
## Language sources
A **single-line** value ending in the right extension is a path under the preferred tree. Anything else (including multiline YAML) is **inline source**.
### Python (`exec.python`)
Runs with the `packages.uv` venv interpreter when present; otherwise `python3`. `argv` is forwarded as `sys.argv` after the script / `-c` body.
```yaml
run:
exec:
python: tools/ingest.py
argv: ["--verbose"]
passthrough: true
```
```yaml
run:
exec:
python: |
import pandas as pd
print(pd.__version__)
```
CLI string eval (`argv: [python3, -c, …]`) still works alongside this form.
### Node (`exec.node`)
`.js` / `.mjs` / `.cjs` path or inline (`node -e`). Uses `packages.pnpm` `NODE_PATH` when present. `argv` is `process.argv` extras.
### Kotlin (`exec.kotlin`)
`.kt` / `.kts` path or inline. Compile-once into the package cache, wire the gradle classpath, forward `exec.argv` to `main`. Needs `kotlinc` (and `java`) on PATH; `packages.gradle` jars are injected automatically.
```yaml
run:
exec:
kotlin: packages/kotlin-greet.kts
argv:
- -n
- "${{ inputs.name }}"
```
### Shells (`exec.bash` / `exec.sh` / `exec.zsh`)
| `bash` | `.sh`, `.bash` | `bash -lc` |
| `sh` | `.sh` | `sh -c` |
| `zsh` | `.zsh`, `.sh` | `zsh -c` |
Inline forms insert `$0` so `$1` / `"$@"` match script semantics. `argv` is those positional parameters (plus passthrough).
## Literal text (`exec.text` / `exec.cat`)
Prints a YAML block with **no subprocess**. Conventional `commands.help` leaves using this form are inlined into `--help`.
```yaml
help:
about: Describe this script
exec:
text: |
summarize-sales
Parse a sales CSV with pandas and print a regional summary.
```
`cat:` is an alias for `text:`.
## Mutual exclusion
Set only one of: `url`, `file`, `kotlin`, `python`, `node`, `bash`, `sh`, `zsh`, `text`. Combine with `argv` and `passthrough` as documented above.