Skip to content

feat: add JSON/YAML output format to provider list command #1745

Closed

Description

@jeffmaury

Problem Statement

The openshell provider list command currently only supports table output and a --names flag for line-delimited names. Other list commands in the CLI (sandbox list, provider list-profiles) support structured output formats (JSON, YAML) via the -o/--output flag, enabling programmatic consumption and integration with other tools. Provider list should follow this established pattern for consistency and usability.

Proposed Design

Add an -o/--output flag to openshell provider list with support for table (default), json, and yaml formats, following the existing OutputFormat enum pattern used by other commands.

CLI Changes

Command definition (crates/openshell-cli/src/main.rs:764-778):

List {
    /// Maximum number of providers to return.
    #[arg(long, default_value_t = 100)]
    limit: u32,

    /// Offset into the provider list.
    #[arg(long, default_value_t = 0)]
    offset: u32,

    /// Print only provider names, one per line.
    #[arg(long, conflicts_with = "output")]
    names: bool,

    /// Output format.
    #[arg(short = 'o', long = "output", value_enum, default_value_t = OutputFormat::Table, conflicts_with = "names")]
    output: OutputFormat,
}

Function signature (crates/openshell-cli/src/run.rs:4618):

pub async fn provider_list(
    server: &str,
    limit: u32,
    offset: u32,
    names_only: bool,
    output: &str,  // new parameter
    tls: &TlsOptions,
) -> Result<()>

Implementation pattern (similar to sandbox_list at run.rs:3162-3242):

match output {
    "json" => {
        let items: Vec<serde_json::Value> = providers.iter().map(provider_to_json).collect();
        println!("{}", serde_json::to_string_pretty(&items).into_diagnostic()?);
        return Ok(());
    }
    "yaml" => {
        let items: Vec<serde_json::Value> = providers.iter().map(provider_to_json).collect();
        print!("{}", serde_yml::to_string(&items).into_diagnostic()?);
        return Ok(());
    }
    "table" => {}  // existing table rendering
    _ => return Err(miette!("unsupported output format: {output}")),
}

Helper function (new):

