jan-cli 0.27.0

YAML-defined CLI trees with progressive help, optional exec aliases, merged extra specs, and SQLite audit logging keyed by git branch
Documentation
# 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**.

Language leaves use Jan’s [warm runtime pool](../cli/runtime.md) when the supervisor is available (isolated child per job). Set `JAN_RUNTIME=0` to force a cold interpreter spawn.

### 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`)

| Field | Path extensions | Inline invocation |
|-------|-----------------|-------------------|
| `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.