Skip to content

Add mass config for managing CLI profiles (SDK v0.3.3) - #252

Merged
chrisghill merged 1 commit into
mainfrom
add-mass-config-cmd
Sep 12, 2026
Merged

chrisghill merged 1 commit into
mainfrom
add-mass-config-cmd

Conversation

@chrisghill

Copy link
Copy Markdown
Member

Adds a mass config command group so users can manage ~/.config/massdriver/config.yaml through the CLI instead of hand-editing it: list, get, add, set, remove, use, and path. Adds a global --profile flag for per-command profile selection.

mass config add verifies credentials against the API before writing, so a mistyped key or organization fails at the point of entry rather than on some later unrelated command. Nothing is written when verification fails; --no-verify skips the check. API keys are masked in all output unless --show-secrets is passed, and the file is written atomically at 0600.

Writes go through the parsed yaml.Node tree rather than re-marshaling a struct, so comments, key order, and keys the CLI doesn't model all survive an edit. That matters because everyone using this today has a hand-annotated file.

Profile selection is entirely the SDK's

This depends on current_profile support added in massdriver-sdk-go v0.3.3, which also exports File, Profile, FilePath(), ReadFile(), Version, and DefaultProfileName. The CLI declares no schema, no file location, and no version of its own, and it does not resolve profiles or credentials. internal/configfile is a writer over the SDK's types; mass config use writes current_profile and the SDK reads it. Precedence — --profile, then MASSDRIVER_PROFILE, then current_profile, then default — lives entirely in Load.

Three contract tests hold the two in agreement: the SDK selects what use writes, the active-profile marker in list/get matches cfg.Profile across all four precedence levels, and the CLI refuses to edit a file the SDK can't read.

Because the SDK now treats current_profile as an explicit selection that fails with ErrProfileNotFound when it names nothing, use validates the profile exists before writing it and remove clears the key when it deletes the active profile. Without those, one typo or one removal would break every subsequent command.

--profile and the client refactor

--profile is passed to the SDK as massdriver.WithProfile via a new newMassdriverClient(cmd) helper that all 75 call sites now route through. An earlier revision set MASSDRIVER_PROFILE in PersistentPreRunE instead, which produced a misleading error — mass whoami --profile typo reported the name "was requested by MASSDRIVER_PROFILE" for a variable the user never set. Each layer now attributes itself correctly. This accounts for most of the diff in cmd/ (15 files, largely one-line changes); runBundleNew, runBundleList, and runRepositoryList gained a cmd *cobra.Command parameter.

Other notes

  • All logic lives in internal/commands/config (75.1% covered) with internal/configfile at 83.2%; cmd/config.go is flag reading and delegation, and cmd/ remains test-free.
  • Adds a CLAUDE.md recording the cmd/internal/commands layering, the SDK-owns-auth boundary, comment and test conventions, and that CI enforces committed generated docs.
  • The 101 modified files under docs/generated/ are the new --profile flag appearing in every command's inherited-options section.

Testing

make check passes. Verified end to end against a hand-annotated config: comments and unknown keys survive writes, precedence resolves to the right profile at each level, verification failure leaves no file behind, and removing the active profile leaves the SDK resolving cleanly.

  # Manage Configuration Profiles

  A profile holds the organization, credential, and API URL used to talk to
  Massdriver. Profiles live in ~/.config/massdriver/config.yaml (or
  $XDG_CONFIG_HOME/massdriver/config.yaml), and these commands read and write
  that file for you.

  Use separate profiles to work across organizations or against a self-hosted
  installation without re-editing the file each time.

  ## Choosing a profile

  A profile is selected in this order, highest precedence first:

  1. The --profile flag
  2. The MASSDRIVER_PROFILE environment variable
  3. The current_profile key in the config file, set by mass config use
  4. The profile named default

  The first three name a profile explicitly, so a name that isn't in the file
  is an error rather than a silent fallback. The default fallback may be
  absent — that is how credentials supplied entirely through environment
  variables resolve.

  ## Getting started

    mass config add default --org acme --api-key mds_xxx
    mass config list

