jan-cli 0.13.0

YAML-defined CLI trees with progressive help, optional exec aliases, merged extra specs, and SQLite audit logging keyed by git branch
Documentation
# GitHub Actions–inspired features for jan

Jan today is a **nested command tree** (`commands` → `exec`) with local runtime metadata (`env`, `requires`, `dependencies`, `os`, `cron`, `include`). GitHub Actions is a **workflow graph** (`on` → `jobs` → `steps`). The useful inspiration is the *declarative vocabulary*, not cloning Actions’ job graph wholesale.

```mermaid
flowchart LR
  subgraph gha [GitHub Actions]
    on --> jobs --> steps
  end
  subgraph jan [jan today]
    commands --> exec
    cron --> runLeaf[run leaf]
  end
  gha -.->|inspire fields| jan
```

## Already close to Actions

| Actions idea | jan today |
|---|---|
| `name:` / description | `metadata`, `about` |
| `runs-on:` / OS | `os:` filter |
| `env:` | `env:` (chain-merged) |
| `defaults.run.working-directory` | global `--cwd` only (no per-node `workdir`) |
| reusable workflows | `include:` (root list + per-node) |
| schedule trigger | `cron:` + `jan cron` |
| “needs another checkout’s tools” | `dependencies:` / `path:` / `requires:` |
| composite action packing | `jan bundle` / install |

## High-value features (fit jan’s model)

These extend `CommandNode` / `ExecSpec` without turning leaves into multi-step workflows.

### 1. Typed inputs (`inputs:` + expressions)

**Shipped.** Declare CLI args on a script node; interpolate into `env` / `argv`.

```yaml
backup:
  about: Backup a path
  inputs:
    path:
      description: Source directory
      required: true
    dest:
      default: ~/Backups
  env:
    public:
      SRC: ${{ inputs.path }}
  commands:
    run:
      exec:
        argv: ["bash", "-lc", "rsync -a \"$SRC\" \"${{ inputs.dest }}\""]
```

```bash
jan … backup run --path ~/Documents
jan … backup run --path ~/Documents --dest /mnt/backup
```

Only `${{ inputs.<name> }}` expressions are supported (not a full Actions expression language).

### 2. Conditionals (`if:`)

Actions: `if: runner.os == 'Linux' && inputs.mode == 'full'`.

**jan shape:** skip or hide a node / refuse run when expression is false. Start small: `os`, env vars, inputs, exit of a tiny predicate—not a full JS-like language.

```yaml
ports:
  if: ${{ os == 'linux' }}
  exec:
    argv: ["ss", "-tlnp"]
```

(`os:` already hard-filters; `if:` generalizes to runtime/env/input conditions.)

### 3. Per-node working directory + shell defaults

Actions: `defaults.run`, `working-directory`, `shell:`.

```yaml
exec:
  argv: ["pytest"]
  workdir: ${{ inputs.path }}
  shell: bash   # optional sugar over bash -lc
```

Today only global `--cwd`. Per-leaf `workdir` is a small ExecSpec change with big ergonomics for script trees.

### 4. Timeout and failure policy

Actions: `timeout-minutes`, `continue-on-error`.

```yaml
run:
  timeout: 5m
  continue-on-error: true
  exec:
    argv: ["bash", "-lc", "..."]
```

Especially useful for `jan cron` (one hung script should not block the minute).

### 5. Richer triggers (still local)

Actions: `on: schedule | workflow_dispatch | push | …`.

Keep triggers **local and explicit**—do not watch git remotes inside jan:

| Trigger | jan analogue |
|---|---|
| `schedule` | done (`cron`) |
| `workflow_dispatch` | normal `jan … run` (+ inputs) |
| path/file hooks | optional `watch:` / `jan watch` (inotify) — stretch |
| “run after X” | `needs:` among cron jobs or a tiny `pipeline:` — stretch |

A GHA-flavored umbrella field is optional sugar, not required:

```yaml
on:
  schedule: ["30 10 * * *"]
  # dispatch is implicit via CLI
```

### 6. Secrets / env files (local analogue of `secrets:`)

