jan-cli 0.24.0

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

Daemon-backed scheduler for `cron:` script leaves. The daemon ticks every **100 ms**.
`jan cron start` / `stop` manage the daemon; `--list` reads its in-memory schedule cache
(not the YAML tree on disk).

```bash
jan cron start                          # start daemon (systemd when available)
jan cron stop                           # stop daemon
jan cron status
jan cron refresh                        # reload cache after editing YAML cron:
jan cron disable ping-agent             # pause an agent (cron + event wakeups)
jan cron enable ping-agent
jan cron disabled                       # list disabled leaf names
jan cron --list                         # cached schedules (daemon must be running)
jan cron --list --at "2026-08-05 09:00"
jan cron --dry-run --at "2026-08-05 10:30:00"   # one-shot; no daemon
jan cron --help
```

## Lifecycle

| Command | Action |
|---------|--------|
| `start` | Start the daemon; installs `~/.config/systemd/user/jan-cron.service` when `systemctl --user` is available, otherwise starts a background process |
| `stop` | Stop the daemon and disable/remove the systemd user service |
| `status` | Report pid, uptime, schedule count, disabled count, events.sock state, wakeup drops/reconnects, running/deferred jobs, and preferred tree |
| `refresh` | Reload the in-memory schedule cache from the preferred YAML tree (`reload` is an alias) |
| `disable <agent>` | Persistently disable cron ticks and Unifier event wakeups for a script leaf |
| `enable <agent>` | Re-enable a disabled agent |
| `disabled` | List disabled agent leaf names |

## Disable / enable

`<agent>` is the script **leaf name** (`ping-agent`) or a chain (`scripts agents ping-agent`).

Disabled agents are skipped for:

- scheduled `cron:` ticks
- Unifier mailbox / named-event wakeups
- one-shot `jan cron --at` runs

State is stored in `$JAN_CONFIG_DIR/cron-disabled.json` (default `~/.config/jan-cli/cron-disabled.json`) and survives `refresh` / daemon restarts. `jan cron --list` marks disabled entries with `[disabled]`.

```bash
jan cron disable ping-agent
jan cron disable scripts agents mute-tracker
jan cron disabled
jan cron enable ping-agent
```

## Schedule cache

On start (and on `refresh`), the daemon walks the preferred tree once and stores every
script with a `cron:` field. The tick loop and `jan cron --list` use that cache only.

After editing `cron:` in YAML, run `jan cron refresh`.

Control socket: `$XDG_RUNTIME_DIR/jan-cli/cron.sock` (fallback: `$JAN_CONFIG_DIR/run/cron.sock`).

## Unifier event wakeups

The daemon also connects to Unifier's `.daemon/events.sock` (under `$UNIFIER_HOME` or
`~/.local/unifier`) and keeps reconnecting while Unifier is down.

| Notice | Wakes | Args / env |
|--------|-------|------------|
| `{"kind":"mailbox","to":"…","id":"…",…}` | Script leaf whose **name** equals `to` | `--message-id <id>`; `JAN_UNIFIER_MESSAGE_ID`, `JAN_UNIFIER_FROM`, `JAN_UNIFIER_TO`, `JAN_UNIFIER_KIND=mailbox` |
| `{"kind":"event","name":"…","id":"…"}` | Script leaf whose **name** equals `name` | `--event-id <id>` (and `--event-name`); `JAN_UNIFIER_EVENT_ID`, `JAN_UNIFIER_EVENT_NAME`, `JAN_UNIFIER_KIND=event` |

Wakeups are queued (cap 256; overflow drops oldest and increments `drops=` in
status) and spawned on the **next 100 ms tick** (same path as cron matches),
subject to concurrency limits:

| Env | Default | Effect |
|-----|---------|--------|
| `JAN_CRON_MAX_CONCURRENT` | `32` | Max simultaneous agent processes |
| `JAN_CRON_MAX_DEFERRED` | `256` | Max jobs waiting for a free slot |
| `JAN_CRON_ALLOW_OVERLAP` | off | If `1`/`true`, allow the same leaf to run twice at once |

By default, a second wake for a leaf that is already running is skipped
(`overlap_skips=`). Tick-phase notices (`kind: tick`) are not agent wakeups.

The leaf does not need a `cron:` field — only a matching script name. Disabled leaves are skipped.

```bash
unifier daemon start
jan cron start -v

# Sender (often itself a cron agent):
unifier message --from ping-agent pong-agent '{"hello":"world"}'

# Jan invokes:
#   jan --no-log scripts agents pong-agent run --message-id <uuid>
```

Start Unifier before (or alongside) Jan so the socket exists; Jan will wait and reconnect.

## One-shot testing (no daemon)

`--dry-run` and `--at` (without `--list`) evaluate schedules in-process:

```bash
jan cron --dry-run --at "2026-08-05 10:30"       # minute match
jan cron --dry-run --at "2026-08-05 10:30:45"    # second match
jan cron --at "2026-08-05 10:30"                 # run matches once
```

## Schedule granularity

See [spec/cron.md](../spec/cron.md) for five-, six-, and seven-field expressions.

Prefer `jan use` so the daemon can resolve your tree.