Skip to content

Support profile aliases for shared Fizzy accounts - #201

Merged
robzolkos merged 10 commits into
masterfrom
fix/issue-200-profile-accounts
Jul 31, 2026
Merged

Support profile aliases for shared Fizzy accounts#201
robzolkos merged 10 commits into
masterfrom
fix/issue-200-profile-accounts

Conversation

@robzolkos

@robzolkos robzolkos commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Separate the selected profile name from the account slug/ID used for API routing.
  • Store credentials by profile alias while routing SDK requests through the profile's account.
  • Add fizzy auth login TOKEN --profile NAME --account ACCOUNT for creating an aliased profile.
  • Show profile and account separately in auth, config, quick-start, and doctor output.
  • Preserve profile-specific API URLs while switching, and clean up aliased credentials safely during logout.

Intent

Fixes #200.

The reported use case is two identities working in the same Fizzy account: for example, Walter's token under profile walter and an agent's token under walter2. 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 walter also made walter the account path, resulting in /walter/boards.json and a 404 when the actual account was 1. With this change:

profile walter  -> credential profile:walter  -> /1/boards.json
profile walter2 -> credential profile:walter2 -> /1/boards.json

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:

account = profile name

That preserves existing config.json files, 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:

fizzy auth login "$WALTER_TOKEN" --profile walter --account 1
fizzy auth login "$AGENT_TOKEN" --profile walter2 --account 1

This writes the Fizzy-specific account setting through the shared profile store:

"walter": {
  "base_url": "https://app.fizzy.do",
  "extra": { "account": "1" }
}

Users whose alias-scoped credentials are already saved can add extra.account directly 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

  • Began with a failing regression test reproducing the alias-as-account behavior.
  • Tests verify two aliases use distinct bearer tokens while both request /1/boards.json.
  • Tests cover old profile fallback, flag and environment alias creation, switch URL preservation, logout/restart behavior, logout-all legacy cleanup, doctor routing, and config reporting.
  • GOWORK=off make check passes, including vet, lint, race tests, and the CLI surface snapshot.
  • Performed a read-only live check with a temporary alias against a real Fizzy account: identity show and board list both 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 --account to fizzy auth login, clears stale alias routing during setup/signup, preserves self-hosted Base URLs, and reports the Account’s source accurately in config show/explain.

  • New Features

    • fizzy auth login TOKEN --profile NAME --account ACCOUNT creates an aliased profile; token saved as profile:NAME, requests route via ACCOUNT.
    • Stronger credential isolation and recovery: validate identifiers, create/update the profile before saving the token, roll back default/profile changes if save fails, fail fast when the profile store is unavailable, skip legacy token migration, clear stale alias routing during setup/signup, and avoid promoting legacy tokens during logout; logout --all also clears account-scoped legacy keys.
    • CLI shows Profile and Account separately in auth status, auth list (adds Account column), config show/explain (adds Account with precedence and precise source, e.g., “profile walter”), quick start, and doctor (routes checks via the account and validates access).
    • Profile switching preserves the target profile’s Base URL, applies it to the session, persists it to YAML, honors --api-url/FIZZY_API_URL, and keeps self-hosted URLs intact when profiles are reconstructed.
    • SDK routes aliases to /ACCOUNT/... using the alias’s token.
  • Migration

    • No action needed if a profile’s name equals the account slug/ID.
    • For aliased profiles that previously 404’d, re-save tokens with an account: fizzy auth login "$WALTER_TOKEN" --profile walter --account 1, fizzy auth login "$AGENT_TOKEN" --profile walter-agent --account 1.
    • Alternatively, add extra.account to the profile in config.json.

Written for commit aff4141. Summary will update on new commits.

Review in cubic

Copilot AI review requested due to automatic review settings July 31, 2026 16:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 --account and 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.

Comment thread internal/commands/root.go Outdated
Comment thread internal/commands/doctor_test.go
Copilot AI review requested due to automatic review settings July 31, 2026 16:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 31, 2026 17:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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=new and a stale local account, resolveProfile uses new as the fallback account, but config explain omits the environment candidate and marks the local value selected. Include the flag/environment profile fallbacks and select YAML candidates from eff.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

  • ensureProfile intentionally preserves an existing extra.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

  • ensureProfile preserves 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 with account so signup-created profiles always route to the account just created/selected.
	if err := ensureProfile(account, apiURL, ""); err != nil {

Copilot AI review requested due to automatic review settings July 31, 2026 17:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 resolveToken before authLoginCmd.RunE validates the profile name. If a legacy bare token exists, fizzy auth login NEW --profile walter.agent first migrates that old token to profile: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.yaml pointing at the previous deployment, so a downgraded CLI or a run without the profile store uses the wrong server. Capture p.BaseURL here 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()}

Copilot AI review requested due to automatic review settings July 31, 2026 17:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-url value with the target profile's saved URL. resolveProfile and the root pre-run preserve those overrides, but auth switch then applies and persists p.BaseURL instead. 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()}

Copilot AI review requested due to automatic review settings July 31, 2026 18:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.account value such as " 1 " therefore passes validation and routes requests through a whitespace-containing URL segment instead of account 1. 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. Consequently fizzy config show --verbose --json cannot 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),
		},

Comment thread internal/commands/auth.go
Copilot AI review requested due to automatic review settings July 31, 2026 19:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 31, 2026 19:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 show output attributes the routed account to the profile selector rather than to the profile metadata. For example, fizzy config show --verbose --profile walter reports account 1 as coming from flag --profile, even though the flag only selected walter and 1 came from that profile's extra.account. This makes the newly added precedence diagnostics inaccurate; resolve the profile context and use the same profileSourceLabel logic already used by config explain.
		"account": map[string]any{
			"value":  emptyToNil(eff.Account),
			"source": displayProfileSource(eff, defaultProfile),
		},

Copilot AI review requested due to automatic review settings July 31, 2026 19:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated no new comments.

@robzolkos
robzolkos merged commit 3834efe into master Jul 31, 2026
17 checks passed
@robzolkos
robzolkos deleted the fix/issue-200-profile-accounts branch July 31, 2026 19:55
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.

Issue with named profiles

2 participants