71a74e7166
Advances the guided-rotation goal without rotating anything. - internal/links: offline change-password URL generation — curated per-service table + RFC 8615 /.well-known/change-password + parent-domain fallback; derives a web host from a credential (git/netrc "user @ host", docker registry, aws) - internal/worklist: secrets-free rotation worklist (source, identity, auto/manual, link) + Markdown renderer - internal/tui: Bubble Tea walk-through (one credential per screen, open link, mark done/skip, progress) — READ-ONLY, enters/stores no secret - cmd: `incredigo worklist` (offline, stdout/--out) and `incredigo guide` (TUI) - tests: link derivation, worklist build/markdown + no-secret-column, TUI navigation/done/quit/view Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
66 lines
2.1 KiB
Go
66 lines
2.1 KiB
Go
// Package worklist turns discovered credentials into a rotation worklist: per
|
|
// credential, whether it can be auto-rotated (a driver exists) or is manual, and
|
|
// the page to rotate it on. It contains NO secrets — only non-secret metadata and
|
|
// links — so a worklist is safe to write to disk or print.
|
|
package worklist
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"incredigo/internal/discover"
|
|
"incredigo/internal/links"
|
|
"incredigo/internal/rotate"
|
|
)
|
|
|
|
// Item is one credential's rotation entry.
|
|
type Item struct {
|
|
Index int
|
|
Credential discover.Credential
|
|
Driver string // "" when no rotation driver handles it (manual)
|
|
Link links.Link
|
|
}
|
|
|
|
// Manual reports whether this credential has no automated rotation path today.
|
|
func (it Item) Manual() bool { return it.Driver == "" }
|
|
|
|
// Build classifies credentials (via the rotate registry) and attaches links.
|
|
// With no drivers registered every Item is Manual.
|
|
func Build(creds []discover.Credential) []Item {
|
|
plans := rotate.PlanAll(creds)
|
|
items := make([]Item, len(plans))
|
|
for i, p := range plans {
|
|
items[i] = Item{
|
|
Index: i + 1,
|
|
Credential: p.Credential,
|
|
Driver: p.Driver,
|
|
Link: links.LinkFor(p.Credential),
|
|
}
|
|
}
|
|
return items
|
|
}
|
|
|
|
// Markdown renders a worklist as a Markdown document (no secrets).
|
|
func Markdown(items []Item) string {
|
|
var b strings.Builder
|
|
b.WriteString("# incredigo rotation worklist\n\n")
|
|
fmt.Fprintf(&b, "%d credential(s). No secrets are included in this file.\n\n", len(items))
|
|
b.WriteString("| # | source | identity | rotation | change-password link |\n")
|
|
b.WriteString("|---|--------|----------|----------|----------------------|\n")
|
|
for _, it := range items {
|
|
rot := "manual"
|
|
if !it.Manual() {
|
|
rot = "auto: " + it.Driver
|
|
}
|
|
url := it.Link.URL
|
|
if url == "" {
|
|
url = "— (no web page)"
|
|
}
|
|
fmt.Fprintf(&b, "| %d | %s | %s | %s | %s |\n",
|
|
it.Index, it.Credential.Source, it.Credential.Identity, rot, url)
|
|
}
|
|
b.WriteString("\nGenerated by incredigo. Links are derived offline ")
|
|
b.WriteString("(curated table or RFC 8615 /.well-known/change-password).\n")
|
|
return b.String()
|
|
}
|