Files
knox/internal/cmd/browser.go
T

116 lines
2.8 KiB
Go

package cmd
import (
"fmt"
"log"
"strings"
"github.com/spf13/cobra"
"github.com/david/knox/internal/db"
"github.com/david/knox/internal/ingest"
)
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 ""
}