diff --git a/.gitignore b/.gitignore index 23571f9..1a006cd 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ docker/workspace/* !docker/workspace/.gitkeep docker/.odek/* !docker/.odek/.gitkeep + +# Claude Code local artifacts +.claude/ diff --git a/README.md b/README.md index 489bcda..3821217 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,9 @@ Any OpenAI-compatible endpoint: Deepseek, OpenAI, Anthropic, Ollama, vLLM, Groq, ### πŸ€– Telegram Bot Run agent tasks directly from Telegram via long-polling. Supports slash commands (`/plan`, `/sessions`, `/resume`, `/prune`, `/help`, etc.), voice message transcription, photo analysis, conversation persistence across restarts, saved plan files, and daily token budgeting. No external Telegram libraries β€” built on stdlib `net/http`. [docs/TELEGRAM.md](docs/TELEGRAM.md) +### ⏰ Scheduled Tasks (native cron) +Run agent tasks on a cron schedule and deliver results to Telegram, stdout, or a log β€” no external cron daemon. The scheduler runs **in-process** (inside `odek telegram` or a standalone `odek schedule daemon`), so a scheduled task sees the same resolved config (API key, model, bot token) an interactive run does. Stdlib-only cron parser with Vixie day-of-month/day-of-week semantics, per-job timezones, missed-run catchup, and a singleton lock so jobs never double-fire. `odek schedule add --cron "0 9 * * 1-5" --deliver telegram "..."`. [docs/SCHEDULES.md](docs/SCHEDULES.md) + ### πŸ“Ž File Attachments Attach files to any prompt with `--ctx` / `-c` (CLI), `@filename` inline references (CLI + REPL + Web UI), or drag-and-drop (Web UI). File content is injected as context blocks before the task β€” no tool calls needed. Comma-separate multiple files: `--ctx main.go,lib.go`. [docs/CLI.md#file-attachments](docs/CLI.md#file-attachments) @@ -164,6 +167,7 @@ odek run "@README.md what does this project do?" | [Memory](docs/MEMORY.md) | Three-tier design, go-vector merge-on-write, `memory` tool | | [Sessions](docs/SESSIONS.md) | Multi-turn conversations, save/resume/trim/cleanup | | [Telegram Bot](docs/TELEGRAM.md) | Telegram integration: bot client, slash commands, session management, plans, media downloads | +| [Scheduled Tasks](docs/SCHEDULES.md) | Native in-process cron: `odek schedule`, Vixie cron syntax, delivery, missed-run catchup, daemon vs embedded | | [Sandboxing](docs/SANDBOXING.md) | Docker isolation model, config, security hardening | | [Security](docs/SECURITY.md) | Threat model, prompt injection defense, sandbox model | | [Sub-Agents](docs/SUBAGENTS.md) | Task decomposition, delegation tool, subagent protocol | diff --git a/cmd/odek/dispatch.go b/cmd/odek/dispatch.go index 12be9cf..95c9db2 100644 --- a/cmd/odek/dispatch.go +++ b/cmd/odek/dispatch.go @@ -51,6 +51,8 @@ func dispatch(args []string) int { return cliExit(mcpCmd(rest)) case "telegram": return cliExit(telegramCmd(rest)) + case "schedule": + return cliExit(scheduleCmd(rest)) default: fmt.Fprintf(os.Stderr, "odek: unknown command %q\n", cmd) printUsage() diff --git a/cmd/odek/main.go b/cmd/odek/main.go index 32155d8..d17f06e 100644 --- a/cmd/odek/main.go +++ b/cmd/odek/main.go @@ -476,6 +476,7 @@ func printUsage() { odek skill odek mcp [--sandbox] odek telegram + odek schedule odek version Commands: @@ -497,6 +498,10 @@ Commands: mcp Start MCP server (Model Context Protocol) over stdio Exposes all built-in tools for Claude Code, Cursor, etc. telegram Start Telegram bot (long-polling mode) + schedule Manage native in-process scheduled tasks (cron) + Subcommands: list, add, rm, enable, disable, run, next, daemon + The daemon (or the Telegram bot) fires jobs and delivers + results to stdout, a log, or a Telegram chat. init Create a config file (default: ./odek.json) version Print version and exit diff --git a/cmd/odek/schedule.go b/cmd/odek/schedule.go new file mode 100644 index 0000000..446349e --- /dev/null +++ b/cmd/odek/schedule.go @@ -0,0 +1,727 @@ +package main + +import ( + "context" + "flag" + "fmt" + "io" + "os" + "os/signal" + "path/filepath" + "strconv" + "strings" + "syscall" + "text/tabwriter" + "time" + + "github.com/BackendStack21/odek" + "github.com/BackendStack21/odek/internal/config" + "github.com/BackendStack21/odek/internal/loop" + "github.com/BackendStack21/odek/internal/render" + "github.com/BackendStack21/odek/internal/schedule" + "github.com/BackendStack21/odek/internal/telegram" +) + +// scheduleCmd is the entry point for "odek schedule" β€” management of native, +// in-process scheduled agent tasks (see internal/schedule). +func scheduleCmd(args []string) error { + if len(args) == 0 { + printScheduleUsage() + return nil + } + + // daemon and run resolve their own config; the rest only touch the store. + switch args[0] { + case "daemon": + return scheduleDaemon(args[1:]) + case "run": + return scheduleRunNow(args[1:]) + } + + st, err := schedule.NewStore() + if err != nil { + return err + } + switch args[0] { + case "list", "ls": + return scheduleList(st) + case "add": + return scheduleAdd(st, args[1:]) + case "rm", "remove", "delete": + return scheduleRemove(st, args[1:]) + case "enable": + return scheduleSetEnabled(st, args[1:], true) + case "disable": + return scheduleSetEnabled(st, args[1:], false) + case "next": + return scheduleNext(st, args[1:]) + default: + return fmt.Errorf("unknown schedule command %q (use list, add, rm, enable, disable, run, next, daemon)", args[0]) + } +} + +func printScheduleUsage() { + fmt.Println(`Usage: odek schedule + +Commands: + list List scheduled jobs (id, next fire, last status) + add --cron "" Add a job (see flags below) + rm Remove a job + enable Enable a job + disable Disable a job (kept, but never fires) + run Run a job once now and deliver (test it) + next Show the next few fire times + daemon Run the scheduler in the foreground + +Add flags: + --name