feat(01-05): add CLI root command, config package, output table, and settings helpers
- cmd/root.go: Cobra root with all 11 subcommands, viper config loading - cmd/stubs.go: 8 stub commands for future phases (verify, import, recon, keys, serve, dorks, hook, schedule) - cmd/scan.go: scan command wiring engine + storage + output with per-installation salt - cmd/providers.go: providers list/info/stats subcommands - cmd/config.go: config init/set/get subcommands - pkg/config/config.go: Config struct with Load() and defaults - pkg/output/table.go: lipgloss terminal table for PrintFindings - pkg/storage/settings.go: GetSetting/SetSetting for settings table CRUD
This commit is contained in:
68
pkg/output/table.go
Normal file
68
pkg/output/table.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/salvacybersec/keyhunter/pkg/engine"
|
||||
)
|
||||
|
||||
var (
|
||||
styleHigh = lipgloss.NewStyle().Foreground(lipgloss.Color("2")) // green
|
||||
styleMedium = lipgloss.NewStyle().Foreground(lipgloss.Color("3")) // yellow
|
||||
styleLow = lipgloss.NewStyle().Foreground(lipgloss.Color("1")) // red
|
||||
styleHeader = lipgloss.NewStyle().Bold(true).Underline(true)
|
||||
)
|
||||
|
||||
// PrintFindings writes findings as a colored terminal table to stdout.
|
||||
// If unmask is true, KeyValue is shown; otherwise KeyMasked is shown.
|
||||
func PrintFindings(findings []engine.Finding, unmask bool) {
|
||||
if len(findings) == 0 {
|
||||
fmt.Println("No API keys found.")
|
||||
return
|
||||
}
|
||||
|
||||
// Header
|
||||
fmt.Fprintf(os.Stdout, "%-20s %-40s %-10s %-30s %s\n",
|
||||
styleHeader.Render("PROVIDER"),
|
||||
styleHeader.Render("KEY"),
|
||||
styleHeader.Render("CONFIDENCE"),
|
||||
styleHeader.Render("SOURCE"),
|
||||
styleHeader.Render("LINE"),
|
||||
)
|
||||
fmt.Println(lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Render(
|
||||
"──────────────────────────────────────────────────────────────────────────────────────────────────────────",
|
||||
))
|
||||
|
||||
for _, f := range findings {
|
||||
keyDisplay := f.KeyMasked
|
||||
if unmask {
|
||||
keyDisplay = f.KeyValue
|
||||
}
|
||||
|
||||
confStyle := styleLow
|
||||
switch f.Confidence {
|
||||
case "high":
|
||||
confStyle = styleHigh
|
||||
case "medium":
|
||||
confStyle = styleMedium
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "%-20s %-40s %-10s %-30s %d\n",
|
||||
f.ProviderName,
|
||||
keyDisplay,
|
||||
confStyle.Render(f.Confidence),
|
||||
truncate(f.Source, 28),
|
||||
f.LineNumber,
|
||||
)
|
||||
}
|
||||
fmt.Printf("\n%d key(s) found.\n", len(findings))
|
||||
}
|
||||
|
||||
func truncate(s string, max int) string {
|
||||
if len(s) <= max {
|
||||
return s
|
||||
}
|
||||
return "..." + s[len(s)-max+3:]
|
||||
}
|
||||
Reference in New Issue
Block a user