Skip to content

project-personas: unified multi-account wrangling across CLI tools (gh, wrangler, …) #22

@LTSCommerce

Description

@LTSCommerce

Problem

This repo has a battle-tested multi-account pattern for GitHub:

  • github_accounts: {alias: username} map in localhost.yml
  • play-github-cli-multi.yml generates per-alias bash functions
    (gh-<alias>, git-<alias>, clone-<alias>, gh-token-<alias>, …)
  • Per-account OAuth scope audit + programmatic gh ssh-key add
  • Auto-detection in a git() wrapper via git-account-helper

That pattern landed via Plans 00034 and 00035 and is the canonical way
the daily-driver talks to multiple GitHub identities without crossing wires.

We now need the same shape on Cloudflare — multiple accounts, each
needing their own wrangler auth context, with wrangler-<alias>
aliases routing every invocation to the correct account. And the same
pain is coming for npm publish accounts, aws profiles, gcloud configs,
supabase, fly.io, etc.

Proposal

A top-level project_personas map in localhost.yml:

project_personas:
  <alias-a>:
    name: \"<Display Name A>\"
    tools:
      gh:
        username: <gh-username-a>
      wrangler:
        account_id: \"<cloudflare-account-id-a>\"
        account_name: \"<CF Display Name A>\"
  <alias-b>:
    name: \"<Display Name B>\"
    tools:
      gh:
        username: <gh-username-b>
      # no wrangler — this persona has no Cloudflare account

Each per-tool playbook reads that map directly, filters to personas
declaring its tool, and generates the bash function family in the
established `-` shape.

KISS migration: fail-fast, no compat shim

`play-github-cli-multi.yml` is updated to consume
`project_personas` directly. If it sees only the legacy
`github_accounts` map, it fail-fasts with a copy-pasteable
migration YAML — the user makes the one-line edit. No two
sources of truth, no silent derivation, no `set_fact`
gymnastics. The fail-fast message IS the migration guide.

Wrangler: explicit env-var injection + GNOME Keyring secrets

Wrangler is fundamentally unlike gh — it has no per-config-dir
OAuth, so the `-` model uses explicit env-var
export per invocation
:

function wrangler-<alias>() {
    local token
    if ! token=\$(secret-tool lookup persona <alias> tool wrangler attr api-token 2>/dev/null); then
        echo \"ERROR: no wrangler API token stored for persona '<alias>'.\" >&2
        echo \"Run: persona-setup.bash --set-token=<alias> wrangler\" >&2
        return 1
    fi
    CLOUDFLARE_API_TOKEN=\"\$token\" \\
    CLOUDFLARE_ACCOUNT_ID=\"<from project_personas>\" \\
        command wrangler \"\$@\"
}

Secret storage: tokens live in the GNOME Keyring via
`secret-tool` — encrypted at rest, unlocked at GNOME login (no
per-call password prompt), per-user isolation enforced by the
keyring daemon, never written to disk in plaintext, never on
the command line where `ps` could see them. Same mechanism
`gh` itself uses on this machine.

Alternatives considered and rejected: plaintext 0600 files
(backups grab plaintext), `pass`/GPG (extra dep), ansible-vault
(awkward at bash-function call time, `vault-pass.secret` on
disk anyway).

Decision gate

Phase 1 is a research gate. The remaining unknowns:

  1. Wrangler API token scopes — confirm minimum scopes for
    Workers / Pages / KV / R2 / D1.
  2. `secret-tool` availability — confirm libsecret is
    reliably present and the keyring is unlocked at function
    call time. Fallback to 0600 files if not.

Implementation phases are blocked on user approval after Phase 1.

Plan

Full plan with phases, tasks, technical decisions (incl.
Decision 4 on secret storage), and success criteria lives at
`CLAUDE/Plan/00045-project-personas-multi-tool-accounts/PLAN.md`
(committed once user approves).

Out of scope

  • npm / aws / gcloud / supabase / fly support (follow-up plans
    each using this plan's persona schema + keyring pattern)
  • Renaming SSH keys (`~/.ssh/github_` stays)
  • GUI/wizard (CLI-only, consistent with `run.bash`)
  • Touching `play-cloudflare-warp.yml` (that's the VPN client,
    unrelated to wrangler)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions