A native macOS app that schedules Claude Code agents to run on a timer and lets you review their output.
ScheduleAgent lives in your Dock and executes claude --print prompts on a schedule you define. Each run's output, tool usage, and token count are captured and stored so you can review them later. The app keeps running while your screen is locked, so agents fire on schedule even when you're away.
Claude Code Desktop added its own scheduled tasks feature. ScheduleAgent takes a different approach and goes further in a few key areas:
- Token and cost tracking — every run records input and output tokens separately. Sort tasks by total token usage across all runs to see what's actually costing you.
- Tool-use audit trail — each run shows exactly which tools were called, in order, with color-coded chips: green for allowed, red for blocked. Desktop just stalls on permission prompts with no audit record.
- Immediate blocked-tool termination — if an agent tries a tool outside its allowed set, ScheduleAgent kills the process instantly and fires a critical notification. You know exactly what was blocked and why.
- Test run in the editor — run any prompt with live streaming output before you save the task. Desktop has no draft mode.
- Task search and sort — filter by name or prompt text; sort by last run, run count, or tokens used.
- Standalone — works with just the
claudeCLI. No Claude Code Desktop installation required.
| # | Name | Schedule | Prompt | Tools |
|---|---|---|---|---|
| 1 | Daily standup digest | Daily at 8:45am, Mon–Fri | Look at git log for the last 24 hours across all branches. Write a 3-bullet standup summary to STANDUP.md — what shipped, what's in progress, any blockers you can infer. |
Read, Write, Bash |
| 2 | Dependency drift monitor | Every 24h | Check package.json (or go.mod, Gemfile, etc.) for dependencies that have newer versions available. If any are more than one major version behind, append a dated warning to DEPS_ALERT.md. |
Read, Write, Bash |
| 3 | Inbox zero assistant | Every 1h | Check my Gmail inbox via MCP. For any unread threads older than 2 hours that look like newsletters or automated notifications, draft a short summary and archive them. Leave anything that looks personal or urgent untouched. |
MCP (Gmail) |
| 4 | Log error watcher | Every 15m | Tail the last 200 lines of ~/logs/app.log. If you find any ERROR or FATAL lines not seen in the previous run (tracked in ~/logs/.last_seen_error), append them with a timestamp to ~/logs/error_digest.md and update the tracker. |
Read, Write, Bash |
| 5 | Weekly project summary | Daily at 6pm, Fridays | Review this week's git commits, any TODO comments added or removed, and open issues mentioned in ISSUES.md. Write a concise weekly summary to WEEKLY.md — wins, carry-overs, and one suggested focus for next week. |
Read, Write, Bash, Glob |
- macOS 14+
- Swift 5.9+ (Xcode 15+ or Swift toolchain)
claudeCLI installed (auto-detected at~/.local/bin/claude,/usr/local/bin/claude, or/opt/homebrew/bin/claude)
Run from source (development):
git clone https://github.com/imjohnbo/schedule-agent.git
cd schedule-agent
swift runBuild a distributable .app bundle:
chmod +x build.sh
./build.sh
open dist/ScheduleAgent.app
# Optionally: cp -r dist/ScheduleAgent.app /Applications/The build script compiles a release binary, wraps it in a proper .app bundle, and ad-hoc signs it (required on Apple Silicon).
The window is split into two panes:
- Left sidebar — lists all your tasks. The header shows the total task and run count, and has sort and add (
+) controls. The sidebar can be hidden with the standard macOS toggle; when hidden, the sort and add controls disappear with it. - Right detail pane — shows the selected task's schedule info, live output during a run, and full run history.
The toolbar at the top right shows task-specific actions when a task is selected: Run Now, Enable/Disable (⚡/⚡̸), and Edit (✎).
- Click + in the sidebar header
- Fill in:
- Name — a label for the task
- Prompt — the instruction sent to Claude
- Schedule — interval (every N minutes/hours) or daily at a specific time and days of the week
- Tool Permissions — which tools Claude is allowed to use (see below)
- Working directory — where
claudeis invoked from (browse with the picker)
- Click Add
Tasks fire automatically on their schedule. To run one immediately, select it and click Run Now in the toolbar.
The detail pane shows:
- Live output — text streamed from Claude as it runs, with a live tool list updating as each tool is called
- Run history — every past run with its timestamp, duration, token usage, and status badge; click a row to expand the full output and tool list
A disabled task won't fire on its schedule. To toggle a task:
- Right-click a task in the sidebar → Enable / Disable
- Or select the task and click the ⚡ / ⚡̸ button in the toolbar
Disabled tasks show an off badge in the sidebar and a Disabled pill in the detail pane header.
Use the Search tasks bar at the top of the sidebar to filter by name or prompt text.
Click the ↑↓ icon in the sidebar header to sort by:
- Name — alphabetical
- Last Run — most recently run first
- Run Count — most runs first
- Tokens Used — most tokens consumed first
ScheduleAgent passes your credentials to the claude CLI automatically:
- Claude Code team plan (OAuth) — if you've run
claude loginin your terminal, the app finds your tokens in~/.claude/automatically. - API key — add your
ANTHROPIC_API_KEYin Settings (⌘,). It's stored in the macOS Keychain and injected into each subprocess.
If neither is configured, a warning banner appears in the main window.
Any text-generation task works without enabling tool permissions:
Summarize the 3 most important things I should know about my current project by reading README.md and any recent CHANGELOG entries. Be brief.
Set the working directory to your project root.
Tasks that read files, write files, or run shell commands need tool permissions configured. Example:
Look at the git log for the last 24 hours. Summarize what changed and append a dated entry to DAILY_LOG.md. If DAILY_LOG.md doesn't exist, create it.
For this prompt you'd allow Read, Write, and Bash.
MCP servers configured in your ~/.claude/settings.json are available to scheduled agents — the same environment you use interactively. Reference them in your prompt naturally:
Use the Linear MCP to find all open bugs assigned to me and write a summary to BUGS.md
Add the MCP tool names (e.g. mcp__linear__list_issues) in the Additional tools field in the task editor.
Each task has one of three permission modes, configured in the task editor:
| Mode | Behaviour | CLI flag |
|---|---|---|
| Ask every time | Claude pauses before any tool use. Will block unattended runs. | (none) |
| Allow specific tools | Auto-approve only the tools you check; Claude still asks for anything else. | --allowedTools Read,Write,… |
| Allow all tools | Never prompts. Claude can read, write, and run shell commands freely. | --dangerously-skip-permissions |
| Tool | What it does |
|---|---|
Read |
Read files |
Write |
Create or overwrite files |
Edit |
Edit existing files in place |
MultiEdit |
Multiple edits in one step |
Bash |
Run shell commands |
Glob |
Find files by pattern |
Grep |
Search file contents |
WebFetch |
Fetch a URL |
WebSearch |
Search the web |
TodoWrite |
Manage task lists |
For MCP tools, type their names in the Additional tools field (comma-separated), e.g. mcp__github__create_issue, mcp__linear__create_issue.
Hover over any tool checkbox in the editor for a description.
Every run records which tools were used and whether each was allowed or blocked. After a run completes, expand the row in the run history to see color-coded chips:
- Green — tool ran successfully
- Red — tool was denied
If a tool is blocked mid-run, the agent is terminated immediately and a critical notification fires: "⛔ Task blocked — Agent tried to use Bash, which isn't permitted. Run ended early."
Each completed run displays token usage (e.g. 12.4k tok) in the run history row alongside the duration. Input and output tokens are tracked separately and summed. Tasks can be sorted by total tokens used across all runs.
Two schedule types are supported:
Interval — fire every N minutes or hours:
- 5 min, 15 min, 30 min, 1h, 2h, 4h, 8h, 24h
Daily — fire at a specific wall-clock time on selected days of the week:
- Hour and minute picker + day-of-week toggles (Sun–Sat)
The scheduler checks for due tasks every 60 seconds, so daily tasks have at most a 1-minute drift.
The app continues scheduling while your screen is locked. For tasks to run across reboots, enable Launch at login in Settings (⌘,).
All data is stored in ~/Library/Application Support/ScheduleAgent/:
| File | Contents |
|---|---|
tasks.json |
Task definitions (name, prompt, schedule, permissions) |
runs.json |
Run history (up to 2,000 most recent runs) |
The Anthropic API key (if set) is stored in the macOS Keychain under anthropic-api-key.
Open Settings (⌘,) to:
- API Key — store your
ANTHROPIC_API_KEYin the Keychain (status dot confirms it's saved) - Launch at login — registers the app as a login item via
SMAppServiceso it starts automatically after reboot
Built with Swift/SwiftUI and Swift Package Manager (no Xcode project required).
Sources/ScheduleAgent/
├── App/
│ ├── ScheduleAgentApp.swift # @main — WindowGroup + Feed + Settings scenes
│ └── AppDelegate.swift # Activation policy, notification permission
├── Models/
│ ├── ScheduledTask.swift # Task model, ScheduleType, PermissionMode
│ └── RunRecord.swift # Run history, ToolUseRecord, RunStatus, token fields
├── Managers/
│ ├── TaskStore.swift # JSON persistence (tasks + runs)
│ ├── ScheduleEngine.swift # 60-second Timer, fires due tasks
│ ├── TaskRunner.swift # Spawns claude --print, parses stream-json
│ └── KeychainHelper.swift # Keychain CRUD for API key
└── Views/
├── TaskListView.swift # NavigationSplitView, sidebar with header
├── TaskDetailView.swift # Meta row, live output, tool chips, run history
├── TaskEditSheet.swift # Create/edit form, NSOpenPanel dir picker
├── SettingsView.swift # API key, launch at login
└── RunRecordRow.swift # Expandable run row with tool list + token count
TaskRunner spawns the claude binary as a subprocess:
claude --print "<prompt>" --no-session-persistence \
--output-format stream-json --verbose \
[--allowedTools Tool1,Tool2 | --dangerously-skip-permissions]
The subprocess always has HOME set explicitly so the CLI finds OAuth tokens in ~/.claude/ regardless of how the app was launched. If an API key is stored in the Keychain and ANTHROPIC_API_KEY isn't already in the environment, it is injected before launch.
The stream-json output is parsed line-by-line on a background serial queue:
assistantmessage events → extract text content for the live output displaytool_usecontent blocks → record which tool was requestedtoolresult events → determine if the tool succeeded or was blockedresultevent → final response text +usagefield for input/output token counts
If a permission block is detected (tool result with is_error: true and permission-related text, or an unresolved tool use at process exit), the process is terminated immediately and the run is marked blocked.
