Skip to content

feat: implement Apollo CLI OpenAPI v0#1

Open
nobodyiam wants to merge 18 commits into
mainfrom
codex/apollo-cli-v0-5628
Open

feat: implement Apollo CLI OpenAPI v0#1
nobodyiam wants to merge 18 commits into
mainfrom
codex/apollo-cli-v0-5628

Conversation

@nobodyiam

@nobodyiam nobodyiam commented Jun 7, 2026

Copy link
Copy Markdown
Member

Summary

Refs apolloconfig/apollo#5624.
Refs apolloconfig/apollo#5628.
Refs apolloconfig/apollo#5629.
Refs apolloconfig/apollo#5630.
Refs apolloconfig/apollo#5631.
Refs apolloconfig/apollo#5625.
Refs apolloconfig/apollo#5626.

This PR initializes the standalone Rust apollo CLI repository and implements the first v0 OpenAPI-oriented slice:

  • top-level command routing for init, auth, profile, app, env, namespace, config, release, and api
  • guided first-time setup through apollo init
  • guided additional profile setup through apollo profile add [name], with --use, --overwrite, --token-stdin, and explicit file credential fallback support
  • global flags for --profile, --server, --output json|table, and --yes
  • profile config loading, context resolution, and profile list/show/use
  • credential-store abstraction with native, env, explicit file fallback, and in-memory test providers
  • Consumer token auth through the existing OpenAPI Authorization header model
  • representative /openapi/v1/* commands for app/env/namespace/config/release workflows
  • raw OpenAPI passthrough via apollo api <method> <path> [--body ...]
  • structured JSON/table output, structured HTTP errors, and conservative redaction
  • confirmation protection for mutating commands before opening a network connection
  • Apollo assembly compatibility for required pagination on config list and release list
  • config set fallback from update to create when Apollo reports a missing item
  • local developer docs and CI for fmt, clippy, and tests

Scope Notes

The CLI is intentionally standalone and does not add code to apolloconfig/apollo.

This PR uses existing Apollo Portal OpenAPI endpoints only. It does not use deprecated Portal WebAPI endpoints for CLI runtime behavior and does not implement server-side schema changes, generated OpenAPI SDK bindings, MCP, or agent sessions. Local integration setup used Portal session endpoints only to create a temporary test app and Consumer token for smoke validation.

Validation

  • cargo fmt --check
  • cargo test
  • cargo test --test openapi
  • cargo clippy --all-targets --all-features -- -D warnings
  • git diff --check
  • cargo run -- --help
  • cargo run -- namespace create --help
  • cargo run -- config apply --help
  • temporary-home manual parse smoke for apollo profile add prod --server ... --operator ... --use --output json
  • ./mvnw -B -pl apollo-assembly -am -DskipTests package in a local Apollo checkout
  • Apollo assembly smoke against http://127.0.0.1:8070 using github,database-discovery,auth profiles and H2, covering env list, app list, namespace list, config list, config set, config get, config diff, release create, release list, and api get /openapi/v1/envs

The local pre-push hook also completed and reported no data security issues. The latest push emitted a non-blocking FaaS rule fetch warning before completing the scan.

Summary by CodeRabbit

  • New Features

    • Full Apollo CLI with init, auth, profile, app/env/namespace/config/release groups and raw API passthrough; global flags and JSON/table outputs
    • Multi-backend credential management (env, file, native), interactive token input, secure storage, and OpenAPI HTTP client with request/response handling
    • Structured rendering with redaction of secrets and table formatting
  • Documentation

    • Comprehensive README documenting commands, config, credential providers, and dev/CI tasks
  • Tests

    • Extensive unit and integration test suites covering auth, CLI, profiles, redaction, and OpenAPI mappings
  • Chores

    • CI workflow, .gitignore, and Apache-2.0 license added

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Rust apollo-cli v0 scaffold: clap CLI surface, command execution (init/auth/profile/app/env/namespace/config/release/api), platform-aware config and credential backends (env/file/native), OpenAPI HTTP client, structured output/errors, secret redaction, CI workflow, and extensive tests.

Changes

Apollo CLI Scaffold v0

Layer / File(s) Summary
Project scaffolding and infrastructure
.github/workflows/ci.yml, .gitignore, LICENSE, Cargo.toml, README.md
Adds CI workflow, .gitignore /target rule, Apache-2.0 license, Cargo.toml with runtime/dev deps, and comprehensive README documenting CLI scope and developer workflows.
CLI parsing contract and global options
src/cli.rs
Clap-based Cli with flattened GlobalOptions (--profile/--server/--output/--yes), Commands enum, typed OutputFormat and HttpMethod, scope and per-command argument structs.
Library and binary entrypoints
src/lib.rs, src/main.rs
main_entry and run parse CLI, call execute, print RenderedOutput to stdout/stderr, and return ExitCode; binary main delegates to library; includes parse unit test.
Configuration loading, persistence, and runtime context resolution
src/config.rs
Defines AppConfig/ProfileConfig/CredentialRef, LoadedConfig, RuntimeContext; implements platform-aware config path resolution, load_config/save_config, resolve_context/resolve_output, and unit tests for OS path conventions.
Credential management with multiple backends
src/credential.rs
Credential domain types and CredentialStore trait; NativeCredentialStore (keyring), FileCredentialStore (credentials/*.token with 0o600 on Unix); token status/resolve/store/delete helpers and stdin/env token handling.
Secret redaction, output rendering, and structured errors
src/redaction.rs, src/output.rs, src/error.rs
Redactor redacts token-like values in text and JSON; Sensitive wrapper masks Debug; OutputWriter/RenderedOutput support JSON/table rendering; CliError and StructuredError schema with render/exit mapping.
OpenAPI HTTP client and path utilities
src/http.rs
OpenApiClient performs blocking JSON requests with Authorization header, enforces relative /openapi/v1 paths, parses responses into OpenApiResponse, maps non-2xx to CliError, and provides path/query encoding helpers.
Command execution and OpenAPI-driven operations
src/command.rs
execute() dispatcher and handlers for init, auth, profile, app/env/namespace/config/release, and api passthrough; enforces --yes for mutating ops, resolves operator/profile context, implements config set PUT→POST fallback, and renders responses.
OpenAPI integration tests & harness
tests/openapi.rs
TCP TestServer and helpers plus tests asserting generated OpenAPI request methods/paths/headers/bodies, confirmation gating, retry-create fallback, namespace create, and config apply synchronize payloads.
Integration tests: CLI, auth, profile, redaction
tests/cli.rs, tests/auth.rs, tests/profile.rs, tests/redaction.rs
Integration tests for help/global flags, auth flows (env/file/native detection, login/logout), profile show/list/use/init/add behavior, structured JSON errors, redaction assertions, and shared test helpers for isolated HOME/config setup.

Sequence Diagram (high-level request flow)

sequenceDiagram
  participant User
  participant Parser
  participant Execute
  participant Config
  participant Credential
  participant OpenApiClient
  participant OutputWriter
  User->>Parser: run `apollo ...`
  Parser->>Execute: execute(Cli)
  Execute->>Config: load_config / resolve_context
  Execute->>Credential: resolve_token or token_from_env_or_stdin
  Execute->>OpenApiClient: request(method, path, body)
  OpenApiClient->>Execute: OpenApiResponse / CliError
  Execute->>OutputWriter: render_success / render_error
  OutputWriter->>User: write stdout/stderr (with redaction)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Poem

🐰 In a burrow of crates the CLI did bloom,
Tokens tucked safe, redacted from view;
Profiles and servers lined up in a row,
Tests hop along, asserting the flow.
The rabbit builds — the binary says "go!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: implementation of an Apollo CLI OpenAPI v0 feature set as a new Rust CLI project.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/apollo-cli-v0-5628

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nobodyiam nobodyiam marked this pull request as ready for review June 7, 2026 03:18
Copilot AI review requested due to automatic review settings June 7, 2026 03:18

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67df365ccd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tests/profile.rs
Comment thread src/credential.rs Outdated
Comment thread src/command.rs Outdated
Comment thread src/command.rs Outdated
Comment thread src/credential.rs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR scaffolds a standalone Rust-based apollo CLI (v0 foundation), including initial command routing, config/context resolution, credential storage abstraction, redaction utilities, structured output/errors, and CI/testing setup.

Changes:

  • Adds clap-based CLI surface area with top-level command groups (auth, profile, etc.) plus global flags and basic routing.
  • Implements config loading/saving + runtime context resolution, along with initial profile and auth command behavior.
  • Introduces structured output/error rendering with redaction, and adds integration/unit tests plus a basic Rust CI workflow.

Reviewed changes

Copilot reviewed 17 out of 19 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
tests/auth.rs Integration tests for auth flows (env token, file fallback, logout).
tests/cli.rs Integration tests for help output, global flags, and placeholder command errors.
tests/profile.rs Integration tests for profile commands and context precedence.
tests/redaction.rs Tests for redaction of text, JSON fields, and Sensitive debug output.
src/cli.rs clap CLI definitions (commands + global flags + output enum).
src/command.rs Command dispatch + implementations for auth/profile scaffolding.
src/config.rs Config path resolution, config load/save, and runtime context/output resolution.
src/credential.rs Credential store abstraction (native/file/env) + token acquisition helpers.
src/error.rs Structured error model and rendering via OutputWriter.
src/lib.rs Library entrypoint + CLI parse tests.
src/main.rs Binary entrypoint delegating to apollo_cli::main_entry.
src/output.rs Output writer for JSON/table + redaction hook.
src/redaction.rs Redaction utilities and Sensitive wrapper type.
README.md Developer docs and CLI behavior overview for the scaffold.
LICENSE Apache 2.0 license text.
Cargo.toml Crate manifest with dependencies and dev-dependencies.
Cargo.lock Dependency lockfile for reproducible builds.
.gitignore Ignores Rust build artifacts (/target).
.github/workflows/ci.yml CI workflow running fmt, clippy (warnings as errors), and tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/profile.rs
Comment thread tests/profile.rs
Comment thread tests/auth.rs
Comment thread tests/auth.rs
Comment thread tests/auth.rs
Comment thread src/credential.rs Outdated
Comment thread src/redaction.rs
Comment thread src/command.rs
Comment thread src/credential.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (13)
Cargo.toml (1)

15-22: ⚡ Quick win

Align serde_json version between runtime and dev dependencies.

Using different serde_json versions across [dependencies] and [dev-dependencies] can cause unnecessary duplicate crate resolution and harder debugging. Prefer a single version unless a test-only constraint requires divergence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Cargo.toml` around lines 15 - 22, The crate lists serde_json twice with
mismatched versions; update the serde_json entry under [dev-dependencies] to
match the runtime version used in [dependencies] (change serde_json = "1.0.145"
to serde_json = "1.0.150" or vice‑versa so both use the same version string) to
avoid duplicate crate resolution; edit Cargo.toml to make the two serde_json
entries identical and then run cargo update if needed to refresh the lockfile.
src/output.rs (1)

63-78: ⚡ Quick win

Distinguish serialization error messages.

Lines 68 and 71-72 use identical .expect("structured success json serialization") messages for different serialization operations (to_value vs to_string_pretty). If either fails, the message won't indicate which step caused the panic.

♻️ Proposed fix to clarify error messages
             OutputFormat::Json => {
                 let value =
-                    serde_json::to_value(value).expect("structured success json serialization");
+                    serde_json::to_value(value).expect("serialize success to json value");
                 let value = redactor.redact_json(value);
                 RenderedOutput::stdout(
                     serde_json::to_string_pretty(&value)
-                        .expect("structured success json serialization"),
+                        .expect("pretty-print json value"),
                 )
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/output.rs` around lines 63 - 78, The two identical expect messages in
render_success's OutputFormat::Json arm obscure which serde step failed; update
the expect on serde_json::to_value(value) to a message like "failed to convert
success value to serde_json::Value in render_success" and change the expect on
serde_json::to_string_pretty(&value) to something like "failed to serialize
redacted success JSON to string in render_success" (refer to render_success,
OutputFormat::Json, serde_json::to_value, serde_json::to_string_pretty, and
redactor.redact_json to locate the lines).
src/error.rs (1)

216-221: ⚡ Quick win

Extract duplicated ensure_trailing_newline helper.

This function is identical to the one in src/output.rs lines 95-100. Duplication makes maintenance harder if the logic ever needs to change.

♻️ Proposed fix to eliminate duplication

Move the helper to output.rs and make it pub(crate), then reuse it here:

In src/output.rs:

-fn ensure_trailing_newline(mut body: String) -> String {
+pub(crate) fn ensure_trailing_newline(mut body: String) -> String {
     if !body.ends_with('\n') {
         body.push('\n');
     }
     body
 }

In src/error.rs:

+use crate::output::ensure_trailing_newline;
+
 use crate::cli::OutputFormat;
 use crate::output::{OutputStream, OutputWriter, RenderedOutput, StructuredError};

 // ... rest of file ...

-fn ensure_trailing_newline(mut body: String) -> String {
-    if !body.ends_with('\n') {
-        body.push('\n');
-    }
-    body
-}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/error.rs` around lines 216 - 221, The function ensure_trailing_newline is
duplicated; remove the copy from the error module and expose the single
implementation in the output module as pub(crate) fn
ensure_trailing_newline(...), then call that shared helper from the error code
(importing or referencing output::ensure_trailing_newline as needed) so both
modules reuse the one implementation and no duplicate helper remains.
src/cli.rs (1)

56-61: ⚡ Quick win

Consider adding value_name to the Use command's name argument.

The name field will work as a required positional argument, but adding a value_name attribute would improve the help text clarity for users.

📝 Suggested improvement
 #[derive(Clone, Debug, Eq, PartialEq, Subcommand)]
 pub enum ProfileCommand {
     List,
     Show,
-    Use { name: String },
+    Use {
+        #[arg(value_name = "PROFILE_NAME")]
+        name: String,
+    },
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli.rs` around lines 56 - 61, Update the ProfileCommand::Use variant to
annotate its name field with a clap value_name so the help text displays a clear
placeholder; specifically, add a #[clap(value_name = "NAME")] (or similar casing
you prefer) attribute on the Use variant's name field (the "name: String" field)
so clap shows NAME instead of a generic placeholder in the help output.
src/config.rs (1)

56-64: 💤 Low value

Consider documenting the Linux fallback assumption.

The current implementation treats any non-macOS, non-Windows platform as Linux. This is reasonable for a v0 scaffold, but a comment would help future maintainers understand this design choice.

📝 Suggested documentation
     fn current() -> Self {
         if cfg!(target_os = "macos") {
             Self::MacOs
         } else if cfg!(target_os = "windows") {
             Self::Windows
         } else {
+            // Treat all other platforms (Linux, BSD, etc.) as Linux
             Self::Linux
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/config.rs` around lines 56 - 64, Add a short inline comment above the
current() function explaining that the implementation intentionally treats any
non-macOS and non-Windows target as Linux (a pragmatic fallback for the v0
scaffold) so future maintainers understand the assumption and can extend the
match to other OSes; reference the current() function and its returned variants
Self::MacOs, Self::Windows, and Self::Linux and keep the comment concise and
placed adjacent to the function definition.
tests/profile.rs (1)

231-240: ⚡ Quick win

Inconsistent environment cleanup vs auth tests.

The base_command helper in tests/auth.rs removes APOLLO_TOKEN (line 187) and sets APOLLO_CLI_TEST_DISABLE_NATIVE (line 190), but this version does neither. If APOLLO_TOKEN is set in the CI environment, it could interfere with profile tests. For consistency and isolation, consider aligning both test files.

🔧 Align with auth.rs env handling
 fn base_command(home: &TempDir) -> Command {
     let mut command = Command::cargo_bin("apollo").expect("apollo binary");
     command.env_remove("APOLLO_PROFILE");
     command.env_remove("APOLLO_SERVER");
     command.env_remove("APOLLO_OUTPUT");
+    command.env_remove("APOLLO_TOKEN");
     command.env_remove("XDG_CONFIG_HOME");
     command.env_remove("APPDATA");
+    command.env("APOLLO_CLI_TEST_DISABLE_NATIVE", "1");
     command.env("HOME", home.path());
     command
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/profile.rs` around lines 231 - 240, The base_command helper in
tests/profile.rs currently doesn't remove APOLLO_TOKEN or set
APOLLO_CLI_TEST_DISABLE_NATIVE, causing CI environment tokens to leak into
profile tests; update the base_command function to call
command.env_remove("APOLLO_TOKEN") and
command.env("APOLLO_CLI_TEST_DISABLE_NATIVE", "1") (matching the handling in
tests/auth.rs) so tests run in isolation and native auth is disabled.
tests/auth.rs (1)

182-230: ⚡ Quick win

Consider extracting common test helpers to a shared module.

The helpers base_command, temp_home, config_path, write_config, and normalize_toml are duplicated in both tests/auth.rs and tests/profile.rs with only minor differences (e.g., APOLLO_CLI_TEST_DISABLE_NATIVE env var). Extracting them to a common test support module (e.g., tests/common/mod.rs) would reduce duplication and ensure consistent test setup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/auth.rs` around lines 182 - 230, Extract the duplicated test helpers
into a shared test support module and update tests to import them: create a
tests/common/mod.rs (or tests/common.rs) and move functions base_command,
temp_home, config_path, credential_file_path, write_config, and normalize_toml
into it; change base_command to accept optional extra env overrides (e.g., a
slice or map) so the minor difference APOLLO_CLI_TEST_DISABLE_NATIVE can be
passed from tests/auth.rs vs tests/profile.rs; update both tests to use
common::base_command(...) and the other helpers, remove the duplicated
implementations, and ensure module visibility (pub) for the moved functions.
src/command.rs (2)

93-96: ⚡ Quick win

Duplicated default credential logic.

This constructs a default CredentialRef identical to the logic in credential::status() (lines 154-157). As noted in the credential.rs review, extract this to a shared helper like credential::default_credential_ref(profile).

♻️ Use shared helper

After adding default_credential_ref to credential.rs:

-        let credential_ref = context.credential.clone().unwrap_or_else(|| CredentialRef {
-            backend: "native".to_owned(),
-            key: profile.clone(),
-        });
+        let credential_ref = context
+            .credential
+            .clone()
+            .unwrap_or_else(|| credential::default_credential_ref(&profile));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/command.rs` around lines 93 - 96, This code duplicates the default
CredentialRef construction used in credential::status(); replace the inline
construction in command.rs (the credential_ref initialization that uses
context.credential.clone().unwrap_or_else(...) and the CredentialRef { backend:
"native", key: profile.clone() }) by calling a shared helper
credential::default_credential_ref(profile) (add that helper to credential.rs if
not present) and use context.credential.clone().unwrap_or_else(||
credential::default_credential_ref(profile.clone())) so the default logic is
centralized.

82-88: ⚡ Quick win

Redundant clone of profile string.

profile on line 84 is cloned even though it's already a String that was cloned from the context on line 55. This creates an unnecessary allocation.

♻️ Remove redundant clone
         let response = AuthLoginResponse {
             stored: true,
-            profile: profile.clone(),
+            profile,
             backend: credential_ref.backend,
             key: credential_ref.key,
         };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/command.rs` around lines 82 - 88, The AuthLoginResponse construction
needlessly clones profile; replace profile.clone() with profile to avoid the
extra allocation (use the moved String for AuthLoginResponse.profile), and
confirm there are no subsequent uses of the profile variable after constructing
AuthLoginResponse; relevant symbols: profile, AuthLoginResponse, credential_ref,
writer.render_success.
src/credential.rs (4)

159-163: 💤 Low value

Inconsistent handling of unknown credential backends.

Unknown credential backends are silently ignored but with different semantics: status() returns Ok(None) (line 162), while delete() returns Ok(()) (line 200). While this might enable forward compatibility, it could also hide configuration mistakes. Consider whether unknown backends should produce a warning or error.

Also applies to: 194-201


204-228: 💤 Low value

Function name order doesn't match execution order.

The function is named token_from_env_or_stdin, suggesting env is checked first, but the implementation checks stdin first (based on the token_stdin flag) and only falls back to env when the flag is false. Consider renaming to token_from_stdin_or_env to match the actual precedence, or reorder the logic to match the name.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/credential.rs` around lines 204 - 228, The function
token_from_env_or_stdin currently checks stdin first (via the token_stdin flag)
then falls back to env::var("APOLLO_TOKEN"), which contradicts its name; either
rename the function to token_from_stdin_or_env to reflect stdin-first precedence
or flip the logic so it queries env::var("APOLLO_TOKEN") first and only reads
stdin when env is empty or token_stdin is true. Update the function
signature/name (token_from_env_or_stdin) and all call sites accordingly if
renaming, or reorder the branches around token_stdin, Sensitive::new, and
env::var to match the chosen precedence.

92-99: 💤 Low value

Consider documenting the fallback behavior when config_path has no parent.

The parent().unwrap_or_else(|| Path::new(".")) fallback assumes that if the config file has no parent directory, the credentials directory should be ./credentials. While this is unlikely given typical config paths, it could be surprising if config_path is just a bare filename.

📝 Optional clarifying comment
 pub fn new(config_path: &Path) -> Self {
     Self {
+        // Credentials are stored in a sibling "credentials" directory.
+        // If config_path has no parent, fall back to "./credentials".
         base_dir: config_path
             .parent()
             .unwrap_or_else(|| Path::new("."))
             .join("credentials"),
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/credential.rs` around lines 92 - 99, The constructor Credential::new
currently computes base_dir from config_path.parent().unwrap_or_else(||
Path::new(".")).join("credentials") which silently falls back to "./credentials"
when config_path has no parent; add a brief doc comment on pub fn new (or an
inline comment above the base_dir initialization) documenting this fallback
behavior — that when config_path is a bare filename the credentials directory
becomes ./credentials — so callers are not surprised, and mention the rationale
(fallback to current directory) and any alternative callers should use if they
need a different resolution.

154-157: ⚡ Quick win

Consolidate the default credential reference construction.

The default CredentialRef { backend: "native", key: profile } pattern appears in both the status function (lines 154-157) and is duplicated in command.rs (logout handler, lines 93-96). Extracting this to a shared helper improves maintainability and ensures consistency.

♻️ Proposed helper function
+pub fn default_credential_ref(profile: &str) -> CredentialRef {
+    CredentialRef {
+        backend: "native".to_owned(),
+        key: profile.to_owned(),
+    }
+}
+
 pub fn status(
     config_path: &Path,
     profile: &str,
     credential: Option<&CredentialRef>,
 ) -> CredentialStatus {
     // ...
-    let credential = credential.cloned().unwrap_or_else(|| CredentialRef {
-        backend: "native".to_owned(),
-        key: profile.to_owned(),
-    });
+    let credential = credential.cloned().unwrap_or_else(|| default_credential_ref(profile));
     // ...
 }

Then update command.rs line 93-96 to use this helper.

Also applies to: 193-202

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/credential.rs` around lines 154 - 157, The code duplicates the default
CredentialRef { backend: "native", key: profile } construction in
src/credential.rs (status) and in command.rs (logout handler) and elsewhere
(lines ~193-202); refactor by adding a single helper function (e.g., fn
default_credential_ref(profile: &str) -> CredentialRef) inside src/credential.rs
or a shared module, returning CredentialRef with backend = "native" and key =
profile.to_owned(), then replace the direct inline constructions in the status
function (the credential.cloned().unwrap_or_else(...) call), the logout handler
in command.rs, and the other duplicated site(s) to call this helper to avoid
repetition and ensure consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 15-17: Replace the mutable tag refs for GitHub Actions in the
workflow with their immutable commit SHAs: update the three "uses" entries shown
(actions/checkout@v4, dtolnay/rust-toolchain@stable, Swatinem/rust-cache@v2) to
pin each to the corresponding commit SHA at the desired version; fetch the exact
commit SHAs from each action's repository (or the release commit that matches
the tags) and substitute the tag refs with the form uses:
owner/repo@<commit-sha> so the workflow uses immutable SHAs instead of
`@v4/`@stable/@v2.
- Line 15: Update the GitHub Actions checkout step to disable persisted
credentials by adding the persist-credentials: false input to the
actions/checkout@v4 step; locate the checkout step (uses: actions/checkout@v4)
in the workflow and add the persist-credentials: false key so downstream steps
do not receive the checkout token.

In `@src/command.rs`:
- Around line 210-220: The server field currently uses unwrap_or_default() in
ProfileSummaryRow::from_profile which yields an empty string for None and causes
inconsistent table output compared to the "<none>" placeholder used elsewhere;
update ProfileSummaryRow::from_profile so its server value maps missing servers
to the same "<none>" placeholder (e.g., replace unwrap_or_default() with logic
that returns "<none>" when profile.server is None) so ProfileSummaryRow::server
rendering matches other missing-value behavior.

In `@src/credential.rs`:
- Around line 116-126: The set method in credential.rs writes the token file
(function set, using path_for_key and Sensitive) but only applies 0o600
permissions on Unix; on Windows you must either apply appropriate ACL
restrictions or explicitly document the limitation. Fix by adding a
Windows-specific branch after fs::write(&path, ...) that calls a helper (e.g.,
set_file_permissions_windows or set_windows_acl) to restrict the file to the
current user using Win32 ACL APIs (or the crate windows-rs/winapi) and return
map_err on failure; alternatively, if you cannot implement ACLs now, add a clear
comment and runtime warning/error in set noting that Windows permissions are not
enforced and recommend secure storage/backends.

In `@src/output.rs`:
- Around line 37-61: In render_error, JSON-formatted errors are currently
emitted via RenderedOutput::stdout; change the JSON branch (match arm for
OutputFormat::Json in render_error) to return a RenderedOutput with
OutputStream::Stderr (the same stream used by the Table branch) instead of using
RenderedOutput::stdout so errors consistently go to stderr; keep the existing
serde_json::to_string_pretty call and redactor.redact_json usage but construct
the RenderedOutput with stream = OutputStream::Stderr and the serialized body.

In `@src/redaction.rs`:
- Around line 11-22: The regexes in Redactor::redact_text (the local variables
authorization and consumer_token) are being compiled on every call; move their
Regex::new(...) initialization into static, lazily-initialized globals (e.g.,
using std::sync::OnceLock or lazy_static) such as AUTHORIZATION and
CONSUMER_TOKEN, then change redact_text to reference these statics (calling
get_or_init() or deref) and reuse the precompiled Regex instances when
performing replace_all; keep the same capture groups and replacement logic
(format!("${{1}}{}", REDACTED)) but remove the per-call compilation.

In `@tests/auth.rs`:
- Around line 207-214: The credential_file_path function currently hardcodes a
macOS path; make it platform-aware by using a standard data directory helper
(e.g., dirs, dirs-next or directories crate) instead of "Library/Application
Support" so tests build the path from the system data directory; update
credential_file_path to call the appropriate API (e.g.,
dirs_next::data_local_dir or directories::ProjectDirs::data_dir) and then join
"apollo/credentials/<key>.token" (keeping TempDir and PathBuf usage) so Linux
uses ~/.local/share or XDG dirs and macOS keeps Library/Application Support.
- Around line 199-205: The helper config_path in tests/auth.rs currently
hardcodes the macOS "Library/Application Support/apollo/config.toml" location
which breaks Linux CI; update config_path(TempDir) to detect the platform at
runtime and return the appropriate path: on macOS keep the existing
Library/Application Support/apollo/config.toml, otherwise use XDG_CONFIG_HOME if
set or fallback to home/.config/apollo/config.toml (constructing with PathBuf
joins). Ensure the function references TempDir.path() and builds the path
accordingly so tests on Linux will find the config file.

In `@tests/profile.rs`:
- Around line 246-252: The config_path helper hardcodes macOS paths; update the
config_path function in tests/profile.rs to be platform-aware like
tests/auth.rs: detect macOS (cfg!(target_os = "macos")) and return
home.path().join("Library").join("Application
Support").join("apollo").join("config.toml") for macOS, otherwise return the
Linux/Unix equivalent (e.g.,
home.path().join(".config").join("apollo").join("config.toml")); keep the
function signature config_path(home: &TempDir) -> PathBuf and mirror the same
branching logic used in tests/auth.rs so CI on Linux uses the correct path.

---

Nitpick comments:
In `@Cargo.toml`:
- Around line 15-22: The crate lists serde_json twice with mismatched versions;
update the serde_json entry under [dev-dependencies] to match the runtime
version used in [dependencies] (change serde_json = "1.0.145" to serde_json =
"1.0.150" or vice‑versa so both use the same version string) to avoid duplicate
crate resolution; edit Cargo.toml to make the two serde_json entries identical
and then run cargo update if needed to refresh the lockfile.

In `@src/cli.rs`:
- Around line 56-61: Update the ProfileCommand::Use variant to annotate its name
field with a clap value_name so the help text displays a clear placeholder;
specifically, add a #[clap(value_name = "NAME")] (or similar casing you prefer)
attribute on the Use variant's name field (the "name: String" field) so clap
shows NAME instead of a generic placeholder in the help output.

In `@src/command.rs`:
- Around line 93-96: This code duplicates the default CredentialRef construction
used in credential::status(); replace the inline construction in command.rs (the
credential_ref initialization that uses
context.credential.clone().unwrap_or_else(...) and the CredentialRef { backend:
"native", key: profile.clone() }) by calling a shared helper
credential::default_credential_ref(profile) (add that helper to credential.rs if
not present) and use context.credential.clone().unwrap_or_else(||
credential::default_credential_ref(profile.clone())) so the default logic is
centralized.
- Around line 82-88: The AuthLoginResponse construction needlessly clones
profile; replace profile.clone() with profile to avoid the extra allocation (use
the moved String for AuthLoginResponse.profile), and confirm there are no
subsequent uses of the profile variable after constructing AuthLoginResponse;
relevant symbols: profile, AuthLoginResponse, credential_ref,
writer.render_success.

In `@src/config.rs`:
- Around line 56-64: Add a short inline comment above the current() function
explaining that the implementation intentionally treats any non-macOS and
non-Windows target as Linux (a pragmatic fallback for the v0 scaffold) so future
maintainers understand the assumption and can extend the match to other OSes;
reference the current() function and its returned variants Self::MacOs,
Self::Windows, and Self::Linux and keep the comment concise and placed adjacent
to the function definition.

In `@src/credential.rs`:
- Around line 204-228: The function token_from_env_or_stdin currently checks
stdin first (via the token_stdin flag) then falls back to
env::var("APOLLO_TOKEN"), which contradicts its name; either rename the function
to token_from_stdin_or_env to reflect stdin-first precedence or flip the logic
so it queries env::var("APOLLO_TOKEN") first and only reads stdin when env is
empty or token_stdin is true. Update the function signature/name
(token_from_env_or_stdin) and all call sites accordingly if renaming, or reorder
the branches around token_stdin, Sensitive::new, and env::var to match the
chosen precedence.
- Around line 92-99: The constructor Credential::new currently computes base_dir
from config_path.parent().unwrap_or_else(|| Path::new(".")).join("credentials")
which silently falls back to "./credentials" when config_path has no parent; add
a brief doc comment on pub fn new (or an inline comment above the base_dir
initialization) documenting this fallback behavior — that when config_path is a
bare filename the credentials directory becomes ./credentials — so callers are
not surprised, and mention the rationale (fallback to current directory) and any
alternative callers should use if they need a different resolution.
- Around line 154-157: The code duplicates the default CredentialRef { backend:
"native", key: profile } construction in src/credential.rs (status) and in
command.rs (logout handler) and elsewhere (lines ~193-202); refactor by adding a
single helper function (e.g., fn default_credential_ref(profile: &str) ->
CredentialRef) inside src/credential.rs or a shared module, returning
CredentialRef with backend = "native" and key = profile.to_owned(), then replace
the direct inline constructions in the status function (the
credential.cloned().unwrap_or_else(...) call), the logout handler in command.rs,
and the other duplicated site(s) to call this helper to avoid repetition and
ensure consistency.

In `@src/error.rs`:
- Around line 216-221: The function ensure_trailing_newline is duplicated;
remove the copy from the error module and expose the single implementation in
the output module as pub(crate) fn ensure_trailing_newline(...), then call that
shared helper from the error code (importing or referencing
output::ensure_trailing_newline as needed) so both modules reuse the one
implementation and no duplicate helper remains.

In `@src/output.rs`:
- Around line 63-78: The two identical expect messages in render_success's
OutputFormat::Json arm obscure which serde step failed; update the expect on
serde_json::to_value(value) to a message like "failed to convert success value
to serde_json::Value in render_success" and change the expect on
serde_json::to_string_pretty(&value) to something like "failed to serialize
redacted success JSON to string in render_success" (refer to render_success,
OutputFormat::Json, serde_json::to_value, serde_json::to_string_pretty, and
redactor.redact_json to locate the lines).

In `@tests/auth.rs`:
- Around line 182-230: Extract the duplicated test helpers into a shared test
support module and update tests to import them: create a tests/common/mod.rs (or
tests/common.rs) and move functions base_command, temp_home, config_path,
credential_file_path, write_config, and normalize_toml into it; change
base_command to accept optional extra env overrides (e.g., a slice or map) so
the minor difference APOLLO_CLI_TEST_DISABLE_NATIVE can be passed from
tests/auth.rs vs tests/profile.rs; update both tests to use
common::base_command(...) and the other helpers, remove the duplicated
implementations, and ensure module visibility (pub) for the moved functions.

In `@tests/profile.rs`:
- Around line 231-240: The base_command helper in tests/profile.rs currently
doesn't remove APOLLO_TOKEN or set APOLLO_CLI_TEST_DISABLE_NATIVE, causing CI
environment tokens to leak into profile tests; update the base_command function
to call command.env_remove("APOLLO_TOKEN") and
command.env("APOLLO_CLI_TEST_DISABLE_NATIVE", "1") (matching the handling in
tests/auth.rs) so tests run in isolation and native auth is disabled.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5465d79d-b6b5-43e9-826e-591047041a0d

📥 Commits

Reviewing files that changed from the base of the PR and between 803b719 and 67df365.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • .github/workflows/ci.yml
  • .gitignore
  • Cargo.toml
  • LICENSE
  • README.md
  • src/cli.rs
  • src/command.rs
  • src/config.rs
  • src/credential.rs
  • src/error.rs
  • src/lib.rs
  • src/main.rs
  • src/output.rs
  • src/redaction.rs
  • tests/auth.rs
  • tests/cli.rs
  • tests/profile.rs
  • tests/redaction.rs

Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/ci.yml Outdated
Comment thread src/command.rs
Comment thread src/credential.rs
Comment thread src/output.rs
Comment thread src/redaction.rs
Comment thread tests/auth.rs
Comment thread tests/auth.rs
Comment thread tests/profile.rs
@nobodyiam nobodyiam changed the title feat: scaffold Apollo CLI v0 feat: implement Apollo CLI OpenAPI v0 Jun 7, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 266455ece1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/command.rs
Comment thread src/command.rs
Comment thread src/command.rs
Comment thread src/command.rs
Comment thread src/command.rs
Comment thread src/command.rs Outdated
Comment thread src/redaction.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 084f6a9741

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/command.rs
Comment thread src/command.rs Outdated
Comment thread src/command.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
tests/openapi.rs (1)

353-360: ⚡ Quick win

Fail fast instead of hanging when a request is never captured.

These helpers block forever on recv(). If a future regression skips the network call, the suite stalls instead of producing a clear failure.

Suggested patch
 use std::fs;
 use std::io::{Read, Write};
 use std::net::{SocketAddr, TcpListener, TcpStream};
 use std::path::PathBuf;
 use std::sync::mpsc::{self, Receiver};
 use std::thread;
+use std::time::Duration;
@@
     fn request(self) -> CapturedRequest {
-        self.request_rx.recv().expect("captured request")
+        self.request_rx
+            .recv_timeout(Duration::from_secs(5))
+            .expect("captured request")
     }
 
     fn requests(self, count: usize) -> Vec<CapturedRequest> {
         (0..count)
-            .map(|_| self.request_rx.recv().expect("captured request"))
+            .map(|_| {
+                self.request_rx
+                    .recv_timeout(Duration::from_secs(5))
+                    .expect("captured request")
+            })
             .collect()
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/openapi.rs` around lines 353 - 360, The helpers request and requests
currently block forever on self.request_rx.recv(), causing test suites to hang;
update them to fail fast by using recv_timeout with a reasonable Duration (e.g.
Duration::from_secs(1)) instead of recv(), and change the expect message to
indicate a timed-out wait for a captured request; apply the same change inside
requests' loop (mapping over request_rx.recv_timeout(...).expect(...)) and add
the necessary use of std::time::Duration so a missing network call produces a
clear panic instead of hanging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/command.rs`:
- Around line 460-483: The helpers required_server and required_operator
currently accept Some("") as valid; change them to treat empty or
whitespace-only strings as missing by filtering out blank values (e.g., use
.filter(|s| !s.trim().is_empty()) on context.server and on the command_operator
/ context.operator results) so they return the existing CliError::invalid_input
when the value is empty; update required_operator to apply this blank-check both
to the mapped command_operator and to context.operator before calling
ok_or_else.

In `@src/http.rs`:
- Around line 64-69: The code currently passes the raw response body into
CliError::http_status (in the status.is_success() failure branch), which can
leak sensitive echoed data; change the call to first redact or bound the body
(e.g., replace with a redacted/trimmed string or call a shared redact helper)
before passing it to CliError::http_status, ensuring you reference the same
parameters (path, format) but provide a safe version of body (for example
truncated to a safe max length and with sensitive patterns masked) so only
redacted/limited content is rendered.
- Around line 47-58: The blocking HTTP flow uses self.client.request(...).send()
without a timeout and embeds raw response.text() into CliError::http_status; add
a per-request timeout (e.g., request = request.timeout(Duration::from_secs(30)))
on the reqwest::blocking::RequestBuilder before .send() to prevent hangs, and
when handling non-2xx responses, parse the response body as JSON
(serde_json::Value) and either redact sensitive keys or truncate/serialize a
sanitized subset before passing it to CliError::http_status (instead of the raw
text and instead of relying only on Redactor::redact_text for
OutputFormat::Table); update references around request, .send(),
CliError::network and CliError::http_status to use the sanitized payload.

---

Nitpick comments:
In `@tests/openapi.rs`:
- Around line 353-360: The helpers request and requests currently block forever
on self.request_rx.recv(), causing test suites to hang; update them to fail fast
by using recv_timeout with a reasonable Duration (e.g. Duration::from_secs(1))
instead of recv(), and change the expect message to indicate a timed-out wait
for a captured request; apply the same change inside requests' loop (mapping
over request_rx.recv_timeout(...).expect(...)) and add the necessary use of
std::time::Duration so a missing network call produces a clear panic instead of
hanging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c90a78ec-f1ea-4712-b9e7-37a193a99aae

📥 Commits

Reviewing files that changed from the base of the PR and between 67df365 and 084f6a9.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • Cargo.toml
  • README.md
  • src/cli.rs
  • src/command.rs
  • src/credential.rs
  • src/error.rs
  • src/http.rs
  • src/lib.rs
  • tests/auth.rs
  • tests/cli.rs
  • tests/openapi.rs
  • tests/profile.rs
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/cli.rs
  • tests/auth.rs
  • src/credential.rs

Comment thread src/command.rs
Comment thread src/http.rs
Comment thread src/http.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/cli.rs (1)

80-107: ⚖️ Poor tradeoff

Consider extracting shared profile setup fields.

InitArgs and ProfileAddArgs duplicate five fields: operator, token_stdin, store_token_in_file, overwrite, and (with different positioning) name. While duplication in CLI arg structs is common, if the fields continue to diverge or additional profile commands are added, consider extracting a shared base struct or using #[command(flatten)] for common options.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli.rs` around lines 80 - 107, InitArgs and ProfileAddArgs duplicate
common CLI fields (name, operator, token_stdin, store_token_in_file, overwrite);
extract those into a shared struct (e.g., ProfileOpts) and flatten it into both
InitArgs and ProfileAddArgs using #[command(flatten)] so the common options are
declared once. Update the definitions for InitArgs and ProfileAddArgs to remove
the duplicated fields and reference the new shared struct (ensure symbol names
like ProfileOpts, InitArgs, ProfileAddArgs and the field names
operator/token_stdin/store_token_in_file/overwrite/name are used consistently),
and run cargo build to confirm no arg parsing regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/command.rs`:
- Around line 235-240: When overwriting a profile in src/command.rs you rebuild
ProfileConfig but drop any existing credential; update the overwrite path so
ProfileConfig preserves the current credential instead of setting credential:
None — fetch the existing profile (if any) and set profile_config.credential =
existing_profile.credential.clone() or take() when constructing profile_config
(same change for the similar block at 242-257), and only replace credential when
the user provides a new token.
- Around line 815-820: The prompt helpers currently ignore a 0-byte read (EOF)
and treat it like empty input; in prompt_yes_no / prompt_required / prompt_line
replace the single-step read_line call with one that captures the returned usize
(bytes read) and map_err as before, then check if bytes == 0 and return an
aborted prompt error (e.g. return Err(CliError::aborted(...) or similar
project-specific variant) instead of treating it as empty/default); preserve
existing trimming/validation of `line` and `value` for non-EOF cases and apply
the same change to the other occurrence referenced in the comment.
- Around line 1002-1014: ProfileSetupResponse is emitting snake_case fields
(active_profile, config_path, next_steps) which breaks JSON consistency; add a
serde attribute to the ProfileSetupResponse struct (e.g., #[serde(rename_all =
"camelCase")]) so all serialized field names become camelCase to match other
responses, leaving existing field names and skip_serializing_if annotations
intact; update any tests or serialization consumers if they relied on snake_case
names.
- Around line 232-237: The setup currently assigns OutputFormat::Json into new
ProfileConfig when cli.global.output is None, causing implicit JSON to be
persisted; change the logic so that ProfileConfig.output is set only when
cli.global.output is Some(value) (i.e., persist explicit overrides), and keep a
separate local display variable (profile_output) computed as
cli.global.output.unwrap_or(OutputFormat::Json) for responses shown during
setup; update the code around resolve_setup_operator, profile_output, and
ProfileConfig construction so ProfileConfig.output is None unless an explicit
--output was provided.

In `@src/error.rs`:
- Around line 87-94: The ProfileAlreadyExists error should carry the command
context instead of hardcoding "profile add": change the
CliErrorKind::ProfileAlreadyExists variant to include a command: Option<String>,
update the constructor function profile_already_exists(profile: &str, format:
OutputFormat) -> Self to accept a command parameter (e.g.,
profile_already_exists(profile: &str, command: Option<String>, format:
OutputFormat) -> Self) and set the variant's command field accordingly, then
update all call sites that construct this error (both from the init flow and
profile add flow) to pass the appropriate command string (e.g.,
Some("init".to_owned()) or Some("profile add".to_owned())) or None as needed,
and finally adjust the error render logic that currently uses command:
Some("profile add".to_owned()) to read the command from the variant's command
field.

---

Nitpick comments:
In `@src/cli.rs`:
- Around line 80-107: InitArgs and ProfileAddArgs duplicate common CLI fields
(name, operator, token_stdin, store_token_in_file, overwrite); extract those
into a shared struct (e.g., ProfileOpts) and flatten it into both InitArgs and
ProfileAddArgs using #[command(flatten)] so the common options are declared
once. Update the definitions for InitArgs and ProfileAddArgs to remove the
duplicated fields and reference the new shared struct (ensure symbol names like
ProfileOpts, InitArgs, ProfileAddArgs and the field names
operator/token_stdin/store_token_in_file/overwrite/name are used consistently),
and run cargo build to confirm no arg parsing regressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5f315852-57bd-4f0a-acd4-f4fe3b00f1ad

📥 Commits

Reviewing files that changed from the base of the PR and between 084f6a9 and a232347.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • Cargo.toml
  • README.md
  • src/cli.rs
  • src/command.rs
  • src/error.rs
  • tests/cli.rs
  • tests/profile.rs
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/cli.rs
  • Cargo.toml

Comment thread src/command.rs Outdated
Comment thread src/command.rs
Comment thread src/command.rs Outdated
Comment thread src/command.rs
Comment thread src/error.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec9370fc82

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/command.rs Outdated
Comment thread src/config.rs Outdated
Comment thread src/command.rs Outdated
Comment thread src/credential.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 03f993b672

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/config.rs Outdated
Comment thread src/command.rs
profile_config.credential = credential.clone();

let mut config = loaded.config.clone();
config.profiles.insert(profile_name.clone(), profile_config);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear stale native credentials on overwrite

When an existing profile with a native credential is overwritten without entering a new token, the replacement config has credential: None, but auth resolution treats that as the default native key equal to the profile name. The old token therefore remains active for the overwritten profile (and may be sent to the new server), even though the profile was replaced without a credential; delete the previous credential or explicitly preserve only when requested.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not resolving this one yet. Current profile overwrite behavior intentionally preserves the existing credential unless a replacement token is provided; changing that would alter credential lifecycle semantics and likely needs an explicit --clear-credential/--preserve-credential product decision.

Comment thread src/command.rs Outdated
Comment thread src/config.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f19ecbaee5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/error.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/auth.rs`:
- Line 184: The assertion currently checks config_root(&home).join("dev.token")
which doesn't match how credential_file_path(&home, "../dev") normalizes; update
the test to assert the normalized credential path or that the credentials
directory is absent: either replace the line with
assert!(!credential_file_path(&home, "../dev").exists()); or
assert!(!config_root(&home).join("apollo").join("credentials").exists()); so the
check aligns with credential_file_path and the expected location.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 979127c9-292e-4474-9e3c-2ceb63d89b16

📥 Commits

Reviewing files that changed from the base of the PR and between a232347 and 40d13dd.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • .github/workflows/ci.yml
  • Cargo.toml
  • README.md
  • src/command.rs
  • src/config.rs
  • src/credential.rs
  • src/error.rs
  • src/http.rs
  • src/output.rs
  • src/redaction.rs
  • tests/auth.rs
  • tests/cli.rs
  • tests/openapi.rs
  • tests/profile.rs
  • tests/redaction.rs
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (8)
  • .github/workflows/ci.yml
  • tests/redaction.rs
  • Cargo.toml
  • src/redaction.rs
  • src/output.rs
  • tests/openapi.rs
  • src/config.rs
  • src/command.rs

Comment thread tests/auth.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 40d13ddbae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/command.rs Outdated
Comment thread src/config.rs
Comment thread src/command.rs
Comment thread src/command.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/command.rs`:
- Around line 46-52: The helper non_blank currently returns the original String
when non-empty, preserving leading/trailing whitespace; update non_blank to trim
the input and return Some(trimmed_string) (or None if trimmed is empty) so
resolve_setup_profile_name, resolve_setup_server, and resolve_setup_operator
(which reuse non_blank) will persist trimmed values (e.g., let t =
value.trim().to_string(); if t.is_empty() { None } else { Some(t) }).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d6391f8-aaca-4a01-9a3f-52c717a694ae

📥 Commits

Reviewing files that changed from the base of the PR and between 40d13dd and c847b18.

📒 Files selected for processing (5)
  • src/command.rs
  • src/config.rs
  • src/credential.rs
  • tests/auth.rs
  • tests/profile.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/auth.rs
  • src/credential.rs
  • tests/profile.rs
  • src/config.rs

Comment thread src/command.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c847b1889c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/http.rs Outdated
Comment thread src/command.rs Outdated
Comment thread src/credential.rs
Comment thread src/command.rs Outdated
Comment thread src/config.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e8e43a6f61

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/command.rs
Comment thread src/command.rs Outdated
Comment thread src/command.rs
Comment thread src/http.rs
Comment thread src/command.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1cc8f74787

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/command.rs Outdated
Comment thread src/command.rs Outdated
Comment thread src/command.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 497c017ead

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/http.rs Outdated
Comment thread src/command.rs Outdated
Comment thread src/command.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f5bf9aaf42

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/command.rs
Comment thread src/http.rs Outdated
Comment thread src/command.rs Outdated
Comment thread src/command.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bc5d56094a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/command.rs Outdated
Comment thread src/config.rs Outdated
Comment thread src/command.rs Outdated
Comment thread src/command.rs Outdated
Comment thread src/command.rs Outdated
Comment thread src/command.rs Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 252be79bd7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/command.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bd7cb17359

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/command.rs
Comment on lines +451 to +452
if let Some(existing_name) = find_app_namespace(openapi, app_id, namespace_name)? {
return Ok(existing_name);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not reuse private namespaces for --public

When namespace create --public is run for an AppNamespace that already exists as private, such as the default application namespace, this early return skips the only request that would carry isPublic: true and then creates the env/cluster namespace using the existing private AppNamespace name. The command can report success while no public namespace was created for other apps to associate with; inspect the existing AppNamespace's isPublic value and reject the mismatch instead of blindly reusing it.

Useful? React with 👍 / 👎.

Comment thread src/credential.rs
Comment on lines +120 to +121
let token = fs::read_to_string(path).map_err(|error| error.to_string())?;
Ok(Some(Sensitive::new(token.trim().to_owned())))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat empty file credentials as unauthenticated

If a file-backed credential file is empty or whitespace-only, for example after a manual edit or a truncated overwrite, this still returns Some(Sensitive("")). auth status then reports the profile as authenticated and OpenAPI commands send a blank Authorization header instead of falling back to the normal authentication-required path, so the trimmed token should be filtered the same way environment and stdin tokens are.

Useful? React with 👍 / 👎.

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