Skip to content

feat(cli): add named auth profile management#12529

Open
anticorrelator wants to merge 4 commits intomainfrom
dustin/cli-profiles
Open

feat(cli): add named auth profile management#12529
anticorrelator wants to merge 4 commits intomainfrom
dustin/cli-profiles

Conversation

@anticorrelator
Copy link
Copy Markdown
Contributor

@anticorrelator anticorrelator commented Apr 3, 2026

Resolves #12000
Resolves #12001

Summary

  • Adds persistent named authentication profiles to the Phoenix CLI, enabling multi-account and multi-host workflows without juggling environment variables
  • New commands: px auth profiles list|create|delete for profile CRUD, px auth switch <name> for switching the active profile
  • Refactors config resolution to a 4-tier merge chain (defaults → profile → env vars → CLI flags), fully backward-compatible
  • Profiles stored as JSON at ~/.px/profiles.json (or $XDG_CONFIG_HOME/phoenix/profiles.json when set)
  • auth status now displays the active profile name

Architecture

┌──────────────────────────────────────────────────────────────────┐
│  CLI Entry (cli.ts)                                              │
│  px auth profiles list|create|delete                             │
│  px auth switch <name>                                           │
│  px auth status                                                  │
└───────────────┬──────────────────────────────────────────────────┘
                │ resolveConfig()
                ▼
┌──────────────────────────────────────────────────────────────────┐
│  Config Resolution Pipeline (config.ts)                          │
│                                                                  │
│  Priority: CLI flags ──► env vars ──► active profile ──► defaults│
│                                        │                         │
│                          loadConfigFromProfile()                 │
│                                        │                         │
└────────────────────────────────────────┼─────────────────────────┘
                                         │ readFileSync
                                         ▼
┌──────────────────────────────────────────────────────────────────┐
│  Profile Storage (profiles.ts)                                   │
│                                                                  │
│  getConfigDir() ──► ~/.px/ (or $XDG_CONFIG_HOME/phoenix/)        │
│  loadProfiles() ──► profiles.json ──► { version, activeProfile,  │
│  saveProfiles()     (readFileSync)      profiles: Record<...> }  │
│  getActiveProfile()                                              │
└──────────────────────────────────────────────────────────────────┘

Implementation

Profile storage (profiles.ts): New module with types (ProfileEntry, ProfilesFile), config path resolution (~/.px default, $XDG_CONFIG_HOME/phoenix when set), file I/O (loadProfiles/saveProfiles), and pure validation helpers. loadProfiles supports strict mode (throws ProfilesFileError on corrupt files — used by mutation commands) and forgiving mode (returns empty state with stderr warning — used by config resolution so a corrupt file never blocks unrelated commands).

Config resolution (config.ts): loadConfigFromEnvironment() refactored to only return explicitly-set env vars (previously mixed in DEFAULT_PHOENIX_ENDPOINT unconditionally). New getBuiltInDefaults() and loadConfigFromProfile() functions. resolveConfig() now performs a 4-tier merge: { ...defaults, ...profile, ...envVars, ...cliFlags }.

Commands (auth.ts): Three-level nesting (auth profiles list|create|delete) is new for the CLI but structurally sound with Commander.js. auth switch is a direct peer of auth status — local-only, no network call. auth profiles create requires --endpoint; --api-key and --project are optional (profiles are partial by design, participating in the merge chain). API keys masked in pretty output, unmasked in JSON/raw for scripting.

Key design choices:

  • JSON over YAML — no new dependencies, consistent with existing PHOENIX_CLIENT_HEADERS parsing
  • PHOENIX_PROFILE env var follows the existing PHOENIX_PROJECT pattern; --profile scoped to auth commands only (other commands gain profile support transparently via resolveConfig())
  • Schema includes version: 1 field for future migration support
  • Profile file schema uses Record<string, ProfileEntry> keyed by name — enforces uniqueness without extra validation

Test plan

  • 310 vitest tests passing across 17 files
  • px auth profiles create staging --endpoint https://staging.example.com --api-key sk-xxx --set-default creates profile and sets as active
  • px auth profiles list shows profiles with masked API keys and * active indicator
  • px auth profiles list --format json outputs full (unmasked) JSON
  • px auth switch staging sets active profile with local confirmation
  • px auth status shows Profile: staging line
  • PHOENIX_HOST=http://override:6006 px auth status — env var overrides profile endpoint (backward compat)
  • Corrupt profiles.json does not block px auth status or non-auth commands (forgiving mode)
  • px auth profiles create / delete / switch error clearly on corrupt file (strict mode)

@anticorrelator anticorrelator requested a review from a team as a code owner April 3, 2026 03:37
@github-project-automation github-project-automation bot moved this to 📘 Todo in phoenix Apr 3, 2026
@mintlify
Copy link
Copy Markdown
Contributor

mintlify bot commented Apr 3, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
arize-phoenix 🟢 Ready View Preview Apr 3, 2026, 3:39 AM

@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Apr 3, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 3, 2026

Open in StackBlitz

@arizeai/phoenix-cli

npm i https://pkg.pr.new/@arizeai/phoenix-cli@12529

@arizeai/phoenix-client

npm i https://pkg.pr.new/@arizeai/phoenix-client@12529

@arizeai/phoenix-evals

npm i https://pkg.pr.new/@arizeai/phoenix-evals@12529

@arizeai/phoenix-mcp

