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.