Files
keyhunter/.planning/phases/17-telegram-scheduler/17-05-PLAN.md

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
17-01
17-02
17-03
17-04
cmd/serve.go
cmd/schedule.go
cmd/stubs.go
cmd/root.go
cmd/serve_test.go
cmd/schedule_test.go
true
SCHED-02
truths artifacts key_links
keyhunter serve --telegram starts bot + scheduler and blocks until signal
keyhunter schedule add creates a persistent cron job
keyhunter schedule list shows all jobs with cron, next run, last run
keyhunter schedule remove deletes a job by name
keyhunter schedule run triggers a job manually
serve and schedule stubs are replaced with real implementations
path provides exports
cmd/serve.go serve command with --telegram flag, bot+scheduler lifecycle
serveCmd
path provides exports
cmd/schedule.go schedule add/list/remove/run subcommands
scheduleCmd
from to via pattern
cmd/serve.go pkg/bot bot.New + bot.Start for Telegram mode bot.New|bot.Start
from to via pattern
cmd/serve.go pkg/scheduler scheduler.New + scheduler.Start scheduler.New|scheduler.Start
from to via pattern
cmd/schedule.go pkg/scheduler scheduler.AddJob/RemoveJob/ListJobs/RunJob scheduler.
from to via pattern
cmd/root.go cmd/serve.go rootCmd.AddCommand(serveCmd) replacing stub AddCommand.*serveCmd
Wire pkg/bot/ and pkg/scheduler/ into the CLI. Replace serve and schedule stubs in cmd/stubs.go with full implementations in cmd/serve.go and cmd/schedule.go.

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)
Task 1: Create cmd/serve.go with --telegram flag and bot+scheduler lifecycle cmd/serve.go, cmd/stubs.go, cmd/root.go 1. Create cmd/serve.go:

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:

  1. Open DB (same pattern as cmd/scan.go — viper.GetString("database.path"), storage.Open)

  2. Load encryption key (same loadOrCreateEncKey pattern from scan.go — extract to shared helper if not already)

  3. Initialize providers.NewRegistry() and engine.NewEngine(reg)

  4. Initialize recon.NewEngine() and register all sources (same as cmd/recon.go pattern)

  5. 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
    }
    
  6. If --telegram:

    • Read token from viper: viper.GetString("telegram.token") or env KEYHUNTER_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()
  7. 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
  8. Signal handling: use signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM) for clean shutdown.

  9. Update cmd/stubs.go: Remove serveCmd and scheduleCmd variable declarations (they move to their own files).

  10. 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 --help shows --telegram and --port flags. Stubs removed.

Task 2: Create cmd/schedule.go with add/list/remove/run subcommands cmd/schedule.go, cmd/schedule_test.go - Test 1: schedule add with valid flags creates job in DB - Test 2: schedule list with no jobs shows empty table - Test 3: schedule remove of nonexistent job returns error message 1. Create cmd/schedule.go:

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)

  1. 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.
- `go build -o /dev/null .` — full binary compiles with no stub conflicts - `go test ./cmd/... -v -run Schedule` passes - `./keyhunter serve --help` shows --telegram flag - `./keyhunter schedule --help` shows add/list/remove/run subcommands - No "not implemented" messages from serve or schedule commands

<success_criteria>

  • keyhunter serve --telegram starts bot+scheduler (requires token config)
  • keyhunter schedule add --name=daily --cron="0 0 * * *" --scan=./repo persists job
  • keyhunter schedule list shows jobs in table format
  • keyhunter schedule remove daily deletes job
  • keyhunter schedule run daily triggers manual scan
  • serve and schedule stubs fully replaced </success_criteria>
After completion, create `.planning/phases/17-telegram-scheduler/17-05-SUMMARY.md`