**Shipped:** `env.private` lists names that must be set in the host environment; values are copied into the child and never stored in YAML. `env.public` holds assignable values. When either is set, the child gets a cleared environment (essentials + listed vars only).

```yaml
env:
  public:
    ISSUE_EDITOR: vim
  private:
    - GH_TOKEN
```

Future sugar may still add `${{ secret.NAME }}` expression forms; existence checks already cover the main security need.

## Medium-value / stretch

### 7. Multi-step `run:` sequences (mini-jobs)

```yaml
deploy:
  steps:
    - run: jan scripts misc preflight run
    - run: ./deploy.sh
      workdir: infra
    - if: ${{ success() }}
      run: jan notify run
```

This is the biggest semantic shift: a leaf becomes a **job**. Worth it only if many scripts are glue of several execs; otherwise keep one `exec` and let bash/Justfile own sequences.

### 8. `needs:` / concurrency for cron

```yaml
nightly:
  cron: "0 2 * * *"
  needs: [backup]
  concurrency: { group: nightly, cancel-in-progress: true }
```

Useful once many cron scripts exist; needs a small lockfile under XDG runtime dir.

### 9. Matrix

```yaml
matrix:
  python: ["3.11", "3.12"]
strategy:
  fail-fast: false
```

Powerful for CI; usually overkill for personal scripts. Defer unless you routinely fan out the same leaf.

### 10. Step outputs / `$GITHUB_OUTPUT` analogue

```yaml
steps:
  - id: ver
    run: echo "v=$(git describe)" >> "$JAN_OUTPUT"
  - run: echo "built ${{ steps.ver.outputs.v }}"
```

Only pays off with multi-step jobs (#7).

### 11. Reusable “actions” (`uses:`)

Beyond `include:` (structural merge), `uses: ./actions/notify.yaml` with inputs/outputs would mirror composite actions. Natural evolution of includes + inputs.

## Poor fits (skip or keep out of core)

- **Hosted runners / `runs-on: ubuntu-latest`** — jan already has host `os:`; no remote fleet.
- **`permissions:` / OIDC / GitHub token scopes** — use OS user + secret refs.
- **Marketplace `uses: actions/checkout@v4`** — out of scope; local `uses:` paths only.
- **PR/push event metadata** — unless you add an explicit `jan hook git …` that *receives* payload from a user-installed hook; don’t poll remotes.
- **Full `${{ }}` expression language** — start with a tiny evaluator (literals, inputs, env, os, `success()`); avoid JS parity.

## Design principle

Stay a **CLI tree with declarative run metadata**, not a second Actions runner:

1. Prefer fields on existing nodes (`inputs`, `if`, `timeout`, `workdir`).
2. Add new builtins only when host orchestration is needed (`jan cron` pattern → maybe `jan watch`, `jan pipeline`).
3. Keep Expressions minimal and validated by `jan validate`.
4. Introduce `steps:` only when single-`exec` + shell becomes clearly painful.

## Suggested adoption order

If/when implementing (not part of this exploration’s deliverable):

1. **`inputs:` + interpolation** — shipped  
2. **`workdir` / `timeout` / `continue-on-error` on `exec`** — small, cron-safe  
3. **`if:`** — generalizes `os:`  
4. **secret refs** — `env.private` covers existence; expression sugar still optional  
5. **`steps:` + outputs** — only if needed  
6. **`needs:` / concurrency / watch / matrix** — last

## Illustrative “Actions-flavored” leaf (target dialect)

```yaml
sync-notes:
  about: Sync notes vault
  cron: "@daily"
  inputs:
    vault:
      default: ~/Notes
  if: ${{ env.JAN_ENABLE_NOTES == '1' }}
  requires: [rsync]
  env:
    VAULT: ${{ inputs.vault }}
  commands:
    run:
      timeout: 10m
      continue-on-error: false
      exec:
        workdir: ${{ inputs.vault }}
        argv: ["bash", "-lc", "rsync -a ./ remote:notes/"]
```

No code changes in this plan—this is the feature map to pull from when extending jan’s YAML.