Usage:
  mass config [command]

Aliases:
  config, profile

Available Commands:
  add         Add a profile
  get         Show a profile's settings (defaults to the active profile)
  list        List configured profiles
  path        Print the path to the configuration file
  remove      Remove a profile
  set         Update settings on an existing profile
  use         Set the active profile

Flags:
  -h, --help   help for config

Global Flags:
      --profile string   Configuration profile to use (overrides MASSDRIVER_PROFILE and the active profile)

Use "mass config [command] --help" for more information about a command. commands for managing config file
@chrisghill

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

Claude finished @chrisghill's task in 1m 38s —— View job


Reviewing PR #252

  • Gather context (PR body, changed files list)
  • Read CLAUDE.md and repo conventions
  • Read internal/configfile package
  • Read internal/commands/config package
  • Read cmd/config.go and cmd/client.go
  • Check SDK contract tests
  • Review cmd/*.go client refactor call sites (spot check)
  • Post final review

Review

This is a clean, well-scoped implementation. A few observations:

Strengths

  • The layering matches CLAUDE.md exactly: internal/configfile writes through the SDK's yaml.Node tree (preserving comments/unknown keys — verified in TestSetProfilePreservesComments), internal/commands/config holds all testable logic, cmd/config.go is pure flag-reading/delegation with no test file.
  • The --profile → newMassdriverClient(cmd) refactor (cmd/client.go) is applied consistently across all 75 call sites — I verified there are no leftover direct massdriver.NewClient() calls in cmd/.
  • Nice attention to failure-mode correctness: mass config use/add validate the profile exists before writing current_profile (internal/commands/config/use.go:14-17), and remove clears current_profile when the active profile is deleted (internal/configfile/config.go:194-198), matching the SDK's stricter ErrProfileNotFound behavior.
  • The three "contract" tests described in the PR body are present and do what's claimed: TestActiveProfileNameMatchesSDK, TestSDKHonorsCurrentProfileWeWrite, TestLoadRefusesFileTheSDKRejects (internal/configfile/config_test.go).
  • Secret masking (secret() in internal/commands/config/config.go:158-166) and atomic 0600 writes (internal/configfile/config.go:217-258) are both correct and tested (TestSaveIsOwnerOnly).
  • RunAdd auto-adopting the first profile as active (internal/commands/config/add.go:54) is a thoughtful UX touch that avoids a "profile not found" trap on first use.

Minor/nit-level notes (nothing blocking):

  • internal/commands/config/remove.go:39-41 — after removing the active profile, the message always says commands "will fall back to default," even if no default profile exists (or a different one is now the sole profile). This mirrors what ActiveProfileName/the SDK actually does, so it's accurate, but could momentarily read oddly if default isn't configured. Not worth blocking on.
  • internal/commands/config/config.go:172-180 (unknownProfileError) is a nice touch reusing the SDK's sentinel error, but it's worth double-checking downstream callers that may type-assert on sdkconfig.ErrProfileNotFound still get sensible errors.Is behavior through the %w wrap — this looks correct (fmt.Errorf wraps it), just flagging as something to keep in mind if the SDK changes its sentinel.
  • Docs/generated diff (101 files) is exactly the expected --profile flag addition to inherited options — consistent with make docs being run.

I was not able to execute go build/go test/make check in this sandboxed environment (commands required approval that wasn't available), so I relied on static reading of the code and the existing test suite rather than a live run. The PR description states make check passes; based on the code and tests I read, I have no reason to doubt that.

No blocking issues found. This looks ready to merge pending CI.

@chrisghill
chrisghill merged commit fb77178 into main Sep 12, 2026
7 checks passed
@chrisghill
chrisghill deleted the add-mass-config-cmd branch September 12, 2026 00:35
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.

3 participants