apollo-cli is the standalone Rust repository for the apollo command-line interface.
This repository currently covers the first Apollo CLI v0 slices:
- top-level v0 command routing
- global flag parsing
- output formatting foundation
- non-secret profile configuration
- runtime context resolution
- credential-store abstraction and auth commands
- conservative redaction foundation
- representative Apollo Portal OpenAPI calls under
/openapi/v1/* - raw OpenAPI passthrough through
apollo api - confirmation protection for mutating commands
- local developer workflow and CI entrypoints
The package is intentionally self-contained and movable. It lives in this standalone repository for review, but avoids repo-specific assumptions so the crate can be split, vendored, or republished without Apollo server-side changes.
It does not implement generated OpenAPI SDK bindings, agent sessions, MCP, or server-side schema changes in this slice.
authprofileappenvnamespaceconfigreleaseapi
Representative v0 commands:
apollo init
apollo profile add dev
apollo profile add prod --use
apollo app list
apollo app get sample-app
apollo env list --app sample-app
apollo namespace list --env DEV --app sample-app
apollo namespace get --env DEV --app sample-app application
apollo namespace create --env DEV --app sample-app application --comment "app settings" --yes
apollo config list --env DEV --app sample-app
apollo config get --env DEV --app sample-app timeout
apollo config set --env DEV --app sample-app timeout 3000 --type 1 --yes
apollo config delete --env DEV --app sample-app timeout --yes
apollo config diff --env DEV --app sample-app --target-env FAT
apollo config apply --env DEV --app sample-app --target-env FAT --yes
apollo release list --env DEV --app sample-app
apollo release create --env DEV --app sample-app --title "release title" --yes
apollo release rollback --env DEV 123 --yes
apollo api get /openapi/v1/apps
apollo api post /openapi/v1/apps --body '{"app":{"appId":"sample-app"}}' --yesThese commands use existing Apollo Portal OpenAPI endpoints only. Deprecated Portal WebAPI
endpoints are intentionally not used.
apollo namespace create registers the AppNamespace first and then creates the namespace in the
requested environment and cluster. It creates a private AppNamespace by default. Pass --public only
when the namespace should be public. File namespace formats are inferred from .json, .yml,
.yaml, and .xml suffixes; other names default to properties. --comment is stored on the
AppNamespace. Public AppNamespace registration sends Apollo's appendNamespacePrefix=true default;
pass --no-append-namespace-prefix when the namespace name must be stored without Apollo's public
namespace prefix behavior.
apollo config set sends Apollo's OpenItemDTO.type field. The default is 0 for a string item.
Apollo Portal also uses 1 for number, 2 for boolean, and 3 for JSON; these values are validated
client-side before the OpenAPI request is sent.
The current scaffold parses these global flags before subcommands:
--profile--server--output json|table--yes
--yes explicitly approves a mutating OpenAPI request without an interactive prompt. It does not
skip target-plan construction, validation, redaction, or operation reporting.
Use apollo init for first-time setup. It creates a profile, writes non-secret profile metadata to
config.toml, and can store an Apollo OpenAPI token through the credential-store abstraction.
For Apollo versions with Portal user access tokens, user-token auth is the recommended mode for
interactive users, AI agents, and personal automation.
For local Apollo assembly testing:
apollo --output json init --store-token-in-file
apollo profile show
apollo env list --app sample-appBy default, apollo init creates a local profile with:
server = "http://127.0.0.1:8070"- no persisted
outputunless--outputis passed auth_mode = "user-token"- no
operator; user-token OpenAPI requests use the token owner as the operator active_profile = "local"
Use apollo profile add to add more environments without hand-editing config:
printf '%s\n' "$DEV_TOKEN" | apollo \
--server https://apollo-dev.example.com \
--output json \
profile add dev \
--token-stdin
apollo profile add prod --server https://apollo-prod.example.com --use
apollo profile add legacy --server https://apollo-legacy.example.com --auth-mode consumer-token --operator aliceprofile add does not switch the active profile by default. Pass --use when the newly added
profile should become active. Existing profiles are protected from accidental replacement; pass
--overwrite to replace one intentionally.
The CLI stores non-secret profile metadata in config.toml under the OS config directory:
- macOS:
~/Library/Application Support/apollo/config.toml - Linux:
$XDG_CONFIG_HOME/apollo/config.tomlor~/.config/apollo/config.toml - Windows:
%APPDATA%\apollo\config.toml
Set APOLLO_CLI_HOME to an absolute directory to place config.toml and file-backed credentials
there instead. This is intended for isolated CI and smoke runs; normal interactive use should keep
the platform default.
The config file stores:
active_profile- profile name
- profile
server - profile
output - profile
auth_mode, eitheruser-tokenorconsumer-token - optional
operator - optional credential lookup metadata, such as backend/key names
Tokens are intentionally not part of the supported config schema. Prefer apollo init,
apollo profile add, and apollo auth login over editing this file by hand.
If auth_mode is missing, the CLI treats the profile as consumer-token for backward
compatibility with existing configs.
Example:
active_profile = "dev"
[profiles.dev]
server = "https://apollo-dev.example.com"
output = "table"
auth_mode = "user-token"
[profiles.dev.credential]
backend = "native"
key = "dev"apollo initapollo profile add [name]apollo profile listapollo profile showapollo profile use <name>
Runtime context resolution follows this order:
- explicit flags such as
--profile,--server,--output - environment variables such as
APOLLO_PROFILE,APOLLO_SERVER,APOLLO_OUTPUT - active profile config
- command defaults
apollo auth loginapollo auth login --token-stdinapollo auth login --token-stdin --store-token-in-fileapollo auth statusapollo auth whoamiapollo auth capabilitiesapollo auth logout
Credential storage uses an internal store abstraction with these logical providers:
native: default credential backend, implemented through the OS credential store via the Rustkeyringcrateenv: read-only CI/headless provider throughAPOLLO_TOKENfile: explicit fallback enabled only with--store-token-in-file- in-memory provider for unit tests
Native backend selection follows the OS behavior of the underlying credential store:
- macOS: Keychain Services
- Windows: Credential Manager
- Linux desktop: freedesktop Secret Service compatible providers
- Linux headless/CI: use
APOLLO_TOKENor explicitly opt into file fallback
File fallback writes token material outside config.toml under the CLI credentials directory and uses restrictive file permissions on Unix. The profile config stores only non-secret credential lookup metadata.
OpenAPI commands support two token modes:
user-token: recommended for interactive users, AI agents, and local automation. Tokens start withapollo_pat_and are sent asAuthorization: Bearer <token>.consumer-token: compatibility mode for existing integrations and older Apollo deployments. Tokens are sent as the rawAuthorization: <token>header value.
apollo init and apollo profile add default to user-token. Use --auth-mode consumer-token
when configuring legacy consumer-token credentials. Mutating commands require a configured
operator only in consumer-token mode; user-token requests use the owning Portal user.
apollo env list, apollo namespace list, apollo namespace get, apollo config get,
apollo config list, apollo config diff, and apollo config apply read env, namespace, or
configuration item data whose scope can be narrower than the app. Use user-token mode for these
commands. Legacy consumer-token mode cannot safely verify env/namespace-level read scope from the
available /openapi/v1/apps/authorized response, so the CLI rejects these scoped-read workflows
instead of relying on app-level visibility alone.
For local or CI use, APOLLO_TOKEN takes precedence and is never written to disk:
APOLLO_TOKEN="$TOKEN" apollo --server http://localhost:8070 app list --output jsonWhen APOLLO_TOKEN starts with apollo_pat_, the CLI automatically treats it as user-token;
otherwise it uses consumer-token compatibility mode.
apollo auth logout removes the credential referenced by the selected profile. It cannot remove
APOLLO_TOKEN from the parent shell environment. When APOLLO_TOKEN is still set, logout reports
that environment credentials will continue to apply; run unset APOLLO_TOKEN to disable that
temporary credential.
For interactive use, configure a profile and store the token with hidden input:
apollo --profile dev auth login
apollo --profile dev app listauth login stores the token in the OS credential store by default. If the native store is not
available in an interactive terminal, the CLI asks whether to use the local file fallback instead.
For scripts or manual paste-with-enter workflows, --token-stdin reads one token line:
printf '%s\n' "$TOKEN" | apollo --profile dev auth login --token-stdin
apollo --profile dev auth login --token-stdin
printf '%s\n' "$LEGACY_CONSUMER_TOKEN" | apollo --profile legacy auth login --auth-mode consumer-token --token-stdinUse user-token self-check commands after login:
apollo --profile dev auth whoami
apollo --profile dev auth capabilitiesThese commands call /openapi/v1/user-tokens/current and
/openapi/v1/user-tokens/current/capabilities. They require user-token auth mode and do not
create, rotate, or revoke tokens; user token creation remains a Portal self-service flow.
The output layer applies conservative redaction to human and JSON output before rendering. Token-like fields, Authorization: Bearer ... headers, and consumer token ... text are rendered as [REDACTED].
Structured JSON errors include:
code: stable error codecategory: stable categorymessage: human-readable message- optional non-sensitive details such as
command,profile,path, oroperation
Current error categories:
authentication_failedpermission_deniedinvalid_inputnot_foundconflictprecondition_failednetworkserverconfirmation_requiredunsupported_operation
Process exit statuses are stable at the following level:
0: success1: runtime or operation failure, including authentication, validation, network/server, and confirmation failures2: command-line parse or usage failure
Use the structured JSON error.code and error.category fields when automation needs a more
specific failure reason than the process exit status.
Before a built-in namespace, config, release, or raw API mutation, the CLI constructs a redacted operation plan from the selected profile/server and the command target. Plans include the fields available for that operation, such as app, env, cluster, namespace, config key/count, release IDs, or a sanitized raw OpenAPI method and path. Config values, request bodies, query values, tokens, and Authorization headers are not included.
In interactive table mode, a mutation without --yes writes the plan and a [y/N] prompt to
stderr. Only y or yes executes the request; n, no, blank input, or EOF rejects it. In
non-interactive mode and in JSON mode, mutations require --yes; otherwise the CLI returns a
confirmation_required error whose operation field contains the redacted plan. Rejection occurs
before any OpenAPI request is sent.
Namespace creation may perform read-only preflight requests after that initial approval. If Apollo resolves a different effective namespace name, such as adding an organization prefix to a public namespace, the CLI shows the resolved plan and requires approval again before either mutation is sent. If the selected profile, server, or output mode changes after approval, the CLI aborts before sending an OpenAPI request and asks the caller to review the new context.
With --yes, table mode still writes the plan before the request. A successful JSON response stays
one valid JSON document and preserves the existing top-level status and data fields while adding
the top-level operation plan.
config diff and config apply use a conservative merge contract. Source-only keys are created,
keys whose source value or comment differs are updated, matching keys are unchanged, and target-only
keys are preserved. An empty source is therefore a successful no-op, not a request to empty the
target. The CLI does not currently provide --prune; deletion requires a separate explicit config
deletion workflow. If a Portal version reports delete operations from the synchronize diff endpoint,
the CLI rejects the plan and does not call items/synchronize.
Both table and JSON output report the source and target scopes plus create, update, delete, and
unchanged counts. The JSON contract also reports strategy: "merge" and
targetOnlyBehavior: "preserve". It never includes config values in the diff result, apply plan, or
apply result.
A standalone config diff is advisory; it does not create a plan artifact for a later invocation.
config apply captures its own fully paginated source snapshot, assesses that exact snapshot through
items/diff, captures the fully paginated target state, and builds the detailed mutation plan from
the returned change set. After approval, it reads the target again and repeats the assessment with
the same captured source snapshot. If either the target item state or assessment changed, the
command returns stale_plan and sends no synchronize request. The initial approval intentionally
occurs before these preflight reads; the detailed approval covers the resulting change counts. If
all counts are zero, the command returns the deterministic data.result: "no-op" success response
without calling items/synchronize.
This stale-plan check is optimistic and best-effort. The current Apollo items/synchronize OpenAPI
contract has no target revision, ETag, or conditional-write precondition, so a target write after
the final check can still race with synchronization. Eliminating that window requires a
contract-first Apollo OpenAPI and server change that validates the target revision atomically with
the item update. Callers that require exclusive writes must coordinate them outside the current CLI
workflow.
The first v0 implementation uses a small generic HTTP client instead of a generated SDK. This keeps
the CLI independent from the Apollo server repository while still constraining all built-in resource
commands to /openapi/v1/*.
Path and payload mapping follows the current Apollo Portal OpenAPI contract, including:
GET /openapi/v1/appsGET /openapi/v1/envsGET /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespacesGET|PUT|DELETE /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/{key}POST /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/diffPOST /openapi/v1/apps/{appId}/appnamespacesfor AppNamespace registrationPOST /openapi/v1/namespacesPOST /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/synchronizeGET /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/activePOST /openapi/v1/envs/{env}/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releasesPUT /openapi/v1/envs/{env}/releases/{releaseId}/rollback
Mutating command confirmation and operation-plan behavior are described in Mutation safety.
Manual release runs publish prebuilt apollo executables on the
GitHub Releases page:
| Platform | Rust target | Archive |
|---|---|---|
| Linux x86-64 | x86_64-unknown-linux-gnu |
.tar.gz |
| Linux ARM64 | aarch64-unknown-linux-gnu |
.tar.gz |
| macOS Intel | x86_64-apple-darwin |
.tar.gz |
| macOS Apple silicon | aarch64-apple-darwin |
.tar.gz |
| Windows x86-64 | x86_64-pc-windows-msvc |
.zip |
Archive names follow apollo-<tag>-<target>.<extension>. Each archive contains the executable,
license, and English and Chinese READMEs. Releases also include SHA256SUMS and GitHub build
provenance attestations for every uploaded archive and checksum file.
For example, download and verify a release with the GitHub CLI:
version=v0.1.0
mkdir "apollo-${version}"
gh release download "${version}" \
--repo apolloconfig/apollo-cli \
--dir "apollo-${version}"
(cd "apollo-${version}" && sha256sum --check SHA256SUMS)
gh attestation verify \
"apollo-${version}/apollo-${version}-x86_64-unknown-linux-gnu.tar.gz" \
--repo apolloconfig/apollo-cliOn macOS, use shasum -a 256 -c SHA256SUMS for the checksum step.
For maintainers, first merge the intended package version and updated Cargo.lock into the default
branch. Then open Actions → Release → Run workflow, keep the default branch selected, and enter
the SemVer without a leading v. The same release can be started with the GitHub CLI:
gh workflow run release.yml \
--repo apolloconfig/apollo-cli \
--ref main \
-f version=0.1.0The workflow rejects malformed versions, versions that do not match Cargo.toml, non-default-branch
runs, and versions whose tag or Release already exists. It reruns formatting, Clippy, and tests;
builds and smoke-tests all five native targets; and creates checksums and attestations. Only after
those checks pass does it create the v<version> tag at the exact workflow commit, generate release
notes, verify the complete draft asset set, and publish the Release. Versions containing a SemVer
prerelease suffix are published as prereleases.
Before starting a release, maintainers must also run Actions → Apollo mutation smoke → Run
workflow on the intended default-branch commit and require a successful result. The workflow is
also scheduled weekly. It builds a pinned Apollo revision with Portal, ConfigService, AdminService,
and disposable H2 databases, then exercises the real mutation contract described below. The Apollo
pin is deliberately stored in scripts/mutation-smoke.sh; update it only as a reviewed source
change.
Build the CLI:
cargo buildRun the help output:
cargo run -- --helpRun tests:
cargo testRun focused OpenAPI command integration tests with the local mock HTTP server:
cargo test --test openapiRun the repeatable real-Portal mutation smoke from a clean checkout (requires Git, curl, jq, awk,
sha256sum or shasum, JDK 17, and the stable Rust toolchain):
./scripts/mutation-smoke.shThe command fetches the pinned Apollo revision, builds its single-process assembly, starts Portal +
H2, builds the CLI, creates an isolated APOLLO_CLI_HOME profile and disposable app/namespaces, and
validates config diff/apply, confirmation rejection, permission failure, release creation/listing,
and rollback against real server state. Target-only keys are verified as preserved under the merge
contract, and no-op state is compared before and after the command. User tokens and config values
stay in a mode-0700 temporary directory, failure diagnostics are dynamically redacted, and the
directory and assembly process are always removed. To reuse an already checked-out clean Apollo
tree at the same pinned commit:
APOLLO_SMOKE_APOLLO_SOURCE=/absolute/path/to/apollo ./scripts/mutation-smoke.shIf you have a local Apollo Portal running, you can also smoke-test against it:
APOLLO_TOKEN="$TOKEN" cargo run -- --server http://localhost:8070 --output json env list --app sample-app
APOLLO_TOKEN="$TOKEN" cargo run -- --server http://localhost:8070 --output json app list
APOLLO_TOKEN="$USER_TOKEN" cargo run -- --server http://localhost:8070 --output json auth whoamiFormat the repository:
cargo fmtLint the repository:
cargo clippy --all-targets --all-features -- -D warningssrc/cli.rs: CLI definition and flag parsingsrc/config.rs: profile config loading, saving, and context resolutionsrc/command.rs: top-level command routingsrc/credential.rs: credential-store abstraction and providerssrc/error.rs: structured CLI error modelsrc/http.rs: generic OpenAPI HTTP client and path helperssrc/output.rs: output rendering abstractionssrc/redaction.rs: conservative redaction utilitiestests/auth.rs: integration coverage for auth commands and credential behaviortests/cli.rs: integration coverage for help, flags, and structured errorstests/openapi.rs: integration coverage for OpenAPI paths, auth headers, and confirmation guardstests/profile.rs: integration coverage for profile commands and context resolutiontests/redaction.rs: integration coverage for redaction behaviorscripts/mutation-smoke.sh: pinned Apollo Portal + H2 mutation smoke and state assertions