npm i https://pkg.pr.new/@arizeai/phoenix-mcp@12529

@arizeai/phoenix-otel

npm i https://pkg.pr.new/@arizeai/phoenix-otel@12529

commit: 4c2ad3c

Comment thread js/packages/phoenix-cli/src/commands/auth.ts
Comment thread js/packages/phoenix-cli/src/profiles.ts Outdated
Comment thread js/packages/phoenix-cli/src/profiles.ts Outdated
Comment thread js/packages/phoenix-cli/src/commands/auth.ts Outdated
Comment thread js/packages/phoenix-cli/src/commands/auth.ts Outdated
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 3, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Comment thread js/packages/phoenix-config/src/env.ts Outdated
Comment thread js/packages/phoenix-cli/src/commands/formatProfiles.ts
Comment thread js/packages/phoenix-cli/src/commands/auth.ts Outdated
Comment thread js/packages/phoenix-cli/src/commands/auth.ts Outdated
Comment thread js/packages/phoenix-cli/src/config.ts Outdated
anticorrelator added a commit that referenced this pull request Apr 13, 2026
…tance errors

Addresses PR #12529 review feedback from yfrigui2:

- Remove --show-secrets from auth profile list/create/delete/switch.
  Output is always masked so agents invoking these commands never pull
  plaintext API keys into their context. Users needing the raw value can
  read ~/.px/profiles.json directly.
- Validate --endpoint is a parseable absolute URL at profile create time
  so typos surface immediately instead of silently creating a broken
  profile.
- Throw ProfileResolutionError when --profile or PHOENIX_PROFILE
  explicitly names a profile that does not resolve. Previously fell
  through to defaults, risking mutations against the wrong Phoenix
  instance. Forgiving fallback is preserved for the "no profile
  requested" path so unrelated commands aren't blocked.
- printBanner swallows resolution errors so `px` with no command never
  crashes from a stale PHOENIX_PROFILE; the actual command invocation
  still surfaces the error cleanly.
@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:XXL This PR changes 1000+ lines, ignoring generated files. labels Apr 13, 2026
@github-project-automation github-project-automation bot moved this from 📘 Todo to 👍 Approved in phoenix Apr 13, 2026
Adds persistent named authentication profiles to the Phoenix CLI,
enabling multi-account and multi-host workflows without juggling
environment variables.

- New commands: `px auth profile list|create|delete` for profile CRUD
  and `px auth switch <name>` to change the active profile.
- Config resolution now a 4-tier merge: CLI flags > env vars > active
  profile > built-in defaults. Fully backward-compatible.
- Profiles stored as JSON at `~/.px/profiles.json` (or
  `$XDG_CONFIG_HOME/px/profiles.json` when set). File is written
  `0o600` so API keys are never world-readable.
- Parsing/validation via Zod schemas (ProfileEntrySchema,
  ProfilesFileSchema) with strict and forgiving load modes.
- `auth status` now displays the active profile name.
- `--show-secrets` is intentionally absent from all profile commands
  so agents invoking these never pull plaintext API keys into their
  context; users needing the raw value can read the JSON file.
- `--endpoint` validated as an absolute URL at create time.
- Explicit `--profile` / `PHOENIX_PROFILE` that names a missing
  profile raises ProfileResolutionError (INVALID_ARGUMENT) so we
  never silently point commands at the wrong instance.
@mikeldking
Copy link
Copy Markdown
Collaborator

should be singular profile

@mikeldking
Copy link
Copy Markdown
Collaborator

use --current to set the current one

@mikeldking
Copy link
Copy Markdown
Collaborator

switch command is confusing - px profile use
add schema to the top of the JSON file
punt on credentials

Promotes profile commands to top-level `px profile {list,create,edit,use,show,delete}`
(dropping `auth profile`/`auth switch`), drops persisted `apiKey` in favor of
env-only `PHOENIX_API_KEY`, and adds a deny-by-default permission allowlist
(`resource.operation`) enforced via openapi-fetch middleware in
`createPhoenixClient`. `assertDeletesEnabled()` and
`PHOENIX_CLI_DANGEROUSLY_ENABLE_DELETES` are retired.

Adds a generated JSON Schema at `schemas/profile.schema.json` (emitted from the
zod source of truth via zod v4's `toJSONSchema`), a CI diff check workflow,
`$schema` support in the config file for editor autocompletion, and shipping
the schema in the npm `files` whitelist.
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels Apr 17, 2026
The package is ESM ("type": "module"), so the inline require() in
spawnEditorSync threw 'require is not defined' at runtime, making
`px profile edit` fail before the editor could open.
When permissions are not passed explicitly, the factory now resolves the
active profile (via --profile arg, PHOENIX_PROFILE env, or file.activeProfile)
and attaches the permission middleware with that profile's effective scope
list. With no active profile, no middleware is attached — users who have not
opted into the profile model keep their prior behavior.

This closes the last wiring gap so profiles that declare `permissions`
actually gate destructive calls across all command handlers, without
touching each of the 22+ createPhoenixClient call sites.

Also isolates delete.test.ts from the developer's real ~/.px/profiles.json
by pointing XDG_CONFIG_HOME at a temp dir per describe block.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

Status: 👍 Approved

Development

Successfully merging this pull request may close these issues.

[cli] px auth switch — Switch between stored profiles [cli] px auth profiles — Named profile management

3 participants