pub mod commands;
pub mod errors;
pub mod legacy_execute;
pub mod render;
pub mod tracing_init;
pub mod translate;
use clap::{ArgAction, Parser, Subcommand, ValueEnum};
#[derive(ValueEnum, Clone, Debug)]
pub enum OutputFormat {
Json,
Yaml,
Table,
}
#[derive(Parser, Debug)]
#[allow(clippy::struct_excessive_bools)]
#[command(
author,
version,
about = "Aperture: Dynamic CLI generator for OpenAPI specifications",
long_about = "Aperture dynamically generates commands from OpenAPI 3.x specifications.\n\
It serves as a bridge between autonomous AI agents and APIs by consuming\n\
OpenAPI specs and creating a rich command-line interface with built-in\n\
security, caching, and agent-friendly features.\n\n\
Examples:\n \
aperture config add myapi api-spec.yaml\n \
aperture api myapi users get-user --id 123\n \
aperture config list\n\n\
Agent-friendly features:\n \
aperture api myapi --describe-json # Get capability manifest\n \
aperture --json-errors api myapi ... # Structured error output\n \
aperture api myapi --dry-run ... # Show request without executing"
)]
pub struct Cli {
#[arg(
long,
global = true,
help = "Output capability manifest as JSON (can be filtered with --jq)"
)]
pub describe_json: bool,
#[arg(long, global = true, help = "Output errors in JSON format")]
pub json_errors: bool,
#[arg(
long,
short = 'q',
global = true,
help = "Suppress informational output"
)]
pub quiet: bool,
#[arg(
short = 'v',
global = true,
action = ArgAction::Count,
help = "Increase logging verbosity (-v for debug, -vv for trace)"
)]
pub verbosity: u8,
#[arg(long, global = true, help = "Show request details without executing")]
pub dry_run: bool,
#[arg(
long,
global = true,
value_name = "KEY",
help = "Set idempotency key header"
)]
pub idempotency_key: Option<String>,
#[arg(
long,
global = true,
value_enum,
default_value = "json",
help = "Output format for response data"
)]
pub format: OutputFormat,
#[arg(
long,
global = true,
value_name = "FILTER",
help = "Apply JQ filter to JSON output (e.g., '.name', '.[] | select(.active)', '.batch_execution_summary.operations[] | select(.success == false)')"
)]
pub jq: Option<String>,
#[arg(
long,
global = true,
value_name = "PATH",
help = "Path to batch file (JSON or YAML) containing multiple operations"
)]
pub batch_file: Option<String>,
#[arg(
long,
global = true,
value_name = "N",
default_value = "5",
help = "Maximum number of concurrent requests for batch operations"
)]
pub batch_concurrency: usize,
#[arg(
long,
global = true,
value_name = "N",
help = "Rate limit for batch operations (requests per second)"
)]
pub batch_rate_limit: Option<u32>,
#[arg(
long,
global = true,
help = "Enable response caching (can speed up repeated requests)"
)]
pub cache: bool,
#[arg(
long,
global = true,
conflicts_with = "cache",
help = "Disable response caching"
)]
pub no_cache: bool,
#[arg(
long,
global = true,
value_name = "SECONDS",
help = "Cache TTL in seconds (default: 300)"
)]
pub cache_ttl: Option<u64>,
#[arg(
long,
global = true,
help = "Use positional arguments for path parameters (legacy syntax)"
)]
pub positional_args: bool,
#[arg(
long,
global = true,
help = "Stream all pages as NDJSON (auto-detects pagination strategy)"
)]
pub auto_paginate: bool,
#[arg(
long,
global = true,
value_name = "N",
help = "Maximum retry attempts (0 = disabled, overrides config)"
)]
pub retry: Option<u32>,
#[arg(
long,
global = true,
value_name = "DURATION",
help = "Initial retry delay (e.g., '500ms', '1s', '2s')"
)]
pub retry_delay: Option<String>,
#[arg(
long,
global = true,
value_name = "DURATION",
help = "Maximum retry delay cap (e.g., '30s', '1m')"
)]
pub retry_max_delay: Option<String>,
#[arg(
long,
global = true,
help = "Allow retrying non-idempotent requests without idempotency key"
)]
pub force_retry: bool,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(long_about = "Manage your collection of OpenAPI specifications.\n\n\
Add specifications to make their operations available as commands,\n\
list currently registered specs, remove unused ones, or edit\n\
existing specifications in your default editor.")]
Config {
#[command(subcommand)]
command: ConfigCommands,
},
#[command(
long_about = "Display a tree-like summary of all available commands for an API.\n\n\
Shows operations organized by tags, making it easy to discover\n\
what functionality is available in a registered API specification.\n\
This provides an overview without having to use --help on each operation.\n\n\
Example:\n \
aperture list-commands myapi"
)]
ListCommands {
context: String,
},
#[command(
long_about = "Execute operations from a registered API specification.\n\n\
The context refers to the name you gave when adding the spec.\n\
Commands are dynamically generated based on the OpenAPI specification,\n\
organized by tags (e.g., 'users', 'posts', 'orders').\n\n\
Examples:\n \
aperture api myapi users get-user --id 123\n \
aperture api myapi posts create-post --body '{\"title\":\"Hello\"}'\n \
aperture api myapi --help # See available operations"
)]
Api {
context: String,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
#[command(long_about = "Search for API operations by keyword or pattern.\n\n\
Search through all registered API specifications to find\n\
relevant operations. The search includes operation IDs,\n\
descriptions, paths, and HTTP methods.\n\n\
Examples:\n \
aperture search 'list users' # Find user listing operations\n \
aperture search 'POST create' # Find POST operations with 'create'\n \
aperture search issues --api sm # Search only in 'sm' API\n \
aperture search 'get.*by.*id' # Regex pattern search")]
Search {
query: String,
#[arg(long, value_name = "API", help = "Search only in specified API")]
api: Option<String>,
#[arg(long, help = "Show detailed information for each result")]
verbose: bool,
},
#[command(
name = "exec",
long_about = "Execute API operations using shortcuts instead of full paths.\n\n\
This command attempts to resolve shortcuts to their full command paths:\n\
- Direct operation IDs: getUserById --id 123\n\
- HTTP method + path: GET /users/123\n\
- Tag-based shortcuts: users list\n\n\
When multiple matches are found, you'll get suggestions to choose from.\n\n\
Examples:\n \
aperture exec getUserById --id 123\n \
aperture exec GET /users/123\n \
aperture exec users list"
)]
Exec {
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
#[command(
long_about = "Get comprehensive documentation for APIs and operations.\n\n\
This provides detailed information including parameters, examples,\n\
response schemas, and authentication requirements. Use it to learn\n\
about available functionality without trial and error.\n\n\
Examples:\n \
aperture docs # Interactive help menu\n \
aperture docs myapi # API overview\n \
aperture docs myapi users get-user # Detailed command help"
)]
Docs {
api: Option<String>,
tag: Option<String>,
operation: Option<String>,
#[arg(long, help = "Enhanced formatting with examples and tips")]
enhanced: bool,
},
#[command(
long_about = "Display comprehensive API overview with statistics and examples.\n\n\
Shows operation counts, method distribution, available categories,\n\
and sample commands to help you get started quickly with any API.\n\n\
Examples:\n \
aperture overview myapi\n \
aperture overview --all # Overview of all registered APIs"
)]
Overview {
api: Option<String>,
#[arg(long, conflicts_with = "api", help = "Show overview for all APIs")]
all: bool,
},
}
#[derive(Subcommand, Debug)]
pub enum ConfigCommands {
#[command(
long_about = "Add an OpenAPI 3.x specification to your configuration.\n\n\
This validates the specification, extracts operations, and creates\n\
a cached representation for fast command generation. The spec name\n\
becomes the context for executing API operations.\n\n\
Supported formats: YAML (.yaml, .yml)\n\
Supported auth: API Key, Bearer Token\n\n\
Examples:\n \
aperture config add myapi ./openapi.yaml\n \
aperture config add myapi https://api.example.com/openapi.yaml"
)]
Add {
name: String,
file_or_url: String,
#[arg(long, help = "Replace the specification if it already exists")]
force: bool,
#[arg(
long,
help = "Reject entire spec if any endpoints have unsupported content types (e.g., multipart/form-data, XML). Default behavior skips unsupported endpoints with warnings."
)]
strict: bool,
},
#[command(
long_about = "Display all currently registered API specifications.\n\n\
Shows the names you can use as contexts with 'aperture api'.\n\
Use this to see what APIs are available for command generation."
)]
List {
#[arg(long, help = "Show detailed information about each API")]
verbose: bool,
},
#[command(
long_about = "Remove a registered API specification and its cached data.\n\n\
This removes both the original specification file and the\n\
generated cache, making the API operations unavailable.\n\
Use 'aperture config list' to see available specifications."
)]
Remove {
name: String,
},
#[command(
long_about = "Open an API specification in your default text editor.\n\n\
Uses the $EDITOR environment variable to determine which editor\n\
to use. After editing, you may need to re-add the specification\n\
to update the cached representation.\n\n\
Example:\n \
export EDITOR=vim\n \
aperture config edit myapi"
)]
Edit {
name: String,
},
#[command(long_about = "Set the base URL for an API specification.\n\n\
This overrides the base URL from the OpenAPI spec and the\n\
APERTURE_BASE_URL environment variable. You can set a general\n\
override or environment-specific URLs.\n\n\
Examples:\n \
aperture config set-url myapi https://api.example.com\n \
aperture config set-url myapi --env staging https://staging.example.com\n \
aperture config set-url myapi --env prod https://prod.example.com")]
SetUrl {
name: String,
url: String,
#[arg(long, value_name = "ENV", help = "Set URL for specific environment")]
env: Option<String>,
},
#[command(
long_about = "Display the base URL configuration for an API specification.\n\n\
Shows the configured base URL override and any environment-specific\n\
URLs. Also displays what URL would be used based on current\n\
environment settings.\n\n\
Example:\n \
aperture config get-url myapi"
)]
GetUrl {
name: String,
},
#[command(
long_about = "Display all configured base URLs across all API specifications.\n\n\
Shows general overrides and environment-specific configurations\n\
for each registered API. Useful for reviewing your URL settings\n\
at a glance."
)]
ListUrls {},
#[command(
long_about = "Configure authentication secrets for API specifications.\n\n\
This allows you to set environment variable mappings for security\n\
schemes without modifying the OpenAPI specification file. These\n\
settings take precedence over x-aperture-secret extensions.\n\n\
Examples:\n \
aperture config set-secret myapi bearerAuth --env API_TOKEN\n \
aperture config set-secret myapi apiKey --env API_KEY\n \
aperture config set-secret myapi --interactive"
)]
SetSecret {
api_name: String,
scheme_name: Option<String>,
#[arg(long, value_name = "VAR", help = "Environment variable name")]
env: Option<String>,
#[arg(long, conflicts_with_all = ["scheme_name", "env"], help = "Configure secrets interactively")]
interactive: bool,
},
#[command(
long_about = "Display configured secret mappings for an API specification.\n\n\
Shows which security schemes are configured with environment\n\
variables and which ones still rely on x-aperture-secret\n\
extensions or are undefined.\n\n\
Example:\n \
aperture config list-secrets myapi"
)]
ListSecrets {
api_name: String,
},
#[command(
long_about = "Remove a configured secret mapping for a specific security scheme.\n\n\
This will remove the environment variable mapping for the specified\n\
security scheme, causing it to fall back to x-aperture-secret\n\
extensions or become undefined.\n\n\
Examples:\n \
aperture config remove-secret myapi bearerAuth\n \
aperture config remove-secret myapi apiKey"
)]
RemoveSecret {
api_name: String,
scheme_name: String,
},
#[command(
long_about = "Remove all configured secret mappings for an API specification.\n\n\
This will remove all environment variable mappings for the API,\n\
causing all security schemes to fall back to x-aperture-secret\n\
extensions or become undefined. Use with caution.\n\n\
Examples:\n \
aperture config clear-secrets myapi\n \
aperture config clear-secrets myapi --force"
)]
ClearSecrets {
api_name: String,
#[arg(long, help = "Skip confirmation prompt")]
force: bool,
},
#[command(
long_about = "Regenerate binary cache files for API specifications.\n\n\
This is useful when cache files become corrupted or when upgrading\n\
between versions of Aperture that have incompatible cache formats.\n\
You can reinitialize all specs or target a specific one.\n\n\
Examples:\n \
aperture config reinit --all # Reinitialize all specs\n \
aperture config reinit myapi # Reinitialize specific spec"
)]
Reinit {
context: Option<String>,
#[arg(long, conflicts_with = "context", help = "Reinitialize all specs")]
all: bool,
},
#[command(long_about = "Clear cached API responses to free up disk space.\n\n\
You can clear cache for a specific API or all cached responses.\n\
This is useful when you want to ensure fresh data from the API\n\
or free up disk space.\n\n\
Examples:\n \
aperture config clear-cache myapi # Clear cache for specific API\n \
aperture config clear-cache --all # Clear all cached responses")]
ClearCache {
api_name: Option<String>,
#[arg(long, conflicts_with = "api_name", help = "Clear all response cache")]
all: bool,
},
#[command(long_about = "Display statistics about cached API responses.\n\n\
Shows cache size, number of entries, and hit/miss rates.\n\
Useful for monitoring cache effectiveness and disk usage.\n\n\
Examples:\n \
aperture config cache-stats myapi # Stats for specific API\n \
aperture config cache-stats # Stats for all APIs")]
CacheStats {
api_name: Option<String>,
},
#[command(long_about = "Set a global configuration setting value.\n\n\
Supports dot-notation for nested settings and type-safe validation.\n\
The configuration file comments and formatting are preserved.\n\n\
Available settings:\n \
default_timeout_secs (integer) - Default timeout for API requests\n \
agent_defaults.json_errors (boolean) - Output errors as JSON by default\n \
retry_defaults.max_attempts (integer) - Max retry attempts (0 = disabled)\n \
retry_defaults.initial_delay_ms (integer) - Initial retry delay in ms\n \
retry_defaults.max_delay_ms (integer) - Maximum retry delay cap in ms\n\n\
Examples:\n \
aperture config set default_timeout_secs 60\n \
aperture config set agent_defaults.json_errors true\n \
aperture config set retry_defaults.max_attempts 3")]
Set {
key: String,
value: String,
},
#[command(
long_about = "Get the current value of a global configuration setting.\n\n\
Supports dot-notation for nested settings.\n\
Use `config settings` to see all available keys.\n\n\
Examples:\n \
aperture config get default_timeout_secs\n \
aperture config get retry_defaults.max_attempts\n \
aperture config get default_timeout_secs --json"
)]
Get {
key: String,
#[arg(long, help = "Output as JSON")]
json: bool,
},
#[command(
long_about = "Display all available configuration settings and their current values.\n\n\
Shows the key name, current value, type, and description for each\n\
setting. Use this to discover available settings and their defaults.\n\n\
Examples:\n \
aperture config settings\n \
aperture config settings --json"
)]
Settings {
#[arg(long, help = "Output as JSON")]
json: bool,
},
#[command(
name = "set-mapping",
long_about = "Customize the CLI command tree for an API specification.\n\n\
Rename tag groups, rename operations, add aliases, or hide commands\n\
without modifying the original OpenAPI spec. Changes take effect\n\
after `config reinit`.\n\n\
Examples:\n \
aperture config set-mapping myapi --group \"User Management\" users\n \
aperture config set-mapping myapi --operation getUserById --name fetch\n \
aperture config set-mapping myapi --operation getUserById --alias get\n \
aperture config set-mapping myapi --operation deleteUser --hidden"
)]
SetMapping {
api_name: String,
#[arg(long, num_args = 2, value_names = ["ORIGINAL", "NEW_NAME"], conflicts_with = "operation")]
group: Option<Vec<String>>,
#[arg(long, value_name = "OPERATION_ID")]
operation: Option<String>,
#[arg(long, requires = "operation", value_name = "NAME")]
name: Option<String>,
#[arg(long = "op-group", requires = "operation", value_name = "GROUP")]
op_group: Option<String>,
#[arg(long, requires = "operation", value_name = "ALIAS")]
alias: Option<String>,
#[arg(long, requires = "operation", value_name = "ALIAS")]
remove_alias: Option<String>,
#[arg(long, requires = "operation")]
hidden: bool,
#[arg(long, requires = "operation", conflicts_with = "hidden")]
visible: bool,
},
#[command(
name = "list-mappings",
long_about = "Display all custom command mappings for an API specification.\n\n\
Shows group renames, operation renames, aliases, and hidden flags.\n\n\
Example:\n \
aperture config list-mappings myapi"
)]
ListMappings {
api_name: String,
},
#[command(
name = "remove-mapping",
long_about = "Remove a custom command mapping for an API specification.\n\n\
Removes a group rename or an operation mapping. Changes take effect\n\
after `config reinit`.\n\n\
Examples:\n \
aperture config remove-mapping myapi --group \"User Management\"\n \
aperture config remove-mapping myapi --operation getUserById"
)]
RemoveMapping {
api_name: String,
#[arg(long, value_name = "ORIGINAL", conflicts_with = "operation")]
group: Option<String>,
#[arg(long, value_name = "OPERATION_ID")]
operation: Option<String>,
},
}