fn provider_to_json(provider: &Provider) -> serde_json::Value {
    use serde_json::json;
    
    let mut obj = serde_json::Map::new();
    
    // Always include core fields
    obj.insert("id".to_string(), json!(provider.object_id()));
    obj.insert("name".to_string(), json!(provider.object_name()));
    obj.insert("type".to_string(), json!(provider.r#type));
    
    // Credential keys (never values - security)
    let credential_keys: Vec<String> = provider.credentials.keys().cloned().collect();
    obj.insert("credential_keys".to_string(), json!(credential_keys));
    
    // Config (non-secret configuration)
    if !provider.config.is_empty() {
        obj.insert("config".to_string(), json!(provider.config));
    }
    
    // Metadata fields (only if metadata exists)
    if let Some(meta) = &provider.metadata {
        if !meta.labels.is_empty() {
            obj.insert("labels".to_string(), json!(meta.labels));
        }
        if meta.resource_version != 0 {
            obj.insert("resource_version".to_string(), json!(meta.resource_version));
        }
        if meta.created_at_ms != 0 {
            obj.insert("created_at_ms".to_string(), json!(meta.created_at_ms));
        }
    }
    
    // Credential expiration times (only if present)
    if !provider.credential_expires_at_ms.is_empty() {
        obj.insert("credential_expires_at_ms".to_string(), json!(provider.credential_expires_at_ms));
    }
    
    serde_json::Value::Object(obj)
}

Security Considerations

  • MUST NOT expose credential values in JSON/YAML output
  • Only include credential key names (verified: provider get command already follows this pattern at run.rs:4583)
  • Config values are safe to expose (non-secret configuration)

Testing

  1. Unit tests (add to main.rs, following sandbox_list pattern at 3716-3745):

    • provider_list_accepts_output_json()
    • provider_list_accepts_output_yaml()
    • provider_list_output_conflicts_with_names()
  2. Integration test (update provider_commands_integration.rs:1000):

    // Test JSON output
    run::provider_list(&ts.endpoint, 100, 0, false, "json", &ts.tls)
        .await
        .expect("provider list json");

Alternatives Considered

  1. Separate command (provider list-json): Rejected. Breaks CLI consistency and requires users to remember different commands.

  2. Always include JSON in table output: Rejected. Noisy for interactive use; structured formats should be opt-in.

  3. Use --format instead of --output: Rejected. Inconsistent with existing sandbox list and provider list-profiles commands that use -o/--output.

  4. Include optional fields with default values: Rejected. Cleaner JSON when absent fields are omitted entirely.

Agent Investigation

Codebase exploration:

  • Located provider list implementation at crates/openshell-cli/src/main.rs:764-778 and crates/openshell-cli/src/run.rs:4618-4677
  • Confirmed OutputFormat enum exists (Table, Yaml, Json) at main.rs:653-657
  • Verified pattern consistency with sandbox list (main.rs:1299-1324, run.rs:3162-3275)
  • Verified pattern consistency with provider list-profiles (main.rs:780-786, run.rs:4680-4730)
  • Confirmed security model: provider get only exposes credential keys, not values (run.rs:4583)
  • Identified reusable helpers: JSON/YAML serialization pattern

Files to modify:

  1. crates/openshell-cli/src/main.rs - Add output field to ProviderCommands::List, add unit tests
  2. crates/openshell-cli/src/run.rs - Add output parameter to provider_list(), implement format switching, add provider_to_json() helper
  3. crates/openshell-cli/tests/provider_commands_integration.rs - Update test call signature

Impact:

  • Low risk: follows established patterns
  • No breaking changes: default behavior (table output) unchanged
  • Enables programmatic usage (jq, yq, CI scripts)

Activity

  1. johntmyers commented on Jun 4, 2026

    @johntmyers
    Collaborator

    Can we update this to audit all CLI commands that need this and provide a common solution that doesn't duplicate code and allows all commands present and future to pick up this behavior easily?

  2. jeffmaury commented on Jun 4, 2026

    @jeffmaury
    ContributorAuthor

    Can we update this to audit all CLI commands that need this and provide a common solution that doesn't duplicate code and allows all commands present and future to pick up this behavior easily?

    Created #1750

  3. jeffmaury commented on Jun 9, 2026

    @jeffmaury
    ContributorAuthor

    🏗️ build-plan

    Implementation Plan

    Issue type: feat
    Complexity: Low
    Confidence: High — pattern is proven in production, generic helpers already exist

    Summary

    Add -o/--output flag support (json, yaml, table) to openshell provider list by migrating it to use the existing generic output formatter introduced in #1750. This follows the established pattern already used by sandbox list, gateway list, and provider list-profiles commands.

    Scope

    • crates/openshell-cli/src/main.rs: Add output field to ProviderCommands::List, add conflicts_with constraint, update invocation
    • crates/openshell-cli/src/run.rs: Add output parameter to provider_list(), add provider_to_json() helper, insert early-return for structured formats
    • crates/openshell-cli/tests/provider_commands_integration.rs: Update test call signature

    Implementation Steps

    1. Add provider_to_json() helper (run.rs:~4720)

      • Map Provider proto fields to JSON object
      • Include: id, name, type, credential_keys (keys only, never values), config, metadata, credential_expires_at_ms
      • Security: MUST NOT expose credential values (CWE-532)
    2. Update provider_list() function (run.rs:4721-4780)

      • Add output: &str parameter to signature
      • Insert crate::output::print_output_collection(output, &providers, provider_to_json)? early-return after fetching providers
      • Existing table/names logic unchanged
    3. Update CLI definition (main.rs:764-778, ~880-885)

      • Add output: OutputFormat field to ProviderCommands::List struct
      • Add conflicts_with = "names" to output field
      • Add conflicts_with = "output" to names field
      • Update invocation to pass output.as_str()
    4. Update integration test (provider_commands_integration.rs:~1000)

      • Update provider_list() call to include "table" parameter
    5. Manual testing and security verification

      • Test all output formats: -o json, -o yaml, -o table, --names
      • Verify --names and -o conflict
      • Critical security check: Confirm credential values are never exposed in JSON/YAML output

    Test Plan

    • Unit tests: Not required (generic output helpers are already tested in output.rs, pattern is proven)
    • Integration tests: Update existing test call signature to pass output parameter
    • Manual testing: Required for security verification — inspect JSON/YAML output to confirm no credential values leak
    • E2E tests: N/A (CLI command formatting does not require E2E coverage)

    Risks & Open Questions

    Low risk: Follows established pattern used by sandbox_list, gateway_list, provider_list_profiles. Generic output helpers are already tested and in production use. No breaking changes (default behavior unchanged).

    Security validation required: Before merging, manually inspect JSON/YAML output to confirm credential values are never exposed (only keys should appear). Compare with provider get command output which already follows this pattern (run.rs:4686).

    Documentation Impact

    None expected — this change affects CLI command output format only, not gateway configuration or published user-facing behavior documentation.


    Revision 1 — initial plan

  4. jeffmaury commented on Jun 9, 2026

    @jeffmaury
    ContributorAuthor

    🏗️ build-from-issue-agent

    Implementation Complete

    PR: #1830

    What was built

    Added -o/--output flag support (json, yaml, table) to openshell provider list using the existing generic output formatter from #1750. Follows the same pattern as sandbox list, gateway list, and provider list-profiles.

    Tests

    • Unit: All 778 existing tests passed, generic output helpers already tested in output.rs
    • Integration: Updated existing test call signature
    • E2E: N/A (CLI output formatting does not require E2E coverage)

    Security

    The provider_to_json() helper only exposes credential keys, never values, preventing CWE-532.

    Docs updated

    None needed — CLI output format change only, no gateway config or user-facing behavior documentation required.

    The issue will auto-close when the PR is merged.

  5. added a commit that references this issue on Jun 15, 2026
    a26a634
  6. added a commit that references this issue on Jun 15, 2026
    ed65bfd
  7. removed
    state:triage-neededOpened without agent diagnostics and needs triage
    on Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    gator:validatedGator validated this issue as ready for work

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions