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    /// Search for API operations across all specifications
187    #[command(long_about = "Search for API operations by keyword or pattern.\n\n\
188                      Search through all registered API specifications to find\n\
189                      relevant operations. The search includes operation IDs,\n\
190                      descriptions, paths, and HTTP methods.\n\n\
191                      Examples:\n  \
192                      aperture search 'list users'     # Find user listing operations\n  \
193                      aperture search 'POST create'     # Find POST operations with 'create'\n  \
194                      aperture search issues --api sm   # Search only in 'sm' API\n  \
195                      aperture search 'get.*by.*id'     # Regex pattern search")]
196    Search {
197        /// Search query (keywords, patterns, or regex)
198        query: String,
199        /// Limit search to a specific API context
200        #[arg(long, value_name = "API", help = "Search only in specified API")]
201        api: Option<String>,
202        /// Show detailed results including paths and parameters
203        #[arg(long, help = "Show detailed information for each result")]
204        verbose: bool,
205    },
206    /// Execute API operations using shortcuts or direct operation IDs
207    #[command(
208        name = "exec",
209        long_about = "Execute API operations using shortcuts instead of full paths.\n\n\
210                      This command attempts to resolve shortcuts to their full command paths:\n\
211                      - Direct operation IDs: getUserById --id 123\n\
212                      - HTTP method + path: GET /users/123\n\
213                      - Tag-based shortcuts: users list\n\n\
214                      When multiple matches are found, you'll get suggestions to choose from.\n\n\
215                      Examples:\n  \
216                      aperture exec getUserById --id 123\n  \
217                      aperture exec GET /users/123\n  \
218                      aperture exec users list"
219    )]
220    Exec {
221        /// Shortcut command arguments
222        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
223        args: Vec<String>,
224    },
225    /// Get detailed documentation for APIs and commands
226    #[command(
227        long_about = "Get comprehensive documentation for APIs and operations.\n\n\
228                      This provides detailed information including parameters, examples,\n\
229                      response schemas, and authentication requirements. Use it to learn\n\
230                      about available functionality without trial and error.\n\n\
231                      Examples:\n  \
232                      aperture docs                        # Interactive help menu\n  \
233                      aperture docs myapi                  # API overview\n  \
234                      aperture docs myapi users get-user  # Detailed command help"
235    )]
236    Docs {
237        /// API name (optional, shows interactive menu if omitted)
238        api: Option<String>,
239        /// Tag/category name (optional)
240        tag: Option<String>,
241        /// Operation name (optional)
242        operation: Option<String>,
243        /// Show enhanced formatting with examples
244        #[arg(long, help = "Enhanced formatting with examples and tips")]
245        enhanced: bool,
246    },
247    /// Show API overview with statistics and quick start guide
248    #[command(
249        long_about = "Display comprehensive API overview with statistics and examples.\n\n\
250                      Shows operation counts, method distribution, available categories,\n\
251                      and sample commands to help you get started quickly with any API.\n\n\
252                      Examples:\n  \
253                      aperture overview myapi\n  \
254                      aperture overview --all  # Overview of all registered APIs"
255    )]
256    Overview {
257        /// API name (required unless using --all)
258        api: Option<String>,
259        /// Show overview for all registered APIs
260        #[arg(long, conflicts_with = "api", help = "Show overview for all APIs")]
261        all: bool,
262    },
263}
264
265#[derive(Subcommand, Debug)]
266pub enum ConfigCommands {
267    /// Add a new API specification from a file
268    #[command(
269        long_about = "Add an OpenAPI 3.x specification to your configuration.\n\n\
270                      This validates the specification, extracts operations, and creates\n\
271                      a cached representation for fast command generation. The spec name\n\
272                      becomes the context for executing API operations.\n\n\
273                      Supported formats: YAML (.yaml, .yml)\n\
274                      Supported auth: API Key, Bearer Token\n\n\
275                      Examples:\n  \
276                      aperture config add myapi ./openapi.yaml\n  \
277                      aperture config add myapi https://api.example.com/openapi.yaml"
278    )]
279    Add {
280        /// Name to identify this API specification (used as context in 'aperture api')
281        name: String,
282        /// Path to the `OpenAPI` 3.x specification file (YAML format) or URL
283        file_or_url: String,
284        /// Overwrite existing specification if it already exists
285        #[arg(long, help = "Replace the specification if it already exists")]
286        force: bool,
287        /// Reject specs with unsupported features instead of skipping endpoints
288        #[arg(
289            long,
290            help = "Reject entire spec if any endpoints have unsupported content types (e.g., multipart/form-data, XML). Default behavior skips unsupported endpoints with warnings."
291        )]
292        strict: bool,
293    },
294    /// List all registered API specifications
295    #[command(
296        long_about = "Display all currently registered API specifications.\n\n\
297                      Shows the names you can use as contexts with 'aperture api'.\n\
298                      Use this to see what APIs are available for command generation."
299    )]
300    List {
301        /// Show detailed information including skipped endpoints
302        #[arg(long, help = "Show detailed information about each API")]
303        verbose: bool,
304    },
305    /// Remove an API specification from configuration
306    #[command(
307        long_about = "Remove a registered API specification and its cached data.\n\n\
308                      This removes both the original specification file and the\n\
309                      generated cache, making the API operations unavailable.\n\
310                      Use 'aperture config list' to see available specifications."
311    )]
312    Remove {
313        /// Name of the API specification to remove
314        name: String,
315    },
316    /// Edit an API specification in your default editor
317    #[command(
318        long_about = "Open an API specification in your default text editor.\n\n\
319                      Uses the $EDITOR environment variable to determine which editor\n\
320                      to use. After editing, you may need to re-add the specification\n\
321                      to update the cached representation.\n\n\
322                      Example:\n  \
323                      export EDITOR=vim\n  \
324                      aperture config edit myapi"
325    )]
326    Edit {
327        /// Name of the API specification to edit
328        name: String,
329    },
330    /// Set base URL for an API specification
331    #[command(long_about = "Set the base URL for an API specification.\n\n\
332                      This overrides the base URL from the OpenAPI spec and the\n\
333                      APERTURE_BASE_URL environment variable. You can set a general\n\
334                      override or environment-specific URLs.\n\n\
335                      Examples:\n  \
336                      aperture config set-url myapi https://api.example.com\n  \
337                      aperture config set-url myapi --env staging https://staging.example.com\n  \
338                      aperture config set-url myapi --env prod https://prod.example.com")]
339    SetUrl {
340        /// Name of the API specification
341        name: String,
342        /// The base URL to set
343        url: String,
344        /// Set URL for a specific environment (e.g., dev, staging, prod)
345        #[arg(long, value_name = "ENV", help = "Set URL for specific environment")]
346        env: Option<String>,
347    },
348    /// Get base URL configuration for an API specification
349    #[command(
350        long_about = "Display the base URL configuration for an API specification.\n\n\
351                      Shows the configured base URL override and any environment-specific\n\
352                      URLs. Also displays what URL would be used based on current\n\
353                      environment settings.\n\n\
354                      Example:\n  \
355                      aperture config get-url myapi"
356    )]
357    GetUrl {
358        /// Name of the API specification
359        name: String,
360    },
361    /// List all configured base URLs
362    #[command(
363        long_about = "Display all configured base URLs across all API specifications.\n\n\
364                      Shows general overrides and environment-specific configurations\n\
365                      for each registered API. Useful for reviewing your URL settings\n\
366                      at a glance."
367    )]
368    ListUrls {},
369    /// Set secret configuration for an API specification security scheme
370    #[command(
371        long_about = "Configure authentication secrets for API specifications.\n\n\
372                      This allows you to set environment variable mappings for security\n\
373                      schemes without modifying the OpenAPI specification file. These\n\
374                      settings take precedence over x-aperture-secret extensions.\n\n\
375                      Examples:\n  \
376                      aperture config set-secret myapi bearerAuth --env API_TOKEN\n  \
377                      aperture config set-secret myapi apiKey --env API_KEY\n  \
378                      aperture config set-secret myapi --interactive"
379    )]
380    SetSecret {
381        /// Name of the API specification
382        api_name: String,
383        /// Name of the security scheme (omit for interactive mode)
384        scheme_name: Option<String>,
385        /// Environment variable name containing the secret
386        #[arg(long, value_name = "VAR", help = "Environment variable name")]
387        env: Option<String>,
388        /// Interactive mode to configure all undefined secrets
389        #[arg(long, conflicts_with_all = ["scheme_name", "env"], help = "Configure secrets interactively")]
390        interactive: bool,
391    },
392    /// List configured secrets for an API specification
393    #[command(
394        long_about = "Display configured secret mappings for an API specification.\n\n\
395                      Shows which security schemes are configured with environment\n\
396                      variables and which ones still rely on x-aperture-secret\n\
397                      extensions or are undefined.\n\n\
398                      Example:\n  \
399                      aperture config list-secrets myapi"
400    )]
401    ListSecrets {
402        /// Name of the API specification
403        api_name: String,
404    },
405    /// Remove a specific configured secret for an API specification
406    #[command(
407        long_about = "Remove a configured secret mapping for a specific security scheme.\n\n\
408                      This will remove the environment variable mapping for the specified\n\
409                      security scheme, causing it to fall back to x-aperture-secret\n\
410                      extensions or become undefined.\n\n\
411                      Examples:\n  \
412                      aperture config remove-secret myapi bearerAuth\n  \
413                      aperture config remove-secret myapi apiKey"
414    )]
415    RemoveSecret {
416        /// Name of the API specification
417        api_name: String,
418        /// Name of the security scheme to remove
419        scheme_name: String,
420    },
421    /// Clear all configured secrets for an API specification
422    #[command(
423        long_about = "Remove all configured secret mappings for an API specification.\n\n\
424                      This will remove all environment variable mappings for the API,\n\
425                      causing all security schemes to fall back to x-aperture-secret\n\
426                      extensions or become undefined. Use with caution.\n\n\
427                      Examples:\n  \
428                      aperture config clear-secrets myapi\n  \
429                      aperture config clear-secrets myapi --force"
430    )]
431    ClearSecrets {
432        /// Name of the API specification
433        api_name: String,
434        /// Skip confirmation prompt
435        #[arg(long, help = "Skip confirmation prompt")]
436        force: bool,
437    },
438    /// Re-initialize cached specifications
439    #[command(
440        long_about = "Regenerate binary cache files for API specifications.\n\n\
441                      This is useful when cache files become corrupted or when upgrading\n\
442                      between versions of Aperture that have incompatible cache formats.\n\
443                      You can reinitialize all specs or target a specific one.\n\n\
444                      Examples:\n  \
445                      aperture config reinit --all     # Reinitialize all specs\n  \
446                      aperture config reinit myapi     # Reinitialize specific spec"
447    )]
448    Reinit {
449        /// Name of the API specification to reinitialize (omit for --all)
450        context: Option<String>,
451        /// Reinitialize all cached specifications
452        #[arg(long, conflicts_with = "context", help = "Reinitialize all specs")]
453        all: bool,
454    },
455    /// Clear response cache
456    #[command(long_about = "Clear cached API responses to free up disk space.\n\n\
457                      You can clear cache for a specific API or all cached responses.\n\
458                      This is useful when you want to ensure fresh data from the API\n\
459                      or free up disk space.\n\n\
460                      Examples:\n  \
461                      aperture config clear-cache myapi     # Clear cache for specific API\n  \
462                      aperture config clear-cache --all     # Clear all cached responses")]
463    ClearCache {
464        /// Name of the API specification to clear cache for (omit for --all)
465        api_name: Option<String>,
466        /// Clear all cached responses
467        #[arg(long, conflicts_with = "api_name", help = "Clear all response cache")]
468        all: bool,
469    },
470    /// Show response cache statistics
471    #[command(long_about = "Display statistics about cached API responses.\n\n\
472                      Shows cache size, number of entries, and hit/miss rates.\n\
473                      Useful for monitoring cache effectiveness and disk usage.\n\n\
474                      Examples:\n  \
475                      aperture config cache-stats myapi     # Stats for specific API\n  \
476                      aperture config cache-stats           # Stats for all APIs")]
477    CacheStats {
478        /// Name of the API specification to show stats for (omit for all APIs)
479        api_name: Option<String>,
480    },
481}