Support profile aliases for shared Fizzy accounts - #201
Conversation
There was a problem hiding this comment.
Pull request overview
Adds profile aliases so distinct credentials can share one Fizzy account while preserving legacy profile behavior.
Changes:
- Separates credential profile names from API account routing.
- Adds
auth login --accountand updates auth/config/doctor output. - Adds documentation, tests, and CLI surface updates.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
SURFACE.txt |
Records the new login flag. |
skills/fizzy/SKILL.md |
Documents aliased profiles. |
README.md |
Adds alias configuration guidance. |
internal/config/config.go |
Updates precedence documentation. |
internal/commands/root.go |
Separates profile and account resolution. |
internal/commands/quickstart.go |
Reports profile and account separately. |
internal/commands/help.go |
Adds aliased-login examples. |
internal/commands/doctor.go |
Diagnoses aliases using routed accounts. |
internal/commands/doctor_test.go |
Updates multi-profile doctor coverage. |
internal/commands/config_cmd.go |
Exposes effective account configuration. |
internal/commands/config_cmd_test.go |
Tests account metadata reporting. |
internal/commands/columns.go |
Adds the account output column. |
internal/commands/auth.go |
Implements alias login, switching, and cleanup. |
internal/commands/auth_test.go |
Tests alias credentials and routing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (3)
internal/commands/config_cmd.go:176
- When no saved profile resolves, these predicates can report the wrong account precedence. For example, with
FIZZY_PROFILE=newand a stale localaccount,resolveProfileusesnewas the fallback account, butconfig explainomits the environment candidate and marks the local value selected. Include the flag/environment profile fallbacks and select YAML candidates fromeff.ProfileSource.
Candidates: []configExplainCandidate{
{Source: profileSourceLabel(resolvedProfile, eff.ProfileName), Value: unsetString(profileAccount(resolvedProfile, profileCfg)), Selected: resolvedProfile != ""},
{Source: "local config", Value: unsetString(fieldValue(localCfg, func(c *cfgpkg.Config) string { return c.Account })), Selected: resolvedProfile == "" && localCfg != nil && localCfg.Account != ""},
{Source: "global config", Value: unsetString(fieldValue(globalCfg, func(c *cfgpkg.Config) string { return c.Account })), Selected: resolvedProfile == "" && (localCfg == nil || localCfg.Account == "") && globalCfg != nil && globalCfg.Account != ""},
internal/commands/setup.go:277
ensureProfileintentionally preserves an existingextra.account. If the selected account slug matches a previously created alias that targets another account, setup saves the new credential/default under this slug but subsequent commands still route to the old aliased account. Setup-created profiles should explicitly associate the profile with the selected account, which also clears stale alias metadata when both names match.
if err := ensureProfile(selectedAccountSlug, apiURL, selectedBoardID); err != nil {
internal/commands/signup.go:826
ensureProfilepreserves existing account metadata, so signup can leave a same-named alias pointing at a different account even though the newly saved YAML and credential use the signup account. Explicitly associate this profile withaccountso signup-created profiles always route to the account just created/selected.
if err := ensureProfile(account, apiURL, ""); err != nil {
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Suppressed comments (2)
internal/commands/root.go:163
- The unknown-profile login path reaches
resolveTokenbeforeauthLoginCmd.RunEvalidates the profile name. If a legacy bare token exists,fizzy auth login NEW --profile walter.agentfirst migrates that old token toprofile:walter.agent, then rejects the invalid name and leaves the orphaned credential behind. Login does not need the previously stored token, so skip token resolution for this command (or validate the selector before any migration).
if cmd == authLoginCmd && newProfile != "" {
internal/commands/auth.go:374
- The YAML compatibility update still omits the target profile's Base URL. After switching from one deployment to another, current code updates the in-memory URL but leaves
config.yamlpointing at the previous deployment, so a downgraded CLI or a run without the profile store uses the wrong server. Capturep.BaseURLhere and save it alongside the account and board.
// Update YAML config for backward compatibility.
globalCfg := config.LoadGlobal()
globalCfg.Account = profileAccountID
globalCfg.Board = profileBoard
if err := globalCfg.Save(); err != nil {
return &output.Error{Code: output.CodeAPI, Message: err.Error()}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Suppressed comments (2)
internal/commands/auth.go:369
- This overwrites the higher-precedence
FIZZY_API_URL/--api-urlvalue with the target profile's saved URL.resolveProfileand the root pre-run preserve those overrides, butauth switchthen applies and persistsp.BaseURLinstead. Only select the profile URL when neither override is present.
if p.BaseURL != "" {
profileAPIURL = p.BaseURL
}
internal/commands/auth.go:56
- The profile metadata and default selection have already been committed when this credential write can fail. For an existing alias whose account is being changed, a failed token replacement leaves the old token routed to the new account; for a new alias, it leaves a default profile with no credential. Restore the previous profile/default state when saving the credential fails (or make these updates transactional) before returning the error.
if err := credsSaveProfileToken(profileName, token); err != nil {
return &output.Error{Code: output.CodeAPI, Message: err.Error()}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
internal/commands/root.go:1261
- The condition validates the trimmed account, but the function returns the untrimmed value. An
extra.accountvalue such as" 1 "therefore passes validation and routes requests through a whitespace-containing URL segment instead of account1. Return the trimmed value used by the check.
if json.Unmarshal(raw, &account) == nil && strings.TrimSpace(account) != "" {
return account
internal/commands/config_cmd.go:113
- In verbose output every other effective configuration field includes its source, but the new account object only contains
value. Consequentlyfizzy config show --verbose --jsoncannot identify how the routed account was selected. Include the account source just as the profile/API URL/board fields do.
"account": map[string]any{
"value": emptyToNil(eff.Account),
},
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.
Suppressed comments (1)
internal/commands/config_cmd.go:114
- The verbose
config showoutput attributes the routed account to the profile selector rather than to the profile metadata. For example,fizzy config show --verbose --profile walterreports account1as coming fromflag --profile, even though the flag only selectedwalterand1came from that profile'sextra.account. This makes the newly added precedence diagnostics inaccurate; resolve the profile context and use the sameprofileSourceLabellogic already used byconfig explain.
"account": map[string]any{
"value": emptyToNil(eff.Account),
"source": displayProfileSource(eff, defaultProfile),
},
Summary
fizzy auth login TOKEN --profile NAME --account ACCOUNTfor creating an aliased profile.Intent
Fixes #200.
The reported use case is two identities working in the same Fizzy account: for example, Walter's token under profile
walterand an agent's token underwalter2. Keeping those credentials separate lets Fizzy attribute cards and other actions to the correct user, while both profiles still target the same account URL.Before this change, selecting
walteralso madewalterthe account path, resulting in/walter/boards.jsonand a 404 when the actual account was1. With this change:Migration and backward compatibility
Existing profiles do not require migration when their profile name is already the account slug or ID. A profile without account metadata continues to resolve as:
That preserves existing
config.jsonfiles, credential keys, setup/signup behavior, and account-scoped URLs.Profiles that currently use an arbitrary alias and therefore hit the reported 404 need a one-time account association. The supported path is to save each token again with the account:
This writes the Fizzy-specific account setting through the shared profile store:
Users whose alias-scoped credentials are already saved can add
extra.accountdirectly without replacing their token.There is intentionally no automatic migration for broken aliases: the old profile file does not contain enough information to infer the correct account safely, and guessing could associate the wrong user's credential with an account. New profiles created without an alias continue using the account slug as their name and need no additional metadata.
Validation
/1/boards.json.GOWORK=off make checkpasses, including vet, lint, race tests, and the CLI surface snapshot.identity showandboard listboth succeeded through the aliased profile. The temporary alias was then removed and the original configuration restored.Summary by cubic
Add profile aliases so multiple identities can share one Fizzy account by separating “profile” from “account” across the CLI. Fixes 404s from alias-as-account routing, adds
--accounttofizzy auth login, clears stale alias routing during setup/signup, preserves self-hosted Base URLs, and reports the Account’s source accurately inconfig show/explain.New Features
fizzy auth login TOKEN --profile NAME --account ACCOUNTcreates an aliased profile; token saved asprofile:NAME, requests route viaACCOUNT.setup/signup, and avoid promoting legacy tokens duringlogout;logout --allalso clears account-scoped legacy keys.auth status,auth list(adds Account column),config show/explain(adds Account with precedence and precise source, e.g., “profile walter”), quick start, anddoctor(routes checks via the account and validates access).--api-url/FIZZY_API_URL, and keeps self-hosted URLs intact when profiles are reconstructed./ACCOUNT/...using the alias’s token.Migration
fizzy auth login "$WALTER_TOKEN" --profile walter --account 1,fizzy auth login "$AGENT_TOKEN" --profile walter-agent --account 1.extra.accountto the profile inconfig.json.Written for commit aff4141. Summary will update on new commits.