package cmd import ( "fmt" "log" "strings" "github.com/david/knox/internal/db" "github.com/david/knox/internal/ingest" "github.com/spf13/cobra" ) func NewBrowserCmd(kdb *db.KnoxDB) *cobra.Command { cmd := &cobra.Command{ Use: "browser", Short: "Ingest browser history from Chromium and Min into the knowledge index", Long: `Reads browser history databases (Chromium and Min), fingerprints URLs, and stores them as observations. Tracks what you've searched for and read. Run periodically to capture browsing exhaust.`, RunE: func(c *cobra.Command, args []string) error { ing := ingest.NewBrowserHistoryIngester() log.Printf("[knox] ingesting browser history...") results, err := ing.IngestAll() if err != nil { return fmt.Errorf("browser ingest: %w", err) } if len(results) == 0 { fmt.Println("No browser history found (Chromium/Min not detected or empty).") return nil } var newCount, dupCount int for _, result := range results { _, isNew, err := kdb.RecordObservation(db.ObservationRecord{ Fingerprint: result.Fingerprint, SourceID: result.SourceID, SourcePath: result.SourcePath, ContentType: result.ContentType, Title: result.Title, Summary: result.Summary, CreatedAt: result.CreatedAt, Confidence: result.Confidence, IngesterVersion: result.IngesterVersion, Trigger: "browser_ingest", Provenance: ingest.ProvenanceJSON(result.Provenance), }) if err != nil { log.Printf("[knox] db error: %v", err) continue } if isNew { newCount++ } else { dupCount++ } } fmt.Printf("Browser history: %d new, %d already seen\n", newCount, dupCount) // Show top domains domains := make(map[string]int) for _, r := range results { domains[extractDomain(r.Summary)]++ } fmt.Println("\nTop domains:") for _, d := range topDomains(domains, 10) { fmt.Printf(" %-40s %d\n", d.Name, d.Count) } return nil }, } return cmd } func topDomains(domains map[string]int, n int) []struct { Name string Count int } { var sorted []struct { Name string Count int } for name, count := range domains { sorted = append(sorted, struct { Name string Count int }{name, count}) } // Simple bubble sort for small n for i := 0; i < len(sorted)-1; i++ { for j := 0; j < len(sorted)-1-i; j++ { if sorted[j].Count < sorted[j+1].Count { sorted[j], sorted[j+1] = sorted[j+1], sorted[j] } } } if len(sorted) > n { sorted = sorted[:n] } return sorted } func extractDomain(url string) string { url = strings.TrimPrefix(url, "https://") url = strings.TrimPrefix(url, "http://") parts := strings.Split(url, "/") if len(parts) > 0 { return parts[0] } return "" }