Repository navigation
Description
Problem Statement
The OpenShell CLI has grown to support --output flags on many commands (e.g., sandbox list, gateway list, provider list-profiles), allowing users to specify output format as JSON, YAML, or table. However, each command duplicates the same 15-20 line match statement to handle output formatting, leading to significant code duplication and maintenance burden.
Current duplication:
gateway_list()- line 1292 in run.rssandbox_list()- line 3185 in run.rsprovider_list_profiles()- line 4700 in run.rsprovider_profile_export()- line 4751 in run.rs- Policy commands - lines 6622, 6729, 6809 in run.rs
Each command repeats ~15-20 lines of boilerplate for the same logic, totaling ~91 lines of duplicated code initially (and growing with each new command that supports --output).
Proposed Design
Create a new output.rs module with generic helper functions that handle the repetitive output formatting logic using an early-return pattern. This preserves the existing code structure while eliminating duplication.
Key design decisions:
- Helper functions over traits/macros - Simpler, easier to understand, works with existing code
- Early-return pattern - Returns
boolto signal if output was handled, caller returns immediately or continues to table rendering - Three helper variants - Handles different use cases (collections, single items, pre-formatted output)
- Incremental migration - Can be adopted command-by-command without breaking changes
Core API:
// For collections of items
pub fn print_output_collection<T, F>(
format: &str,
items: &[T],
to_json: F,
) -> Result<bool>
where
F: Fn(&T) -> serde_json::Value
// For single items
pub fn print_output_single<T>(
format: &str,
item: &T,
to_json: impl Fn(&T) -> serde_json::Value,
) -> Result<bool>
// For pre-formatted output
pub fn print_output_direct(
format: &str,
json_fn: impl FnOnce() -> Result<String>,
yaml_fn: impl FnOnce() -> Result<String>,
) -> Result<bool>Usage example:
Before (18 lines):
match output {
"json" => {
let items: Vec<serde_json::Value> = sandboxes.iter().map(sandbox_to_json).collect();
println!("{}", serde_json::to_string_pretty(&items).into_diagnostic()?);
return Ok(());
}
"yaml" => {
let items: Vec<serde_json::Value> = sandboxes.iter().map(sandbox_to_json).collect();
print!("{}", serde_yml::to_string(&items).into_diagnostic()?);
return Ok(());
}
"table" => {}
_ => return Err(miette!("unsupported output format: {output}")),
}After (3 lines):
if print_output_collection(output, &sandboxes, sandbox_to_json)? {
return Ok(());
}Files to modify:
- Create:
crates/openshell-cli/src/output.rs(~80 lines) - Update:
crates/openshell-cli/src/lib.rs- add module declaration - Update:
crates/openshell-cli/src/run.rs- migrate commands to use helpers
Alternatives Considered
- Traits - More idiomatic Rust but requires implementing traits on types we don't own, adding complexity
- Macros - Would reduce boilerplate but makes code harder to debug and understand
- Do nothing - Continue duplicating code, but this makes adding new output formats harder and increases maintenance burden
The helper function approach was chosen for simplicity, type safety, and ease of adoption.
Agent Investigation
Analyzed crates/openshell-cli/src/main.rs (2745 lines) and crates/openshell-cli/src/run.rs (8768 lines):
- Identified 7+ commands with duplicated output formatting logic
- Confirmed
OutputFormatenum exists at line 652 ofmain.rs - Verified dependencies (
serde,serde_json,serde_yml) are already available - Each command has its own converter function (e.g.,
sandbox_to_json,gateway_to_json) - Pattern is consistent across all commands, making a generic solution feasible
Benefits:
- Reduces duplication by ~91 lines initially
- Single source of truth for output formatting
- Type-safe, compiler-verified
- Easy to extend (e.g., adding CSV format requires updating one module)
- Backward compatible
- Incremental adoption (migrate commands one at a time)
Activity
feat(cli): add generic output formatter to eliminate --output flag du…
feat(cli): add generic output formatter to eliminate --output flag du…
feat(cli): add generic output formatter to eliminate --output flag du…
feat(cli): add generic output formatter to eliminate --output flag du…
johntmyers commentedon Jun 4, 2026
Validated
This issue is valid for OpenShell because it describes a concentrated CLI maintainability refactor with a clear implementation path: consolidating repeated JSON/YAML/table output handling across existing CLI commands without changing the user-facing behavior.
Recommended next step: continue review and CI on the linked implementation PR #1753.
feat(cli): add generic output formatter to eliminate --output flag du…
feat(cli): add JSON/YAML output format to provider list command
feat(cli): add JSON/YAML output format to provider list command (#1830)