Skip to content

WIP: feat(cli): named credential profiles + --profile (#879 layer 2) - #1161

Closed
asjdf wants to merge 1 commit into
PerpetualSoftware:mainfrom
asjdf:feat/879-named-credential-profiles
Closed

WIP: feat(cli): named credential profiles + --profile (#879 layer 2)#1161
asjdf wants to merge 1 commit into
PerpetualSoftware:mainfrom
asjdf:feat/879-named-credential-profiles

Conversation

@asjdf

@asjdf asjdf commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Layer 2 of #879: named credential profiles in a v3 store, plus a stateless --profile / PAD_PROFILE selector.

This is a draft / WIP. asjdf will review it before it is ready. Please do not merge yet.

Part of #879 (layer 2 of 2). Layer 1 is #1160 (PAD_TOKEN env override) and is not included here — this branch is independent of #1160 so the two can land in either order. When #1160 merges, PAD_TOKEN should sit above the store lookup in NewClientFromURL (commented at that chokepoint).

Store

Bump ~/.pad/credentials.json to v3. Each server key holds a map of named profiles; default is the implicit profile so single-user v2 files migrate transparently (same probe-and-migrate pattern LoadStore() already uses for v1 → v2). Reads stay side-effect-free; Save writes v3.

{
  "version": 3,
  "credentials": {
    "https://app.getpad.dev": {
      "profiles": {
        "default": { "token": "...", "user_id": "..." },
        "cursor":  { "token": "...", "user_id": "..." }
      }
    }
  }
}

Selection (stateless, per invocation)

--profile persistent flag > PAD_PROFILE env > default

No persisted "current profile", no pad auth switch.

  • pad auth login --profile cursor / pad auth logout --profile cursor
  • whoami prints Profile: <name> only when the active profile is not default
  • Store API: GetProfile(url, name); Get(url) delegates to GetProfile(url, "default") so existing call sites stay
  • NewClientFromURL uses the resolved profile so every command that goes through the constructor picks the right token
  • init and server info use the same resolved profile (they short-circuit the store before the client, same trap xarmian called out for layer 1)

Single-profile users see zero behavior change.

How to test

go test ./internal/cli/ ./cmd/pad/ -count=1

TDD: profile tests were written first and failed on the v2 store (GetProfile undefined), then the v3 implementation went green. Existing ./internal/cli/ and ./cmd/pad/ suites stay green.

Manual:

  1. pad auth login — still writes profiles.default; pad auth whoami has no Profile line
  2. pad auth login --profile cursor — adds a sibling profile; default is untouched
  3. pad --profile cursor auth whoami / PAD_PROFILE=cursor pad auth whoami — reports the cursor identity and prints Profile: cursor
  4. pad auth logout --profile cursor — drops only cursor; default remains

Test plan

  • go test ./internal/cli/ ./cmd/pad/ -count=1
  • v2 file still loads (default profile) and is not rewritten until Save
  • --profile beats PAD_PROFILE beats default
  • whoami omits Profile on default, prints it otherwise
  • logout --profile does not wipe sibling profiles

Refs #879. Layer 1 is #1160.

…erpetualSoftware#879)

v2 stored one identity per server URL, so concurrent agents clobbered each other. v3 nests named profiles under each server, selected per invocation by --profile then PAD_PROFILE then default, with transparent v2 migration.
@asjdf

asjdf commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Closing this draft — we'll use PAD_TOKEN (#1160) for per-agent identity instead of named profiles.

@asjdf asjdf closed this Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant