Skip to content

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

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):

pubasyncfnprovider_list(server:&str,limit:u32,offset:u32,names_only:bool,output:&str,// new parametertls:&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()?);returnOk(());}"yaml" => {let items:Vec<serde_json::Value> = providers.iter().map(provider_to_json).collect();print!("{}", serde_yml::to_string(&items).into_diagnostic()?);returnOk(());}"table" => {}// existing table rendering
_ => returnErr(miette!("unsupported output format: {output}")),}

Helper function (new):

fnprovider_to_json(provider:&Provider) -> serde_json::Value{use serde_json::json;letmut 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)ifletSome(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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    gator:validatedGator validated this issue as ready for workstate:triage-neededOpened without agent diagnostics and needs triage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions