Skip to content

Refactor pc config to support key-based configuration and commands#90

Open
austin-denoble wants to merge 8 commits into
mainfrom
adenoble/improve-configuration-path
Open

Refactor pc config to support key-based configuration and commands#90
austin-denoble wants to merge 8 commits into
mainfrom
adenoble/improve-configuration-path

Conversation

@austin-denoble
Copy link
Copy Markdown
Collaborator

@austin-denoble austin-denoble commented May 28, 2026

Problem

The pc config command exposed configuration through a flat set of verb-noun subcommands — get-api-key, set-api-key, set-color, set-environment — with one Cobra command file per setting. The pattern had a few problems:

  • Adding a new setting required adding a new command file and registering it in two places (the config command tree and the root-level auth-skip list).
  • No way to view all current settings at once — no list equivalent.
  • No way to describe a setting (its purpose, valid values, sensitivity).
  • The naming diverged from how most modern CLIs (gh, aws, npm, stripe) handle config, raising the learning curve for new users.

This becomes more painful as the set of configurable values grows.

Solution

Refactor pc config to a generic, registry-driven interface modeled after gh config. A single keyDescriptor in internal/pkg/cli/command/config/registry.go holds each setting's getter, setter, clear function, optional onChange side-effect hook, short and long descriptions, sensitivity flag, and valid values. The get / set / unset / list / describe commands are generic and dispatch through the registry — adding a new config key is now a single registry entry, no new command file required.

New commands

$ pc config list
KEY           VALUE        DESCRIPTION
api-key       <not set>    Default API key for authenticating with Pinecone
environment   production   Pinecone environment to target (production or staging)
color         true         Enable or disable colored terminal output

$ pc config get api-key
[INFO] api-key: <not set>

$ pc config set api-key pcsk_...
[SUCCESS] api-key updated

$ pc config set environment staging
[SUCCESS] environment updated
[INFO] You have been logged out; to login again, run `pc login`
[INFO] To set a new API key, run `pc config set api-key <value>`

$ pc config unset api-key
[SUCCESS] api-key cleared

$ pc config describe environment
KEY            environment
VALUE          production
SENSITIVE      false
VALID VALUES   production, staging
DESCRIPTION    Pinecone environment to target (production or staging)

Select which Pinecone environment the CLI talks to. Most users should leave this set to 'production'; 'staging' is intended for Pinecone internal development.

Changing the environment clears your existing authentication state: any OAuth session is logged out, the default API key is cleared, and the target organization and project are reset. You will need to re-authenticate
and re-target after switching.

Registry-driven extensibility

Each setting is one entry in configRegistry. Adding a new key — say, a default output format — would look like:

"output-format": {
    Description: "Default output format for commands",
    ValidValues: []string{"text", "json"},
    getStr: func() string { return conf.OutputFormat.Get() },
    setStr: func(value string) error {
        switch value {
        case "text", "json":
            conf.OutputFormat.Set(value)
            return nil
        default:
            return fmt.Errorf("invalid value %q; must be one of: text, json", value)
        }
    },
    clearStr: func() { conf.OutputFormat.Clear() },
},

No new command file. No changes to root.go's skip-auth list. It just works across get, set, unset, list, and describe.

Side-effect hooks

The environment key needs to clear OAuth state, the API key, and the target org/project when it changes. That used to live inline in set-environment. It now lives in an onChange hook on the registry entry and fires consistently from both set and unset:

onChange: func(ctx context.Context, _, _ string) []string {
    // Logout OAuth, clear API key, clear target org/project,
    // return human-readable info lines for the user.
}

Flags

  • --json on get, list, describe for machine-readable output.
  • --reveal on get, list, describe to unmask sensitive values (defaults to masking head/tail for api-key).

Backwards compatibility

The four legacy commands (get-api-key, set-api-key, set-color, set-environment) are preserved as hidden, deprecated aliases. They continue to function and print Cobra's standard deprecation notice
pointing at the new equivalent:

$ pc config set-color true
Command "set-color" is deprecated, use 'pc config set color <true|false>' instead
[SUCCESS] Config property color updated to true

Existing scripts and docs keep working; new users land on the new pattern.

Type of Change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

The legacy commands are kept as hidden aliases, so this is non-breaking for existing users. Docs referencing pc config set-api-key etc. should be updated to use pc config set api-key <value> going forward.

Test Plan

  • just test-unit passes
  • pc config list — confirms all three keys render with descriptions
  • pc config get api-key — confirms <not set> placeholder for empty values
  • pc config get api-key --reveal — confirms full value shown when revealed
  • pc config set environment staging — confirms OAuth/API key/target wipe fires
  • pc config unset environment (from staging) — confirms same wipe fires (regression fix)
  • pc config set environment prod — confirms canonical "production" is stored and reported
  • pc config describe api-key / environment / color — confirms long description rendered when present, omitted when blank
  • Legacy pc config get-api-key / set-api-key / set-color / set-environment — confirms each still works and prints deprecation notice
  • --json on get, list, describe — confirms structured output

Note

Medium Risk
Environment onChange clears OAuth, API keys, and targets on set/unset—behavior change vs legacy paths for unset; API key handling remains sensitive but follows existing masking patterns.

Overview
Replaces per-setting pc config subcommands with generic get, set, unset, list, and describe that dispatch through a central configRegistry (keyDescriptor entries for api-key, color, environment). New keys are added via registry entries instead of new command files.

ConfigService backs the handlers with validation, ErrNoChange idempotency, and onChange hooks (environment changes now clear OAuth, API key, and target org/project on both set and unset). get / list / describe support --json and --reveal for sensitive values; set / unset support --json.

