Phoenix phoenix-cli-development
git clone https://github.com/Arize-ai/phoenix
T=$(mktemp -d) && git clone --depth=1 https://github.com/Arize-ai/phoenix "$T" && mkdir -p ~/.claude/skills && cp -r "$T/js/packages/phoenix-cli/.agents/skills/phoenix-cli-development" ~/.claude/skills/arize-ai-phoenix-phoenix-cli-development && rm -rf "$T"
js/packages/phoenix-cli/.agents/skills/phoenix-cli-development/SKILL.mdPhoenix CLI Design Specification
The Phoenix CLI (
px) is a command-line interface for the Phoenix AI observability platform. It serves two distinct audiences simultaneously: humans typing commands in a terminal and coding agents (Claude Code, Cursor, Codex, Gemini CLI) executing commands programmatically.
This specification uses RFC 2119 keywords (MUST, SHOULD, MAY, etc.) to indicate requirement strength.
Command Structure: Noun-Verb
All commands MUST follow a noun-verb pattern, modeled after the GitHub CLI (
gh):
px <resource> <action> [arguments] [options]
Resource names MUST be singular — they name the type of thing you're acting on, not how many:
px project list # not "px projects" px project create # not "px create-project" px trace get <trace-id> px dataset get <name-or-id> px auth status
Standard verbs
Commands SHOULD use these verbs consistently across all resources:
| Verb | Purpose | Takes argument? | Example |
|---|---|---|---|
| List/query multiple resources | No (uses flags) | |
| Fetch a single resource by ID | Yes (required) | |
| Create a new resource | Varies | |
| Modify an existing resource | Yes (required) | |
| Remove a resource | Yes (required) | |
Not every resource supports every verb — datasets MAY omit
create via CLI if the primary flow is through the SDK. Commands SHALL only add verbs that make sense for the resource.
Additional verbs for specialized actions are RECOMMENDED when the standard set doesn't cover it:
,px auth loginpx auth statuspx self updatepx docs fetchpx api graphql <query>
Backward compatibility during migration
The CLI is evolving from a flat structure (
px projects, px traces) toward full noun-verb. During the transition, both forms MAY coexist. When migrating an existing command:
- The new noun-verb form MUST be created as the primary command
- The old form SHOULD be kept as a hidden alias (Commander's
or a hidden command) so existing scripts don't break.alias() - Only the noun-verb form SHALL be documented going forward
Dual-Audience Design
The CLI MUST be equally usable by a person at a terminal and by a coding agent. Every command that outputs data MUST support
--format:
(default) — Human-readable tables and formattingpretty
— Indented JSON for human inspection of structured datajson
— Compact single-line JSON for piping intoraw
or agent consumptionjq
Commands MAY support additional formats (e.g.,
--format text for prompts). The default MUST always be pretty.
Progress indicators MUST write to stderr. Agents SHOULD pass
--no-progress to suppress them.
# Agent-friendly invocation px trace list --format raw --no-progress | jq '...'
Semantic exit codes
Defined in
src/exitCodes.ts. Commands MUST use the named constants and MUST NOT use bare numeric literals.
| Code | Constant | Meaning |
|---|---|---|
| 0 | | Command completed successfully |
| 1 | | Unspecified or unexpected error |
| 2 | | User cancelled (e.g., declined a confirmation) |
| 3 | | Bad CLI flags, missing required args, invalid input |
| 4 | | Not authenticated or insufficient permissions |
| 5 | | Failed to connect to server or network request |
Interactive default with non-interactive mode
Commands MAY prompt interactively when a required value is missing or a confirmation is needed — this is the human-friendly default. The
--no-input flag MUST suppress all prompts: both missing-value prompts and destructive-action confirmations. Non-interactive mode is also activated automatically when no TTY is attached (piped stdin).
In non-interactive mode, if a required value is missing, the command MUST exit immediately with
ExitCode.INVALID_ARGUMENT and print the correct invocation. If a confirmation would have been shown, the command MUST proceed as if confirmed:
# Human: missing --name triggers interactive prompt px project create # Agent: all inputs as flags, no prompts px project create --name my-project --format raw --no-input # Human: gets "Are you sure?" prompt px dataset delete my-dataset # Agent: skips confirmation px dataset delete my-dataset --no-input --format raw
Error: Missing required flag --name. px project create --name <project-name>
Idempotent commands
Commands SHOULD be idempotent where possible. Running the same command twice MUST NOT produce duplicate resources or unexpected errors:
commands SHOULD supportcreate
to return the existing resource instead of failing--if-not-exists
commands on a missing resource SHOULD exit withdelete
(not an error)ExitCode.SUCCESS
Return structured data on success
Mutating commands (
create, update, delete) MUST return the affected resource in the selected --format on stdout. Commands MUST NOT print bare success messages like "Project created." — output the resource so agents can extract IDs, URLs, and other fields:
$ px project create --name foo --format raw {"id":"proj_abc","name":"foo","createdAt":"2025-03-15T10:00:00Z"}
Fail fast with actionable errors
When a command fails due to invalid input, the error message MUST include the correct invocation syntax. Follow the pattern established by
getConfigErrorMessage() in src/config.ts:
- Show what was wrong
- Show the correct command to fix it
- Suggest a related command when helpful (e.g., "Available projects:
")px project list
When
--format raw or --format json is active, errors SHOULD also be written as structured JSON to stderr so agents can parse them. Use the StructuredError shape:
interface StructuredError { error: string; // Human-readable error message code: string; // ExitCode constant name (e.g., "INVALID_ARGUMENT") hint?: string; // Suggested command to resolve the issue }
{ "error": "Project not found", "code": "FAILURE", "hint": "px project list --format raw" }
Progressive help discovery
Every subcommand MUST include a
--help with at least one concrete example. Use Commander's .addHelpText('after', ...) to append an Examples: block:
$ px project create --help Usage: px project create [options] Create a new Phoenix project. Options: --name <name> Project name (required) --description <d> Project description --format <format> Output format (pretty|json|raw) (default: "pretty") -h, --help Display help Examples: px project create --name my-project px project create --name my-project --format raw
Agents discover capabilities incrementally:
px → px project → px project create --help. Each level MUST provide enough information to navigate deeper.
Adding a New Command
Options interface
Every handler MUST define a TypeScript interface for its options. Field names MUST be descriptive — abbreviations MUST NOT be used. Common options that appear across many commands:
interface CommonOptions { endpoint?: string; // --endpoint: Phoenix API endpoint override apiKey?: string; // --api-key: API key override project?: string; // --project: Project name or ID override format?: OutputFormat; // --format: Output format (pretty/json/raw) progress?: boolean; // --no-progress: Suppress progress indicators }
Commands that prompt for input or confirmation MUST support non-interactive mode:
interface InteractiveCommandOptions extends CommonOptions { noInput?: boolean; // --no-input: Suppress all prompts }
Configuration resolution
The CLI MUST resolve configuration from multiple sources. Use
resolveConfig() from src/config.ts for this merge logic. Priority:
- CLI flags (highest priority) —
,--endpoint
,--api-key--project - Environment variables —
,PHOENIX_HOST
,PHOENIX_API_KEYPHOENIX_PROJECT - Defaults —
for endpointhttp://localhost:6006
Command handlers MUST NOT read environment variables directly.
Output formatting
Each resource type SHOULD have formatting modules in
src/commands/format*.ts. When creating a new resource command, a corresponding formatter MUST be created following the existing pattern.
— Shared table rendering with terminal-width-aware column truncationformatTable.ts
,formatProjectsOutput()
, etc. — Resource-specific formattersformatTracesOutput()
Formatters MUST accept a
format option and return a string.
I/O functions
Commands MUST use the helpers from
src/io.ts:
writeOutput({ message }); // → stdout (data the user/agent wants) writeError({ message }); // → stderr (errors) writeProgress({ message, noProgress }); // → stderr (suppressible status updates)
console.log MUST NOT be used directly. The writeOutput/writeError split ensures stdout contains only data output, which is REQUIRED for piping correctness.
Naming Conventions
Code MUST follow these naming conventions (see also the phoenix-typescript skill):
- Functions and variables:
—camelCase
,createProjectCommandprojectListHandler - Types and interfaces:
—PascalCase
,ProjectListOptionsOutputFormat - Constants:
—SCREAMING_SNAKE_CASE
,ExitCode.SUCCESSCLI_VERSION - Files:
—camelCase.ts
,projects.tsformatProjects.ts - No abbreviations —
notprojectIdentifier
,projId
notannotationConfigannCfg
Testing
Tests use vitest and live in the
test/ directory, mirroring src/ structure.
pnpm test # run all tests pnpm test:watch # watch mode
When adding a command, tests MUST cover:
- The handler logic (mocking the Phoenix client)
- Formatter output for each format mode
- Edge cases: missing config, network errors, empty results
- Exit code correctness for error paths
Build and Run
pnpm build # TypeScript → build/ pnpm dev # Run from source via tsx (during development)
The CLI is published as
@arizeai/phoenix-cli with binary aliases px and phoenix-cli.
Global Options Placement
Global options (
--endpoint, --api-key) MUST be placed on the verb, not the noun. This is REQUIRED because Commander attaches options to the command that defines them:
# Correct — options on the verb px project list --endpoint http://my-server:6006 # Wrong — options on the noun (Commander won't parse these) px project --endpoint http://my-server:6006 list
Checklist for Adding a New Resource Command
- Create
withsrc/commands/<resource>.ts
exporting the nouncreate<Resource>Command() - Add verb subcommands (
,list
,get
, etc.) as neededcreate - Create
for output formattingsrc/commands/format<Resource>.ts - Define options interfaces with descriptive field names
- Use
for configuration,resolveConfig()
for API callscreatePhoenixClient() - Use
/writeOutput()
for I/O, semanticwriteError()
constants for errorsExitCode - Export from
src/commands/index.ts - Register in
viasrc/cli.tsprogram.addCommand() - Add tests in
test/ - Run
— fix any failures before proceedingpnpm test - Run
— fix any type errors before proceedingpnpm build - Update
with usage examples showing both human and agent-friendly invocationsREADME.md - Update the external Phoenix CLI skill at
when commands, flags, examples, or output shapes change.agents/skills/phoenix-cli/SKILL.md - Keep skill updates concise: document the new capability with the smallest useful examples and output-shape notes rather than repeating the full README
- If the command prompts for input or confirmation, support
(see--no-input
)InteractiveCommandOptions - Add at least one example to
via--help.addHelpText('after', ...) - Mutating commands return the affected resource on stdout (not just a success message)
- Run manually with
and--format raw
to verify agents get clean, parseable output--no-input