jan-cli 0.20.0

YAML-defined CLI trees with progressive help, optional exec aliases, merged extra specs, and SQLite audit logging keyed by git branch
Documentation
# YAML spec format

A jan tree is a YAML document with optional `metadata`, optional root-level `include`, and a `commands` map. After `jan use`, that document (plus grafted includes) **is** the CLI.

## Root document

```yaml
metadata:
  name: myapp
  description: |
    Shown at the root help level.

include:
  - fragments/extra.yaml

commands:
  hello:
    about: Say hello
    exec:
      argv: ["echo", "hello"]
```

| Field | Type | Description |
|-------|------|-------------|
| `metadata.name` | string | Optional display name |
| `metadata.description` | string | Root help blurb |
| `include` | list | Merge other YAML command maps into this root |
| `commands` | map | Top-level subcommands |

## Command node fields

Each entry under `commands` is a **command node**:

| Field | Type | Description |
|-------|------|-------------|
| `about` | string | Shown in help |
| `commands` | map | Nested subcommands |
| `exec` | object | Run a program or print literal text |
| `include` | string or map | Graft YAML as a subtree, or a script file as an exec leaf |
| `os` | list | Offer this node only on listed platforms (`linux`, `macos`, `windows`; `darwin``macos`) |
| `computer` | list | Offer this node only on listed registered computer ids (`jan computer set`); empty = all computers |
| `path` | string | Directory prepended to `PATH` when this script runs |
| `dependencies` | list | Other script **names** whose `path` dirs are prepended first |
| `packages` | map | `uv` / `pnpm` / `gradle` deps (see [Packages]packages.md) |
| `requires` | list | External binaries that must exist on `PATH` before run |
| `env` | map | Public / private / `pass` (see [Env]env.md) |
| `inputs` | map | Named `--flags` (see [Inputs]inputs.md) |
| `cron` | string or list | Five-field crontab expression(s) (see [Cron]cron.md) |
| `tests` | map | Given/When/Then cases (see [Tests]tests.md) |
| `aliases` | string, list, or map | Extra `jan alias` names and/or traditional shell aliases (see [`jan alias`]../cli/alias.md) |
| `config` | map | Host configuration for `jan config` emit / link / unlink / apply / deps (see [`jan config`]../cli/config.md) |

## Constraints

- A node cannot define both `exec` and nested `commands`.
- A node with `include` cannot also define `exec` or `commands` in the same map (the include *becomes* the node body).
- Language `exec` fields (`url`, `file`, `kotlin`, `python`, `node`, `bash`, `sh`, `zsh`, `text`) are mutually exclusive.
- `exec.argv` for a plain argv leaf must be non-empty; the first element is the program.
- `exec.url` requires `sha256`. `exec.file` may optionally pin `sha256`.

## Nested subcommands

```yaml
commands:
  android:
    about: Android workflows
    commands:
      skills:
        about: Skill tools
        commands:
          list:
            about: List skills
            exec:
              argv: ["echo", "listing"]
```

Invoke: `jan android skills list`

## Platform filtering (`os`)

```yaml
commands:
  ports:
    os: [linux]
    about: List listening ports (Linux only)
    exec:
      argv: ["ss", "-tlnp"]
```

Nodes hidden on other platforms are omitted from help and cannot be invoked. Override the detected platform with `JAN_OS` (this is a same-user footgun, not a security boundary).

## Conventional `run` / `help` children

Script-style nodes often look like:

```yaml
sum:
  about: Add numbers
  commands:
    help:
      exec:
        text: |
          sum — add comma-separated integers
    run:
      exec:
        bash: |
          IFS=, read -r -a n <<< "$1"
          ...
        passthrough: true
```

`jan alias` emits aliases for `run` leaves, plus any extra names or shell RHS declared in `aliases:`. Subcommand `--help` lists those aliases. `jan cron` executes the `run` child of a scheduled script.

## Chain merge

When you invoke a deep path, jan merges `dependencies`, `requires`, `env`, `inputs`, `packages`, and `path` from every node along the chain:

- Deeper nodes override public `env` keys, package-manager decls, and input defs
- Private env names are unioned
- `packages.uv` / `pnpm` / `gradle` are replaced per manager (no list merge)

Then it resolves typed inputs, interpolates `${{ inputs.* }}`, prepends PATH, checks `requires`, and spawns the leaf.