aperture_cli/
cli.rs

1use clap::{Parser, Subcommand, ValueEnum};
2
3#[derive(ValueEnum, Clone, Debug)]
4pub enum OutputFormat {
5    /// Output as JSON (default)
6    Json,
7    /// Output as YAML
8    Yaml,
9    /// Output as formatted table
10    Table,
11}
12
13#[derive(Parser, Debug)]
14#[allow(clippy::struct_excessive_bools)]
15#[command(
16    author,
17    version,
18    about = "Aperture: Dynamic CLI generator for OpenAPI specifications",
19    long_about = "Aperture dynamically generates commands from OpenAPI 3.x specifications.\n\
20                  It serves as a bridge between autonomous AI agents and APIs by consuming\n\
21                  OpenAPI specs and creating a rich command-line interface with built-in\n\
22                  security, caching, and agent-friendly features.\n\n\
23                  Examples:\n  \
24                  aperture config add myapi api-spec.yaml\n  \
25                  aperture api myapi users get-user --id 123\n  \
26                  aperture config list\n\n\
27                  Agent-friendly features:\n  \
28                  aperture api myapi --describe-json    # Get capability manifest\n  \
29                  aperture --json-errors api myapi ...  # Structured error output\n  \
30                  aperture api myapi --dry-run ...      # Show request without executing"
31)]
32pub struct Cli {
33    /// Output a JSON manifest of all available commands and parameters
34    #[arg(
35        long,
36        global = true,
37        help = "Output capability manifest as JSON (can be filtered with --jq)"
38    )]
39    pub describe_json: bool,
40
41    /// Output all errors as structured JSON to stderr
42    /// When used with batch operations, outputs a clean JSON summary at the end
43    #[arg(long, global = true, help = "Output errors in JSON format")]
44    pub json_errors: bool,
45
46    /// Show the HTTP request that would be made without executing it
47    #[arg(long, global = true, help = "Show request details without executing")]
48    pub dry_run: bool,
49
50    /// Set the Idempotency-Key header for safe retries
51    #[arg(
52        long,
53        global = true,
54        value_name = "KEY",
55        help = "Set idempotency key header"
56    )]
57    pub idempotency_key: Option<String>,
58
59    /// Output format for response data
60    #[arg(
61        long,
62        global = true,
63        value_enum,
64        default_value = "json",
65        help = "Output format for response data"
66    )]
67    pub format: OutputFormat,
68
69    /// Apply JQ filter to response data, describe-json output, or batch results (with --json-errors)
70    #[arg(
71        long,
72        global = true,
73        value_name = "FILTER",
74        help = "Apply JQ filter to JSON output (e.g., '.name', '.[] | select(.active)', '.batch_execution_summary.operations[] | select(.success == false)')"
75    )]
76    pub jq: Option<String>,
77
78    /// Execute operations from a batch file
79    #[arg(
80        long,
81        global = true,
82        value_name = "PATH",
83        help = "Path to batch file (JSON or YAML) containing multiple operations"
84    )]
85    pub batch_file: Option<String>,
86
87    /// Maximum concurrent requests for batch operations
88    #[arg(
89        long,
90        global = true,
91        value_name = "N",
92        default_value = "5",
93        help = "Maximum number of concurrent requests for batch operations"
94    )]
95    pub batch_concurrency: usize,
96
97    /// Rate limit for batch operations (requests per second)
98    #[arg(
99        long,
100        global = true,
101        value_name = "N",
102        help = "Rate limit for batch operations (requests per second)"
103    )]
104    pub batch_rate_limit: Option<u32>,
105
106    /// Enable response caching
107    #[arg(
108        long,
109        global = true,
110        help = "Enable response caching (can speed up repeated requests)"
111    )]
112    pub cache: bool,
113
114    /// Disable response caching
115    #[arg(
116        long,
117        global = true,
118        conflicts_with = "cache",
119        help = "Disable response caching"
120    )]
121    pub no_cache: bool,
122
123    /// TTL for cached responses in seconds
124    #[arg(
125        long,
126        global = true,
127        value_name = "SECONDS",
128        help = "Cache TTL in seconds (default: 300)"
129    )]
130    pub cache_ttl: Option<u64>,
131
132    /// Use positional arguments for path parameters (legacy syntax)
133    #[arg(
134        long,
135        global = true,
136        help = "Use positional arguments for path parameters (legacy syntax)"
137    )]
138    pub positional_args: bool,
139
140    #[command(subcommand)]
141    pub command: Commands,
142}
143
144#[derive(Subcommand, Debug)]
145pub enum Commands {
146    /// Manage API specifications (add, list, remove, edit)
147    #[command(long_about = "Manage your collection of OpenAPI specifications.\n\n\
148                      Add specifications to make their operations available as commands,\n\
149                      list currently registered specs, remove unused ones, or edit\n\
150                      existing specifications in your default editor.")]
151    Config {
152        #[command(subcommand)]
153        command: ConfigCommands,
154    },
155    /// List available commands for an API specification
156    #[command(
157        long_about = "Display a tree-like summary of all available commands for an API.\n\n\
158                      Shows operations organized by tags, making it easy to discover\n\
159                      what functionality is available in a registered API specification.\n\
160                      This provides an overview without having to use --help on each operation.\n\n\
161                      Example:\n  \
162                      aperture list-commands myapi"
163    )]
164    ListCommands {
165        /// Name of the API specification context
166        context: String,
167    },
168    /// Execute API operations for a specific context
169    #[command(
170        long_about = "Execute operations from a registered API specification.\n\n\
171                      The context refers to the name you gave when adding the spec.\n\
172                      Commands are dynamically generated based on the OpenAPI specification,\n\
173                      organized by tags (e.g., 'users', 'posts', 'orders').\n\n\
174                      Examples:\n  \
175                      aperture api myapi users get-user --id 123\n  \
176                      aperture api myapi posts create-post --body '{\"title\":\"Hello\"}'\n  \
177                      aperture api myapi --help  # See available operations"
178    )]
179    Api {
180        /// Name of the API specification context
181        context: String,
182        /// Remaining arguments will be parsed dynamically based on the `OpenAPI` spec
183        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
184        args: Vec<String>,
185    },
186}
187
188#[derive(Subcommand, Debug)]
189pub enum ConfigCommands {
190    /// Add a new API specification from a file
191    #[command(
192        long_about = "Add an OpenAPI 3.x specification to your configuration.\n\n\
193                      This validates the specification, extracts operations, and creates\n\
194                      a cached representation for fast command generation. The spec name\n\
195                      becomes the context for executing API operations.\n\n\
196                      Supported formats: YAML (.yaml, .yml)\n\
197                      Supported auth: API Key, Bearer Token\n\n\
198                      Examples:\n  \
199                      aperture config add myapi ./openapi.yaml\n  \
200                      aperture config add myapi https://api.example.com/openapi.yaml"
201    )]
202    Add {
203        /// Name to identify this API specification (used as context in 'aperture api')
204        name: String,
205        /// Path to the `OpenAPI` 3.x specification file (YAML format) or URL
206        file_or_url: String,
207        /// Overwrite existing specification if it already exists
208        #[arg(long, help = "Replace the specification if it already exists")]
209        force: bool,
210        /// Reject specs with unsupported features instead of skipping endpoints
211        #[arg(
212            long,
213            help = "Reject entire spec if any endpoints have unsupported content types (e.g., multipart/form-data, XML). Default behavior skips unsupported endpoints with warnings."
214        )]
215        strict: bool,
216    },
217    /// List all registered API specifications
218    #[command(
219        long_about = "Display all currently registered API specifications.\n\n\
220                      Shows the names you can use as contexts with 'aperture api'.\n\
221                      Use this to see what APIs are available for command generation."
222    )]
223    List {
224        /// Show detailed information including skipped endpoints
225        #[arg(long, help = "Show detailed information about each API")]
226        verbose: bool,
227    },
228    /// Remove an API specification from configuration
229    #[command(
230        long_about = "Remove a registered API specification and its cached data.\n\n\
231                      This removes both the original specification file and the\n\
232                      generated cache, making the API operations unavailable.\n\
233                      Use 'aperture config list' to see available specifications."
234    )]
235    Remove {
236        /// Name of the API specification to remove
237        name: String,
238    },
239    /// Edit an API specification in your default editor
240    #[command(
241        long_about = "Open an API specification in your default text editor.\n\n\
242                      Uses the $EDITOR environment variable to determine which editor\n\
243                      to use. After editing, you may need to re-add the specification\n\
244                      to update the cached representation.\n\n\
245                      Example:\n  \
246                      export EDITOR=vim\n  \
247                      aperture config edit myapi"
248    )]
249    Edit {
250        /// Name of the API specification to edit
251        name: String,
252    },
253    /// Set base URL for an API specification
254    #[command(long_about = "Set the base URL for an API specification.\n\n\
255                      This overrides the base URL from the OpenAPI spec and the\n\
256                      APERTURE_BASE_URL environment variable. You can set a general\n\
257                      override or environment-specific URLs.\n\n\
258                      Examples:\n  \
259                      aperture config set-url myapi https://api.example.com\n  \
260                      aperture config set-url myapi --env staging https://staging.example.com\n  \
261                      aperture config set-url myapi --env prod https://prod.example.com")]
262    SetUrl {
263        /// Name of the API specification
264        name: String,
265        /// The base URL to set
266        url: String,
267        /// Set URL for a specific environment (e.g., dev, staging, prod)
268        #[arg(long, value_name = "ENV", help = "Set URL for specific environment")]
269        env: Option<String>,
270    },
271    /// Get base URL configuration for an API specification
272    #[command(
273        long_about = "Display the base URL configuration for an API specification.\n\n\
274                      Shows the configured base URL override and any environment-specific\n\
275                      URLs. Also displays what URL would be used based on current\n\
276                      environment settings.\n\n\
277                      Example:\n  \
278                      aperture config get-url myapi"
279    )]
280    GetUrl {
281        /// Name of the API specification
282        name: String,
283    },
284    /// List all configured base URLs
285    #[command(
286        long_about = "Display all configured base URLs across all API specifications.\n\n\
287                      Shows general overrides and environment-specific configurations\n\
288                      for each registered API. Useful for reviewing your URL settings\n\
289                      at a glance."
290    )]
291    ListUrls {},
292    /// Re-initialize cached specifications
293    #[command(
294        long_about = "Regenerate binary cache files for API specifications.\n\n\
295                      This is useful when cache files become corrupted or when upgrading\n\
296                      between versions of Aperture that have incompatible cache formats.\n\
297                      You can reinitialize all specs or target a specific one.\n\n\
298                      Examples:\n  \
299                      aperture config reinit --all     # Reinitialize all specs\n  \
300                      aperture config reinit myapi     # Reinitialize specific spec"
301    )]
302    Reinit {
303        /// Name of the API specification to reinitialize (omit for --all)
304        context: Option<String>,
305        /// Reinitialize all cached specifications
306        #[arg(long, conflicts_with = "context", help = "Reinitialize all specs")]
307        all: bool,
308    },
309    /// Clear response cache
310    #[command(long_about = "Clear cached API responses to free up disk space.\n\n\
311                      You can clear cache for a specific API or all cached responses.\n\
312                      This is useful when you want to ensure fresh data from the API\n\
313                      or free up disk space.\n\n\
314                      Examples:\n  \
315                      aperture config clear-cache myapi     # Clear cache for specific API\n  \
316                      aperture config clear-cache --all     # Clear all cached responses")]
317    ClearCache {
318        /// Name of the API specification to clear cache for (omit for --all)
319        api_name: Option<String>,
320        /// Clear all cached responses
321        #[arg(long, conflicts_with = "api_name", help = "Clear all response cache")]
322        all: bool,
323    },
324    /// Show response cache statistics
325    #[command(long_about = "Display statistics about cached API responses.\n\n\
326                      Shows cache size, number of entries, and hit/miss rates.\n\
327                      Useful for monitoring cache effectiveness and disk usage.\n\n\
328                      Examples:\n  \
329                      aperture config cache-stats myapi     # Stats for specific API\n  \
330                      aperture config cache-stats           # Stats for all APIs")]
331    CacheStats {
332        /// Name of the API specification to show stats for (omit for all APIs)
333        api_name: Option<String>,
334    },
335}