Files
Evan JarrettandClaude Opus 4.8 8a556a5893 credhelper: key accounts by DID (v3 config) with lossless migration
The credential helper stored accounts keyed by handle, which broke on
handle renames and let the Docker `store` path (username+secret, no DID)
overwrite a good account with a DID-less one — how evan.jarrett.net on
buoy.cr ended up active with a blank DID.

Re-key everything by the stable DID, treating handle as a mutable display
label. DID is recovered client-side via standard AT-proto handle
resolution (DNS TXT _atproto.<handle> + HTTPS .well-known/atproto-did) —
no server change, no JWT, no auth, and no indigo pulled into the helper.

- resolve.go: stdlib handle->DID resolver
- config.go: v3 DID-keyed schema; find/activeAccount/upsert/rekey helpers;
  upsert never blanks a known DID; migrateV2toV3 re-keys existing files in
  place (no login lost; DID-less accounts stay provisional and self-heal)
- protocol.go: store resolves+preserves DID; get lazily backfills+re-keys;
  list reports the active account's handle (was arbitrary map iteration)
- status/switch/logout: display handle, key/compare by DID
- config_test.go: migration, upsert-never-blanks-DID, rekey, find, list,
  resolver parse helpers

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-02 13:38:45 -05:00

66 lines
1.3 KiB
Go

package credhelper
import (
"fmt"
"os"
"sort"
"github.com/spf13/cobra"
)
func newStatusCmd() *cobra.Command {
return &cobra.Command{
Use: "status",
Short: "Show all configured accounts",
RunE: runStatus,
}
}
func runStatus(cmd *cobra.Command, args []string) error {
sc, err := loadConfig()
if err != nil {
return fmt.Errorf("loading config: %w", err)
}
if len(sc.Registries) == 0 {
fmt.Fprintf(os.Stderr, "No accounts configured.\n")
fmt.Fprintf(os.Stderr, "Run: %s login\n", cfg.BinaryName)
return nil
}
// Sort registry URLs for stable output
var urls []string
for url := range sc.Registries {
urls = append(urls, url)
}
sort.Strings(urls)
for _, url := range urls {
reg := sc.Registries[url]
fmt.Printf("%s\n", url)
// Sort accounts by handle for stable output (the map is keyed by DID).
accts := make([]*Account, 0, len(reg.Accounts))
for _, acct := range reg.Accounts {
accts = append(accts, acct)
}
sort.Slice(accts, func(i, j int) bool { return accts[i].Handle < accts[j].Handle })
active := reg.activeAccount()
for _, acct := range accts {
marker := " "
if acct == active {
marker = "* "
}
did := ""
if acct.DID != "" {
did = fmt.Sprintf(" (%s)", acct.DID)
}
fmt.Printf(" %s%s%s\n", marker, acct.Handle, did)
}
fmt.Println()
}
return nil
}