opencode-scheduled-commandsopencode plugin: run user-defined slash commands on a schedule (cron / interval). Zero-dependency, single-file, hot-reload config.
0
454
近 7 天 454
36.3
生态多维模型
5 天前
2026-08-15
快速安装与配置
opencode.json写入当前项目的 opencode.json,只对这个仓库生效。
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-scheduled-commands@1.3.0"]
}写入 ~/.config/opencode/opencode.json,对所有项目生效。
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-scheduled-commands@1.3.0"]
}若你要在本地改造这个插件,先装到项目里再从本地路径引用。
shell
pnpm add -D opencode-scheduled-commandsopencode 启动时会通过内嵌运行时自动加载 npm 依赖并缓存至本地目录,无需手动在全局环境执行安装。
Schedule opencode commands like cron—auto-fix bugs every 30 minutes, push code daily, run health checks on the hour, all unattended.
- Zero dependency, single file (~550 lines TypeScript), no
npm installneeded - Support for 5-field cron expressions and fixed intervals (
30s/5m/2h/1d) - Hot-reload within 5 seconds, modify
schedules.jsonwithout restarting opencode - Creates a new session for each run, with overlap prevention, timeout abort, error isolation, structured logs, and TUI notifications
- Configure
allowOverlap: trueto allow concurrent runs (starts new tasks even if previous one is still running)
Features
- Reads task list from
.opencode/schedules.json(JSONC format with comments and trailing commas) - Each task can be configured with 5-field cron expression or fixed interval (
every), executes when due - Calls user-defined commands (
.opencode/commands/*.md) via SDKsession.command, server automatically expands command templates and$ARGUMENTS - Each execution creates a new session (ID recorded in
.opencode/.scheduled-state.json), multiple executions don't share conversation context; session title includes start time - Overlap prevention: skips execution if task is already running; different tasks can run in parallel
- Optional timeout: automatically aborts session on timeout to prevent hanging during unattended operation; waits for previous session to fully stop before next execution
- Error isolation: individual task config errors/missing commands don't affect other tasks
- Structured logging: outputs as
scheduled-commandsservice - Optional TUI toast notifications (automatically ignored in web/headless environments)
Requirements
- opencode v1.18+ (uses
{ id, server }server plugin export format)
Installation
Method 1: npm install (recommended)
Since v1.3.0 the package ships a compiled JS entry (scheduled-commands.js), so you can load it
directly as a remote plugin in opencode.jsonc:
"plugin": ["opencode-scheduled-commands"]
Or, if you prefer to vendor the file into the project:
npm install opencode-scheduled-commands
cp node_modules/opencode-scheduled-commands/scheduled-commands.ts .opencode/plugins/
Method 2: Copy directly from repository
No npm package installation needed. Copy scheduled-commands.ts to your project's .opencode/plugins/ directory:
mkdir -p .opencode/plugins
cp scheduled-commands.ts .opencode/plugins/
Restart opencode to take effect (no restart needed for config changes). The plugin is auto-discovered and loaded on startup.
Note for v1.2.0 and earlier: the package entry was a TypeScript source file (
scheduled-commands.ts). opencode Desktop (Electron/Node) cannot import.tsfiles insidenode_modules(Stripping types is currently unsupported for files under node_modules), so"plugin": ["opencode-scheduled-commands"]failed silently on Desktop. If you use an old version, copy the file into.opencode/plugins/and let opencode auto-discover it. (opencode CLI, which runs on Bun, could always load the.tsentry directly.)
Quick Start
1. Define commands (what you want to schedule)
Create custom commands in .opencode/commands/, for example .opencode/commands/push.md:
---
description: Push code and tags (with safe sync check)
agent: build
---
1. **Check status**: `git status -sb` and `git ls-remote --tags origin`; if local has no ahead commits and all local tags are synced → prompt "nothing to push" and exit
2. **Sync upstream**: `git fetch origin`; if local is behind → `git pull --rebase` (run `git stash push -u` first if there are changes, `git stash pop` after success); conflict → stop and report
3. **Push code**: if upstream exists → `git push`; otherwise → `git push -u origin HEAD`
4. **Push tags**: `git push --tags`
5. **Verify**: `git status -sb` shows up-to-date; failure → report error, forbid `--force`
2. Configure scheduler (.opencode/schedules.json)
{
// Task list (JSONC, supports comments and trailing commas)
"jobs": [
{
"name": "Fix bugs every 30 minutes", // Task name (unique identifier)
"command": "bugfix", // Execute .opencode/commands/bugfix.md
"arguments": "", // Parameters passed to command ($ARGUMENTS / $1 ...)
"cron": "*/30 * * * *", // 5-field cron: min hour day month dow
// "every": "30m", // Or fixed interval: "30s", "5m", "2h", "1d"
"enabled": true, // Skip task if false
"notify": true, // Send TUI toast notification on completion
"timeoutMs": 600000 // Single execution timeout (ms), abort on timeout
}
]
}
See examples/schedules.example.json and the field table below for complete field documentation.
Configuration Fields
| Field | Type | Description |
|---|---|---|
name |
string | Task name, unique identifier, used for state recording and logging (defaults to command#index) |
command |
string | Required. Custom command name, corresponds to .opencode/commands/<command>.md |
arguments |
string | Parameters passed to command (corresponds to $ARGUMENTS / $1 / $2 ... in template) |
agent |
string | Override agent specified in command frontmatter (defaults to command's agent) |
model |
string | Override execution model (defaults to command/session default model) |
cron |
string | 5-field cron: min hour day month dow (choose one with every, cron takes priority) |
every |
string | Fixed interval: 30s, 5m, 2h, 1d (default unit is seconds) |
enabled |
boolean | Skip task if false (default true) |
notify |
boolean | Try to send TUI toast notification on completion |
timeoutMs |
number | Single execution timeout (ms), abort session and mark as timeout on expiry (0 = no limit) |
allowOverlap |
boolean | true allows overlap with previous execution: starts new task on schedule even if previous one is still running (default false) |
Scheduling Rules
cron (5 fields: min hour day month dow)
- Supports
*,*/nstep,a-brange,a,b,clist combinations - Day of week field
0and7both mean Sunday - vixie cron semantics: OR when both day fields are restricted, AND otherwise
- Examples:
*/30 * * * *every 30 minutes;0 9 * * 1-5weekdays at 9 AM;0 2 * * *daily at 2 AM - ⚠️
*/30in the minute field means "every 30 minutes"; in the hour field (* */30 * * *) it only matches at 0 o'clock
every (fixed interval)
- Units:
ms/s/m/h/d, default is seconds - Based on
lastRunprogression; missed triggers during opencode downtime only catch up once, not continuously
How It Works
- New session each run: Each execution creates a new session with title
[scheduled] <start time> <task name>(e.g.,[scheduled] 2026-08-10 14:30:00 Fix bugs every 30 mins #1), viewable in TUI session list; recent session ID is recorded in.opencode/.scheduled-state.json(for record only, not reused) - Overlap prevention: Skips execution if task is already running; different tasks can run in parallel. After timeout abort, also waits for previous session to fully stop before allowing next execution. Only one scheduler instance per directory (to avoid duplicate/overlap execution). For tasks that need concurrency, set
"allowOverlap": true—no longer waits for previous completion, starts new task on schedule (everyinterval calculated from last start time, cron follows wall clock, slow tasks won't delay subsequent triggers) - State file:
.opencode/.scheduled-state.jsonrecords each task's sessionId, last run time/status (ok/error/timeout/aborted/skipped)/count; delete file to reset all tasks - Hot reload: Re-reads config file every 5 seconds tick, modifying
schedules.jsontakes effect within 5 seconds - Logging: Outputs structured logs via
client.app.logasscheduled-commandsservice - Config isolation: Single Job field error → skip that Job; entire file parse failure → don't load this time, report error again after 5 minutes; auto-recovers when fixed
Important Notes
- Permissions: Scheduled tasks drive agent execution via SDK; if project permission is
ask, unattended operation may hang waiting for confirmation.建议给任务涉及的工具配置allow - Model costs: Scheduled tasks consume real model tokens; set frequency reasonably
- Missing commands: Execution returns error and records to state file, doesn't affect other tasks
- Plugin changes require restart; config changes don't require restart
Debugging & Troubleshooting
Jobs not running? Enable debug logging to see the scheduler's per-tick decisions:
SCHEDULED_COMMANDS_DEBUG=1 opencode
Debug mode (level: debug) outputs:
- Each tick: number of jobs loaded, currently running jobs, each job's next run time and skip reason (not due / still running / no schedule configured /
enabled: false) - Lock details: lock file path, PID that acquired it
- Missing config: explicit hint when
schedules.jsondoes not exist
Common issues:
- Stale lock file blocks the scheduler: a scheduler instance lock lives at
.opencode/.scheduled-lock(containing the owner PID). If the process crashed or was killed (Docker restart, Desktop relaunch, etc.), the stale lock is auto-detected by probing the owner PID and recovered — no manual cleanup needed. If the log says "Scheduler already running ... held by PID x" and that process is genuinely alive, delete the lock file and restart opencode - Read-only directory: the plugin needs to create the lock and state files under the project directory; read-only directories (sandbox/read-only mounts) fail startup, with a clear "Failed to start scheduler" error including permission hints
- Logs not visible: errors and warnings are mirrored to the console (stderr) as well as the opencode log service, so they are never silently dropped even if the SDK log channel is unavailable
Example Commands
Practical commands included in the repository:
- examples/commands/push.md — Push code and tags (with safe sync check)
- examples/commands/bugfix.md — Pull latest source, find random bug, fix carefully, run integration tests, commit (for auto-fix bugs every 30 minutes scenario)
Common Configuration Examples
{
"jobs": [
{ "name": "Push every 30 minutes", "command": "push", "every": "30m" },
{ "name": "Start at 9 AM weekdays", "command": "start", "cron": "0 9 * * 1-5" },
{ "name": "Health check every minute", "command": "check", "cron": "* * * * *", "timeoutMs": 120000 }
]
}
Testing
Uses Node.js built-in test runner (node:test, no dependency installation needed), runs TypeScript directly:
npm test
Coverage:
- Pure functions:
parseJsonc(comments/trailing commas/comments-in-strings),parseCron(fields/step/range/dow 0 & 7),nextCronTime(including vixie day/dow OR semantics),parseInterval,nextIntervalTime,errorMessage - Scheduler (
createScheduler+ mock SDK client):every/crontriggering, hot reload, interval catch-up, overlap prevention,allowOverlapconcurrency, timeout abort (including "don't allow next execution until previous session stops"), state file persistence, config error isolation