schedx Repository Tour

Open this file in a browser with internet access so it can load Mermaid and impress.js from CDNs.

# Inside schedx

A repository tour for contributors: what the tool does, how a command flows through the codebase, where state lives, and where to edit when you want to change behavior.

### Core idea

`schedx` is a local-first scheduler CLI for commands, prompts, and webhooks.

### What to remember

- CLI front door
- file-backed state
- backend-provided ticks
- single execution engine

## Repository Map

src/commands user-facing command handlers
src/engine dispatch, execution, locks, logs
src/store files, atomic writes, history, backups
src/schedule parse and compute due times
src/model serializable domain types
src/backend systemd, launchd, none
src/output human and JSON formatting
tests CLI-first integration coverage

## The Whole System in One Diagram

flowchart LR U["User or Agent"] --> CLI["Clap CLI"] CLI --> CMD["commands/*"] CMD --> STORE["store/*"] CMD --> BACKEND["backend/*"] STORE --> DISPATCH["engine/dispatcher.rs"] BACKEND --> DISPATCH DISPATCH --> EXEC["engine/executor.rs"] EXEC --> LOGS["logs + history + last_run"]

## How `schedx add` Works

sequenceDiagram participant User participant Main as main.rs participant Add as commands/add.rs participant Parser as schedule/parser.rs participant Store as store/state.rs participant Backend as backend/* User->>Main: schedx add ... Main->>Add: execute(...) Add->>Parser: parse_schedule(...) Add->>Store: update_state(...) Add->>Backend: ensure_dispatcher() Add-->>User: created job + next run

The command handler validates flags, builds an `Action`, parses the schedule, writes state under lock, and then tries to install or heal the tick backend.

## CLI Routing Is Intentionally Thin

    let cli = Cli::parse();

    let result = match &cli.command {
        Commands::Add { .. } => commands::add::execute(...),
        Commands::List { .. } => commands::list::execute(...),
        Commands::Run { id } => commands::run::execute(id),
        Commands::Dispatch => commands::dispatch::execute(),
        Commands::Exec { .. } => commands::exec::execute(...),
    };

This is a repository habit worth preserving: keep `main.rs` boring and push behavior into focused modules.

## Dispatch and Execution

flowchart TD TICK["systemd / launchd / daemon"] --> D["_dispatch"] D --> DUE{"Job due?"} DUE -- no --> NEXT["Check next job"] DUE -- yes --> SKIP{"skip_remaining > 0?"} SKIP -- yes --> ADV["Advance without executing"] SKIP -- no --> EXEC["_exec"] EXEC --> LOCK["Per-job lock"] LOCK --> ACTION["Run / Prompt / Webhook"] ACTION --> STATE["Update last_run and counters"] ACTION --> HIST["Append history"] ACTION --> LOG["Write log file"]

## Persistence Model

    ~/.schedx/
      jobs.json
      config.json
      run-history.jsonl
      backups/
      logs/
      locks/

### jobs.json

authoritative job definitions + last-run summary

### run-history.jsonl

append-only audit trail of completed and skipped runs

## Action Types

### Run

- shell or argv mode
- optional workdir
- stdout/stderr to log file

### Prompt

- named agent profile
- stdin or argv prompt mode
- same timeout/log rules as commands

### Webhook

- typed HTTP method
- HTTPS by default
- bounded response logging

### Common rule

- one execution engine
- one run record model
- one log/history path

## Backends

- `systemd` on Linux
- `launchd` on macOS
- `none` for manual daemon/test mode

The backend only provides the heartbeat. The due-job logic stays inside Rust, in the dispatcher.

## Tests Tell the Story

    pub fn cmd(&self) -> assert_cmd::Command {
        let mut cmd = assert_cmd::Command::cargo_bin("schedx").unwrap();
        cmd.env("SCHEDX_HOME", self.home());
        cmd.env("SCHEDX_BACKEND", "none");
        cmd
    }

Tests create an isolated scheduler home, disable system backends, and exercise the real binary. That keeps them close to user behavior.

## Where to Edit

### New command

- `src/cli.rs`
- `src/main.rs`
- `src/commands/<name>.rs`

### New action type

- `src/model/action.rs`
- `src/commands/add.rs`
- `src/engine/executor.rs`
- `src/output/format.rs`

### Schedule changes

- `src/schedule/parser.rs`
- `tests/schedule_parse.rs`

### Storage changes

- `src/store/paths.rs`
- `src/store/state.rs`
- `src/store/history.rs`

## First-Day Contributor Checklist

- Read `src/main.rs`, `src/cli.rs`, `src/commands/add.rs`
- Follow a job through `dispatcher.rs` and `executor.rs`
- Inspect files under `SCHEDX_HOME` after running the CLI locally
- Use the CLI tests as your behavior contract
- Preserve the separation between commands, engine, store, and output

The one-line summary: local-first scheduling engine, CLI front door, backend heartbeat, single executor.