10 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 17-telegram-scheduler | 05 | execute | 3 |
|
|
true |
|
|
Purpose: Makes Telegram bot and scheduled scanning accessible via CLI commands (SCHED-02). This is the final integration plan. Output: cmd/serve.go, cmd/schedule.go replacing stubs.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/17-telegram-scheduler/17-CONTEXT.md @.planning/phases/17-telegram-scheduler/17-01-SUMMARY.md @.planning/phases/17-telegram-scheduler/17-02-SUMMARY.md @.planning/phases/17-telegram-scheduler/17-03-SUMMARY.md @.planning/phases/17-telegram-scheduler/17-04-SUMMARY.md @cmd/root.go @cmd/stubs.go @cmd/scan.go From pkg/bot/bot.go: ```go type Config struct { Token string; AllowedChats []int64; DB *storage.DB ScanEngine *engine.Engine; ReconEngine *recon.Engine ProviderRegistry *providers.Registry; EncKey []byte } func New(cfg Config) (*Bot, error) func (b *Bot) Start(ctx context.Context) error func (b *Bot) Stop() func (b *Bot) NotifyNewFindings(result scheduler.JobResult) ```From pkg/scheduler/scheduler.go:
type Config struct {
DB *storage.DB
ScanFunc func(ctx context.Context, scanCommand string) (int, error)
OnComplete func(result JobResult)
}
func New(cfg Config) (*Scheduler, error)
func (s *Scheduler) Start(ctx context.Context) error
func (s *Scheduler) Stop() error
func (s *Scheduler) AddJob(name, cronExpr, scanCommand string, notifyTelegram bool) error
func (s *Scheduler) RemoveJob(name string) error
func (s *Scheduler) ListJobs() ([]storage.ScheduledJob, error)
func (s *Scheduler) RunJob(ctx context.Context, name string) (JobResult, error)
From cmd/root.go:
rootCmd.AddCommand(serveCmd) // currently from stubs.go
rootCmd.AddCommand(scheduleCmd) // currently from stubs.go
From cmd/scan.go (pattern to follow):
dbPath := viper.GetString("database.path")
db, err := storage.Open(dbPath)
reg, err := providers.NewRegistry()
eng := engine.NewEngine(reg)
serveCmd (replaces stub in stubs.go):
Use: "serve"
Short: "Start the KeyHunter server (Telegram bot, scheduler, web dashboard)"
Long: "Starts the KeyHunter server. Use --telegram to enable the Telegram bot."
Flags:
--telegram(bool, default false): Enable Telegram bot--port(int, default 8080): HTTP port for web dashboard (Phase 18, placeholder)
RunE logic:
-
Open DB (same pattern as cmd/scan.go — viper.GetString("database.path"), storage.Open)
-
Load encryption key (same loadOrCreateEncKey pattern from scan.go — extract to shared helper if not already)
-
Initialize providers.NewRegistry() and engine.NewEngine(reg)
-
Initialize recon.NewEngine() and register all sources (same as cmd/recon.go pattern)
-
Create scan function for scheduler:
scanFunc := func(ctx context.Context, scanCommand string) (int, error) { src := sources.NewFileSource(scanCommand, nil) ch, err := eng.Scan(ctx, src, engine.ScanConfig{Workers: runtime.NumCPU()*4}) // collect findings, save to DB, return count } -
If --telegram:
- Read token from viper:
viper.GetString("telegram.token")or envKEYHUNTER_TELEGRAM_TOKEN - If empty, return error "telegram.token not configured (set in ~/.keyhunter.yaml or KEYHUNTER_TELEGRAM_TOKEN env)"
- Read allowed chats:
viper.GetIntSlice("telegram.allowed_chats") - Create bot:
bot.New(bot.Config{Token, AllowedChats, DB, ScanEngine, ReconEngine, ProviderRegistry, EncKey}) - Create scheduler with OnComplete wired to bot.NotifyNewFindings:
sched := scheduler.New(scheduler.Config{ DB: db, ScanFunc: scanFunc, OnComplete: func(r scheduler.JobResult) { tgBot.NotifyNewFindings(r) }, }) - Start scheduler in goroutine
- Start bot (blocks on long polling)
- On SIGINT/SIGTERM: bot.Stop(), sched.Stop(), db.Close()
- Read token from viper:
-
If NOT --telegram (future web-only mode):
- Create scheduler without OnComplete (or with log-only callback)
- Start scheduler
- Print "Web dashboard not yet implemented (Phase 18). Scheduler running. Ctrl+C to stop."
- Block on signal
-
Signal handling: use
signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)for clean shutdown. -
Update cmd/stubs.go: Remove
serveCmdandscheduleCmdvariable declarations (they move to their own files). -
Update cmd/root.go: The AddCommand calls stay the same — they just resolve to the new files instead of stubs.go. Verify no compilation conflicts. cd /home/salva/Documents/apikey && go build ./cmd/... cmd/serve.go compiles.
keyhunter serve --helpshows --telegram and --port flags. Stubs removed.
scheduleCmd (replaces stub):
Use: "schedule"
Short: "Manage scheduled recurring scans"
Parent command with subcommands (no RunE on parent — shows help if called alone).
scheduleAddCmd:
Use: "add"
Short: "Add a new scheduled scan"
Flags:
--name(string, required): Job name--cron(string, required): Cron expression (e.g., "0 */6 * * *")--scan(string, required): Path to scan--notify(string, optional): Notification channel ("telegram" or empty)
RunE:
- Open DB
- Create scheduler.New with DB
- Call sched.AddJob(name, cron, scan, notify=="telegram")
- Print "Scheduled job '{name}' added. Cron: {cron}, Path: {scan}"
scheduleListCmd:
Use: "list"
Short: "List all scheduled scans"
RunE:
- Open DB
- List all jobs via db.ListScheduledJobs()
- Print table: Name | Cron | Path | Notify | Enabled | Last Run | Next Run
- Use lipgloss table formatting (same pattern as other list commands)
scheduleRemoveCmd:
Use: "remove [name]"
Short: "Remove a scheduled scan"
Args: cobra.ExactArgs(1)
RunE:
- Open DB
- Delete job by name
- If 0 rows affected: "No job named '{name}' found"
- Else: "Job '{name}' removed"
scheduleRunCmd:
Use: "run [name]"
Short: "Manually trigger a scheduled scan"
Args: cobra.ExactArgs(1)
RunE:
- Open DB, init engine (same as serve.go pattern)
- Create scheduler with scanFunc
- Call sched.RunJob(ctx, name)
- Print result: "Job '{name}' completed. Found {N} keys in {duration}."
Register subcommands: scheduleCmd.AddCommand(scheduleAddCmd, scheduleListCmd, scheduleRemoveCmd, scheduleRunCmd)
- Create cmd/schedule_test.go:
- TestScheduleAdd_MissingFlags: Run command without --name, verify error about required flag
- TestScheduleList_Empty: Open :memory: DB, list, verify no rows (test output format)
- Use the cobra command testing pattern from existing cmd/*_test.go files cd /home/salva/Documents/apikey && go build -o /dev/null . && go test ./cmd/... -v -count=1 -run "Schedule" schedule add/list/remove/run subcommands work. Full binary compiles. Tests pass.
<success_criteria>
keyhunter serve --telegramstarts bot+scheduler (requires token config)keyhunter schedule add --name=daily --cron="0 0 * * *" --scan=./repopersists jobkeyhunter schedule listshows jobs in table formatkeyhunter schedule remove dailydeletes jobkeyhunter schedule run dailytriggers manual scan- serve and schedule stubs fully replaced </success_criteria>