Legacy get-api-key, set-api-key, set-color, and set-environment remain as hidden, deprecated aliases; help text points users to pc config set api-key. root.go adds the new subcommands to the auth-skip list. Broad unit tests cover masking, JSON output, and registry behavior.

Reviewed by Cursor Bugbot for commit 8936497. Bugbot is set up for automated code reviews on this repo. Configure here.

…mands

Replaces the per-setting subcommands (get-api-key, set-api-key, set-color, set-environment) with a generic, registry-driven interface modeled after `gh config` and `aws configure`. A new keyDescriptor in registry.go holds each setting's getter, setter, validator, side-effect hook, short/long descriptions, and sensitivity flag. Adding a new key is supported through a registry entry rather than a command file.

Adds `pc config get|set|unset|list|describe <key>`, with --json and --reveal flag support for sensitive values like API keys. The four legacy commands are maintained as hidden, deprecated aliases to maintain functionality for existing users.
Comment thread internal/pkg/cli/command/config/unset.go Outdated
Comment thread internal/pkg/cli/command/config/set.go Outdated
`pc config unset` now invokes the onChange hook when clearing a key changes its value, fixing a bug where `unset environment` on staging silently resets to production without clearing the staging-tied OAuth session, API key, or target org/project.

`pc config set` now passes the canonical stored value to onChange via a post-set getStr() read, rather than the raw user input. Hooks like environment's now see "production" instead of "prod" when the input is normalized during setStr.
Extract business logic from cobra Run blocks into run* functions that accept a ConfigService interface, enabling unit testing without touching the real viper config store. Add --json output to set and unset commands for machine-readable use. Add a Hidden field to keyDescriptor to suppress internal keys (environment) from list output and tab completion while keeping them accessible by name.
Comment thread internal/pkg/cli/command/config/registry.go
Comment thread internal/pkg/cli/command/config/registry.go
Comment thread internal/pkg/cli/command/config/registry.go Outdated
Comment thread internal/pkg/cli/command/config/get.go
… onChange: add specific fields to keyDescriptor for validateStr, persistStr, and onChange. make sure config set|unset call validateStr, onChange, and then persistStr to avoid onChange failing after persistence has already occurred
Comment thread internal/pkg/cli/command/config/unset.go
case "production", "staging":
// canonical values
default:
return "", fmt.Errorf("invalid environment %q; must be one of: production, staging", value)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Environment prod alias dropped

Medium Severity

pc config set environment rejects prod, while the hidden legacy set-environment command still maps prod to production. The new registry path only accepts the literals production and staging, so shorthand that worked before fails on the primary command.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1a6a4f9. Configure here.

}
}
desc.persistStr(normalizedVal)
return lines, nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Environment side effects before persist

Medium Severity

For environment, Set and Unset run onChange (OAuth logout, API key and target clears with immediate disk writes) before persistStr updates the environment field. If the final environment write fails or the process exits afterward, auth and targets can be wiped while the stored environment never changes.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1a6a4f9. Configure here.

},
persistStr: func(value string) {
secrets.DefaultAPIKey.Set(value)
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Empty api-key allowed on set

Low Severity

The api-key registry entry has no validateStr, so pc config set api-key accepts an empty value and persists it. The deprecated set-api-key command rejects empty input, and the registry comment implies nil ValidValues means any non-empty string.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1a6a4f9. Configure here.

if lines, err = desc.onChange(ctx, oldVal, newVal); err != nil {
return nil, err
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Env binding retriggers environment hook

Medium Severity

environment uses conf.Environment.Get() for unset no-op checks and ErrNoChange, but that getter includes the bound PINECONE_ENVIRONMENT variable. When the env var overrides the file, Unset/Set toward production can keep seeing a non-default effective value, so the destructive onChange hook runs on every attempt even after the file already holds the target value.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1a6a4f9. Configure here.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 5 potential issues.

There are 9 total unresolved issues (including 4 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8936497. Configure here.

target organization and project are reset. You will need to re-authenticate
and re-target after switching.
`),
Hidden: true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

environment omitted from list

Medium Severity

The environment setting is marked Hidden, and List only includes visibleKeys(), so pc config list omits environment even though the PR describes listing all three settings including environment.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8936497. Configure here.

}
}
desc.persistStr(newVal)
return lines, nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

unset api-key skips auth state

Medium Severity

pc config unset api-key only clears the stored API key via persistStr and never reconciles state.AuthedUser, unlike pc auth clear --api-key, so the in-process auth context can stay on API-key mode after the key was removed.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8936497. Configure here.

token, err := oauth.Token(ctx)
if err != nil {
return nil, fmt.Errorf("error retrieving oauth token: %w", err)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Token error blocks env change

Medium Severity

The environment onChange hook requires oauth.Token to succeed before set or unset can finish, so a stale or broken OAuth record can prevent changing or resetting environment even when the user only relies on an API key.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8936497. Configure here.

}

if opts.json {
fmt.Fprintln(os.Stdout, text.IndentJSON(setOutput{Key: keyName, Value: value}))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JSON set shows raw value

Low Severity

With --json, pc config set prints the user-supplied value from the CLI args instead of the normalized value persisted by the registry (for example on vs true for color).

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8936497. Configure here.


if opts.json {
fmt.Fprintln(os.Stdout, text.IndentJSON(setOutput{Key: keyName, Value: value}))
return nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JSON hides auth side effects

High Severity

With --json, successful pc config set and pc config unset return only key metadata and exit before printing onChange lines, so environment changes that log out OAuth, clear the API key, and reset targets complete silently for JSON consumers.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8936497. Configure here.

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.

2 participants