Skip to main content

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    /// Suppress non-essential output (success messages, tips, hints)
47    /// Only outputs requested data and errors
48    #[arg(
49        long,
50        short = 'q',
51        global = true,
52        help = "Suppress informational output"
53    )]
54    pub quiet: bool,
55
56    /// Show the HTTP request that would be made without executing it
57    #[arg(long, global = true, help = "Show request details without executing")]
58    pub dry_run: bool,
59
60    /// Set the Idempotency-Key header for safe retries
61    #[arg(
62        long,
63        global = true,
64        value_name = "KEY",
65        help = "Set idempotency key header"
66    )]
67    pub idempotency_key: Option<String>,
68
69    /// Output format for response data
70    #[arg(
71        long,
72        global = true,
73        value_enum,
74        default_value = "json",
75        help = "Output format for response data"
76    )]
77    pub format: OutputFormat,
78
79    /// Apply JQ filter to response data, describe-json output, or batch results (with --json-errors)
80    #[arg(
81        long,
82        global = true,
83        value_name = "FILTER",
84        help = "Apply JQ filter to JSON output (e.g., '.name', '.[] | select(.active)', '.batch_execution_summary.operations[] | select(.success == false)')"
85    )]
86    pub jq: Option<String>,
87
88    /// Execute operations from a batch file
89    #[arg(
90        long,
91        global = true,
92        value_name = "PATH",
93        help = "Path to batch file (JSON or YAML) containing multiple operations"
94    )]
95    pub batch_file: Option<String>,
96
97    /// Maximum concurrent requests for batch operations
98    #[arg(
99        long,
100        global = true,
101        value_name = "N",
102        default_value = "5",
103        help = "Maximum number of concurrent requests for batch operations"
104    )]
105    pub batch_concurrency: usize,
106
107    /// Rate limit for batch operations (requests per second)
108    #[arg(
109        long,
110        global = true,
111        value_name = "N",
112        help = "Rate limit for batch operations (requests per second)"
113    )]
114    pub batch_rate_limit: Option<u32>,
115
116    /// Enable response caching
117    #[arg(
118        long,
119        global = true,
120        help = "Enable response caching (can speed up repeated requests)"
121    )]
122    pub cache: bool,
123
124    /// Disable response caching
125    #[arg(
126        long,
127        global = true,
128        conflicts_with = "cache",
129        help = "Disable response caching"
130    )]
131    pub no_cache: bool,
132
133    /// TTL for cached responses in seconds
134    #[arg(
135        long,
136        global = true,
137        value_name = "SECONDS",
138        help = "Cache TTL in seconds (default: 300)"
139    )]
140    pub cache_ttl: Option<u64>,
141
142    /// Use positional arguments for path parameters (legacy syntax)
143    #[arg(
144        long,
145        global = true,
146        help = "Use positional arguments for path parameters (legacy syntax)"
147    )]
148    pub positional_args: bool,
149
150    /// Maximum number of retry attempts for failed requests
151    #[arg(
152        long,
153        global = true,
154        value_name = "N",
155        help = "Maximum retry attempts (0 = disabled, overrides config)"
156    )]
157    pub retry: Option<u32>,
158
159    /// Initial delay between retries (e.g., "500ms", "1s")
160    #[arg(
161        long,
162        global = true,
163        value_name = "DURATION",
164        help = "Initial retry delay (e.g., '500ms', '1s', '2s')"
165    )]
166    pub retry_delay: Option<String>,
167
168    /// Maximum delay cap between retries (e.g., "30s", "1m")
169    #[arg(
170        long,
171        global = true,
172        value_name = "DURATION",
173        help = "Maximum retry delay cap (e.g., '30s', '1m')"
174    )]
175    pub retry_max_delay: Option<String>,
176
177    /// Force retry on non-idempotent requests without an idempotency key
178    #[arg(
179        long,
180        global = true,
181        help = "Allow retrying non-idempotent requests without idempotency key"
182    )]
183    pub force_retry: bool,
184
185    #[command(subcommand)]
186    pub command: Commands,
187}
188
189#[derive(Subcommand, Debug)]
190pub enum Commands {
191    /// Manage API specifications (add, list, remove, edit)
192    #[command(long_about = "Manage your collection of OpenAPI specifications.\n\n\
193                      Add specifications to make their operations available as commands,\n\
194                      list currently registered specs, remove unused ones, or edit\n\
195                      existing specifications in your default editor.")]
196    Config {
197        #[command(subcommand)]
198        command: ConfigCommands,
199    },
200    /// List available commands for an API specification
201    #[command(
202        long_about = "Display a tree-like summary of all available commands for an API.\n\n\
203                      Shows operations organized by tags, making it easy to discover\n\
204                      what functionality is available in a registered API specification.\n\
205                      This provides an overview without having to use --help on each operation.\n\n\
206                      Example:\n  \
207                      aperture list-commands myapi"
208    )]
209    ListCommands {
210        /// Name of the API specification context
211        context: String,
212    },
213    /// Execute API operations for a specific context
214    #[command(
215        long_about = "Execute operations from a registered API specification.\n\n\
216                      The context refers to the name you gave when adding the spec.\n\
217                      Commands are dynamically generated based on the OpenAPI specification,\n\
218                      organized by tags (e.g., 'users', 'posts', 'orders').\n\n\
219                      Examples:\n  \
220                      aperture api myapi users get-user --id 123\n  \
221                      aperture api myapi posts create-post --body '{\"title\":\"Hello\"}'\n  \
222                      aperture api myapi --help  # See available operations"
223    )]
224    Api {
225        /// Name of the API specification context
226        context: String,
227        /// Remaining arguments will be parsed dynamically based on the `OpenAPI` spec
228        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
229        args: Vec<String>,
230    },
231    /// Search for API operations across all specifications
232    #[command(long_about = "Search for API operations by keyword or pattern.\n\n\
233                      Search through all registered API specifications to find\n\
234                      relevant operations. The search includes operation IDs,\n\
235                      descriptions, paths, and HTTP methods.\n\n\
236                      Examples:\n  \
237                      aperture search 'list users'     # Find user listing operations\n  \
238                      aperture search 'POST create'     # Find POST operations with 'create'\n  \
239                      aperture search issues --api sm   # Search only in 'sm' API\n  \
240                      aperture search 'get.*by.*id'     # Regex pattern search")]
241    Search {
242        /// Search query (keywords, patterns, or regex)
243        query: String,
244        /// Limit search to a specific API context
245        #[arg(long, value_name = "API", help = "Search only in specified API")]
246        api: Option<String>,
247        /// Show detailed results including paths and parameters
248        #[arg(long, help = "Show detailed information for each result")]
249        verbose: bool,
250    },
251    /// Execute API operations using shortcuts or direct operation IDs
252    #[command(
253        name = "exec",
254        long_about = "Execute API operations using shortcuts instead of full paths.\n\n\
255                      This command attempts to resolve shortcuts to their full command paths:\n\
256                      - Direct operation IDs: getUserById --id 123\n\
257                      - HTTP method + path: GET /users/123\n\
258                      - Tag-based shortcuts: users list\n\n\
259                      When multiple matches are found, you'll get suggestions to choose from.\n\n\
260                      Examples:\n  \
261                      aperture exec getUserById --id 123\n  \
262                      aperture exec GET /users/123\n  \
263                      aperture exec users list"
264    )]
265    Exec {
266        /// Shortcut command arguments
267        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
268        args: Vec<String>,
269    },
270    /// Get detailed documentation for APIs and commands
271    #[command(
272        long_about = "Get comprehensive documentation for APIs and operations.\n\n\
273                      This provides detailed information including parameters, examples,\n\
274                      response schemas, and authentication requirements. Use it to learn\n\
275                      about available functionality without trial and error.\n\n\
276                      Examples:\n  \
277                      aperture docs                        # Interactive help menu\n  \
278                      aperture docs myapi                  # API overview\n  \
279                      aperture docs myapi users get-user  # Detailed command help"
280    )]
281    Docs {
282        /// API name (optional, shows interactive menu if omitted)
283        api: Option<String>,
284        /// Tag/category name (optional)
285        tag: Option<String>,
286        /// Operation name (optional)
287        operation: Option<String>,
288        /// Show enhanced formatting with examples
289        #[arg(long, help = "Enhanced formatting with examples and tips")]
290        enhanced: bool,
291    },
292    /// Show API overview with statistics and quick start guide
293    #[command(
294        long_about = "Display comprehensive API overview with statistics and examples.\n\n\
295                      Shows operation counts, method distribution, available categories,\n\
296                      and sample commands to help you get started quickly with any API.\n\n\
297                      Examples:\n  \
298                      aperture overview myapi\n  \
299                      aperture overview --all  # Overview of all registered APIs"
300    )]
301    Overview {
302        /// API name (required unless using --all)
303        api: Option<String>,
304        /// Show overview for all registered APIs
305        #[arg(long, conflicts_with = "api", help = "Show overview for all APIs")]
306        all: bool,
307    },
308}
309
310#[derive(Subcommand, Debug)]
311pub enum ConfigCommands {
312    /// Add a new API specification from a file
313    #[command(
314        long_about = "Add an OpenAPI 3.x specification to your configuration.\n\n\
315                      This validates the specification, extracts operations, and creates\n\
316                      a cached representation for fast command generation. The spec name\n\
317                      becomes the context for executing API operations.\n\n\
318                      Supported formats: YAML (.yaml, .yml)\n\
319                      Supported auth: API Key, Bearer Token\n\n\
320                      Examples:\n  \
321                      aperture config add myapi ./openapi.yaml\n  \
322                      aperture config add myapi https://api.example.com/openapi.yaml"
323    )]
324    Add {
325        /// Name to identify this API specification (used as context in 'aperture api')
326        name: String,
327        /// Path to the `OpenAPI` 3.x specification file (YAML format) or URL
328        file_or_url: String,
329        /// Overwrite existing specification if it already exists
330        #[arg(long, help = "Replace the specification if it already exists")]
331        force: bool,
332        /// Reject specs with unsupported features instead of skipping endpoints
333        #[arg(
334            long,
335            help = "Reject entire spec if any endpoints have unsupported content types (e.g., multipart/form-data, XML). Default behavior skips unsupported endpoints with warnings."
336        )]
337        strict: bool,
338    },
339    /// List all registered API specifications
340    #[command(
341        long_about = "Display all currently registered API specifications.\n\n\
342                      Shows the names you can use as contexts with 'aperture api'.\n\
343                      Use this to see what APIs are available for command generation."
344    )]
345    List {
346        /// Show detailed information including skipped endpoints
347        #[arg(long, help = "Show detailed information about each API")]
348        verbose: bool,
349    },
350    /// Remove an API specification from configuration
351    #[command(
352        long_about = "Remove a registered API specification and its cached data.\n\n\
353                      This removes both the original specification file and the\n\
354                      generated cache, making the API operations unavailable.\n\
355                      Use 'aperture config list' to see available specifications."
356    )]
357    Remove {
358        /// Name of the API specification to remove
359        name: String,
360    },
361    /// Edit an API specification in your default editor
362    #[command(
363        long_about = "Open an API specification in your default text editor.\n\n\
364                      Uses the $EDITOR environment variable to determine which editor\n\
365                      to use. After editing, you may need to re-add the specification\n\
366                      to update the cached representation.\n\n\
367                      Example:\n  \
368                      export EDITOR=vim\n  \
369                      aperture config edit myapi"
370    )]
371    Edit {
372        /// Name of the API specification to edit
373        name: String,
374    },
375    /// Set base URL for an API specification
376    #[command(long_about = "Set the base URL for an API specification.\n\n\
377                      This overrides the base URL from the OpenAPI spec and the\n\
378                      APERTURE_BASE_URL environment variable. You can set a general\n\
379                      override or environment-specific URLs.\n\n\
380                      Examples:\n  \
381                      aperture config set-url myapi https://api.example.com\n  \
382                      aperture config set-url myapi --env staging https://staging.example.com\n  \
383                      aperture config set-url myapi --env prod https://prod.example.com")]
384    SetUrl {
385        /// Name of the API specification
386        name: String,
387        /// The base URL to set
388        url: String,
389        /// Set URL for a specific environment (e.g., dev, staging, prod)
390        #[arg(long, value_name = "ENV", help = "Set URL for specific environment")]
391        env: Option<String>,
392    },
393    /// Get base URL configuration for an API specification
394    #[command(
395        long_about = "Display the base URL configuration for an API specification.\n\n\
396                      Shows the configured base URL override and any environment-specific\n\
397                      URLs. Also displays what URL would be used based on current\n\
398                      environment settings.\n\n\
399                      Example:\n  \
400                      aperture config get-url myapi"
401    )]
402    GetUrl {
403        /// Name of the API specification
404        name: String,
405    },
406    /// List all configured base URLs
407    #[command(
408        long_about = "Display all configured base URLs across all API specifications.\n\n\
409                      Shows general overrides and environment-specific configurations\n\
410                      for each registered API. Useful for reviewing your URL settings\n\
411                      at a glance."
412    )]
413    ListUrls {},
414    /// Set secret configuration for an API specification security scheme
415    #[command(
416        long_about = "Configure authentication secrets for API specifications.\n\n\
417                      This allows you to set environment variable mappings for security\n\
418                      schemes without modifying the OpenAPI specification file. These\n\
419                      settings take precedence over x-aperture-secret extensions.\n\n\
420                      Examples:\n  \
421                      aperture config set-secret myapi bearerAuth --env API_TOKEN\n  \
422                      aperture config set-secret myapi apiKey --env API_KEY\n  \
423                      aperture config set-secret myapi --interactive"
424    )]
425    SetSecret {
426        /// Name of the API specification
427        api_name: String,
428        /// Name of the security scheme (omit for interactive mode)
429        scheme_name: Option<String>,
430        /// Environment variable name containing the secret
431        #[arg(long, value_name = "VAR", help = "Environment variable name")]
432        env: Option<String>,
433        /// Interactive mode to configure all undefined secrets
434        #[arg(long, conflicts_with_all = ["scheme_name", "env"], help = "Configure secrets interactively")]
435        interactive: bool,
436    },
437    /// List configured secrets for an API specification
438    #[command(
439        long_about = "Display configured secret mappings for an API specification.\n\n\
440                      Shows which security schemes are configured with environment\n\
441                      variables and which ones still rely on x-aperture-secret\n\
442                      extensions or are undefined.\n\n\
443                      Example:\n  \
444                      aperture config list-secrets myapi"
445    )]
446    ListSecrets {
447        /// Name of the API specification
448        api_name: String,
449    },
450    /// Remove a specific configured secret for an API specification
451    #[command(
452        long_about = "Remove a configured secret mapping for a specific security scheme.\n\n\
453                      This will remove the environment variable mapping for the specified\n\
454                      security scheme, causing it to fall back to x-aperture-secret\n\
455                      extensions or become undefined.\n\n\
456                      Examples:\n  \
457                      aperture config remove-secret myapi bearerAuth\n  \
458                      aperture config remove-secret myapi apiKey"
459    )]
460    RemoveSecret {
461        /// Name of the API specification
462        api_name: String,
463        /// Name of the security scheme to remove
464        scheme_name: String,
465    },
466    /// Clear all configured secrets for an API specification
467    #[command(
468        long_about = "Remove all configured secret mappings for an API specification.\n\n\
469                      This will remove all environment variable mappings for the API,\n\
470                      causing all security schemes to fall back to x-aperture-secret\n\
471                      extensions or become undefined. Use with caution.\n\n\
472                      Examples:\n  \
473                      aperture config clear-secrets myapi\n  \
474                      aperture config clear-secrets myapi --force"
475    )]
476    ClearSecrets {
477        /// Name of the API specification
478        api_name: String,
479        /// Skip confirmation prompt
480        #[arg(long, help = "Skip confirmation prompt")]
481        force: bool,
482    },
483    /// Re-initialize cached specifications
484    #[command(
485        long_about = "Regenerate binary cache files for API specifications.\n\n\
486                      This is useful when cache files become corrupted or when upgrading\n\
487                      between versions of Aperture that have incompatible cache formats.\n\
488                      You can reinitialize all specs or target a specific one.\n\n\
489                      Examples:\n  \
490                      aperture config reinit --all     # Reinitialize all specs\n  \
491                      aperture config reinit myapi     # Reinitialize specific spec"
492    )]
493    Reinit {
494        /// Name of the API specification to reinitialize (omit for --all)
495        context: Option<String>,
496        /// Reinitialize all cached specifications
497        #[arg(long, conflicts_with = "context", help = "Reinitialize all specs")]
498        all: bool,
499    },
500    /// Clear response cache
501    #[command(long_about = "Clear cached API responses to free up disk space.\n\n\
502                      You can clear cache for a specific API or all cached responses.\n\
503                      This is useful when you want to ensure fresh data from the API\n\
504                      or free up disk space.\n\n\
505                      Examples:\n  \
506                      aperture config clear-cache myapi     # Clear cache for specific API\n  \
507                      aperture config clear-cache --all     # Clear all cached responses")]
508    ClearCache {
509        /// Name of the API specification to clear cache for (omit for --all)
510        api_name: Option<String>,
511        /// Clear all cached responses
512        #[arg(long, conflicts_with = "api_name", help = "Clear all response cache")]
513        all: bool,
514    },
515    /// Show response cache statistics
516    #[command(long_about = "Display statistics about cached API responses.\n\n\
517                      Shows cache size, number of entries, and hit/miss rates.\n\
518                      Useful for monitoring cache effectiveness and disk usage.\n\n\
519                      Examples:\n  \
520                      aperture config cache-stats myapi     # Stats for specific API\n  \
521                      aperture config cache-stats           # Stats for all APIs")]
522    CacheStats {
523        /// Name of the API specification to show stats for (omit for all APIs)
524        api_name: Option<String>,
525    },
526    /// Set a global configuration setting
527    #[command(long_about = "Set a global configuration setting value.\n\n\
528                      Supports dot-notation for nested settings and type-safe validation.\n\
529                      The configuration file comments and formatting are preserved.\n\n\
530                      Available settings:\n  \
531                      default_timeout_secs              (integer)  - Default timeout for API requests\n  \
532                      agent_defaults.json_errors        (boolean)  - Output errors as JSON by default\n  \
533                      retry_defaults.max_attempts       (integer)  - Max retry attempts (0 = disabled)\n  \
534                      retry_defaults.initial_delay_ms   (integer)  - Initial retry delay in ms\n  \
535                      retry_defaults.max_delay_ms       (integer)  - Maximum retry delay cap in ms\n\n\
536                      Examples:\n  \
537                      aperture config set default_timeout_secs 60\n  \
538                      aperture config set agent_defaults.json_errors true\n  \
539                      aperture config set retry_defaults.max_attempts 3")]
540    Set {
541        /// Setting key (use `config settings` to see all available keys)
542        key: String,
543        /// Value to set (validated against expected type)
544        value: String,
545    },
546    /// Get a global configuration setting value
547    #[command(
548        long_about = "Get the current value of a global configuration setting.\n\n\
549                      Supports dot-notation for nested settings.\n\
550                      Use `config settings` to see all available keys.\n\n\
551                      Examples:\n  \
552                      aperture config get default_timeout_secs\n  \
553                      aperture config get retry_defaults.max_attempts\n  \
554                      aperture config get default_timeout_secs --json"
555    )]
556    Get {
557        /// Setting key to retrieve (use `config settings` to see all available keys)
558        key: String,
559        /// Output as JSON
560        #[arg(long, help = "Output as JSON")]
561        json: bool,
562    },
563    /// List all available configuration settings
564    #[command(
565        long_about = "Display all available configuration settings and their current values.\n\n\
566                      Shows the key name, current value, type, and description for each\n\
567                      setting. Use this to discover available settings and their defaults.\n\n\
568                      Examples:\n  \
569                      aperture config settings\n  \
570                      aperture config settings --json"
571    )]
572    Settings {
573        /// Output as JSON
574        #[arg(long, help = "Output as JSON")]
575        json: bool,
576    },
577}