--- phase: 09-osint-infrastructure plan: 05 type: execute wave: 2 depends_on: ["09-01", "09-02", "09-03", "09-04"] files_modified: - cmd/recon.go - cmd/stubs.go - cmd/root.go autonomous: true requirements: [RECON-INFRA-08] must_haves: truths: - "`keyhunter recon full` runs Engine.SweepAll + Dedup and prints a masked findings table" - "`keyhunter recon list` prints the registered source names one per line" - "--stealth, --respect-robots (default true), --query flags exist on `recon full`" - "ExampleSource is registered at init() so Phase 9 ships a demonstrable pipeline" - "The stub reconCmd in cmd/stubs.go is removed; cmd/recon.go owns the command tree" artifacts: - path: "cmd/recon.go" provides: "reconCmd with subcommands `full` and `list`, flag wiring, source registration" contains: "var reconCmd" - path: "cmd/stubs.go" provides: "reconCmd stub removed; other stubs unchanged" key_links: - from: "cmd/recon.go" to: "pkg/recon.Engine" via: "NewEngine + Register(ExampleSource{}) + SweepAll" pattern: "recon\\.NewEngine" - from: "cmd/recon.go" to: "pkg/recon.Dedup" via: "Dedup applied to SweepAll results before printing" pattern: "recon\\.Dedup" --- Wire the recon package into the Cobra CLI with `keyhunter recon full` and `keyhunter recon list`. Remove the stub reconCmd from cmd/stubs.go. Register ExampleSource at init() so `recon full` produces visible output end-to-end on a fresh clone. Purpose: Satisfies RECON-INFRA-08 "Recon full command — parallel sweep across all sources with deduplication". Completes the phase's user-facing entrypoint. Output: cmd/recon.go (new), cmd/stubs.go (stub removed), cmd/root.go (registration unchanged or updated) @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/phases/09-osint-infrastructure/09-CONTEXT.md @cmd/stubs.go @.planning/phases/09-osint-infrastructure/09-01-SUMMARY.md @.planning/phases/09-osint-infrastructure/09-03-SUMMARY.md Task 1: Remove reconCmd stub from cmd/stubs.go cmd/stubs.go Delete the `var reconCmd = &cobra.Command{...}` block from cmd/stubs.go. Leave verifyCmd, serveCmd, scheduleCmd untouched. The real reconCmd will be declared in cmd/recon.go (Task 2). Verify cmd/root.go still references `reconCmd` — it will resolve to the new declaration in cmd/recon.go (same package `cmd`). cd /home/salva/Documents/apikey && ! grep -q 'var reconCmd' cmd/stubs.go cmd/stubs.go no longer declares reconCmd; file still compiles with other stubs. Task 2: Create cmd/recon.go with full and list subcommands cmd/recon.go Create cmd/recon.go declaring `var reconCmd` plus subcommands `reconFullCmd` and `reconListCmd`. Flag wiring: - `--stealth` bool, default false - `--respect-robots` bool, default true - `--query` string, default "" (empty -> sources use their own default keywords) ```go package cmd import ( "context" "fmt" "github.com/salvacybersec/keyhunter/pkg/recon" "github.com/spf13/cobra" ) var ( reconStealth bool reconRespectRobots bool reconQuery string ) var reconCmd = &cobra.Command{ Use: "recon", Short: "Run OSINT recon across internet sources", Long: "Run OSINT recon sweeps across registered sources. Phase 9 ships with an ExampleSource stub; real sources land in Phases 10-16.", } var reconFullCmd = &cobra.Command{ Use: "full", Short: "Sweep all enabled sources in parallel and deduplicate findings", RunE: func(cmd *cobra.Command, args []string) error { eng := buildReconEngine() cfg := recon.Config{ Stealth: reconStealth, RespectRobots: reconRespectRobots, Query: reconQuery, } ctx := context.Background() all, err := eng.SweepAll(ctx, cfg) if err != nil { return fmt.Errorf("recon sweep: %w", err) } deduped := recon.Dedup(all) fmt.Printf("recon: swept %d sources, %d findings (%d after dedup)\n", len(eng.List()), len(all), len(deduped)) for _, f := range deduped { fmt.Printf(" [%s] %s %s %s\n", f.SourceType, f.ProviderName, f.KeyMasked, f.Source) } return nil }, } var reconListCmd = &cobra.Command{ Use: "list", Short: "List registered recon sources", RunE: func(cmd *cobra.Command, args []string) error { eng := buildReconEngine() for _, name := range eng.List() { fmt.Println(name) } return nil }, } // buildReconEngine constructs the recon Engine with all sources registered. // Phase 9 ships ExampleSource only; Phases 10-16 will add real sources here // (or via a registration side-effect in their packages). func buildReconEngine() *recon.Engine { e := recon.NewEngine() e.Register(recon.ExampleSource{}) return e } func init() { reconFullCmd.Flags().BoolVar(&reconStealth, "stealth", false, "enable UA rotation and jitter delays") reconFullCmd.Flags().BoolVar(&reconRespectRobots, "respect-robots", true, "respect robots.txt for web-scraping sources") reconFullCmd.Flags().StringVar(&reconQuery, "query", "", "override query sent to each source") reconCmd.AddCommand(reconFullCmd) reconCmd.AddCommand(reconListCmd) } ``` Do NOT modify cmd/root.go unless `rootCmd.AddCommand(reconCmd)` is missing. (It currently exists because the stub was registered there.) cd /home/salva/Documents/apikey && go build ./... && go run . recon list | grep -q '^example$' `keyhunter recon list` prints "example". `keyhunter recon full` prints 2 findings from ExampleSource with "recon: swept 1 sources, 2 findings (2 after dedup)". - `go build ./...` succeeds - `go run . recon list` prints `example` - `go run . recon full` prints "recon: swept 1 sources, 2 findings (2 after dedup)" and two lines with [recon:example] - `go run . recon full --stealth --query=test` runs without error - reconCmd owned by cmd/recon.go, stub removed from cmd/stubs.go - `recon full` and `recon list` subcommands work end-to-end - Dedup wired through the SweepAll result - Flags --stealth, --respect-robots (default true), --query all parse After completion, create `.planning/phases/09-osint-infrastructure/09-05-SUMMARY.md`