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

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

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