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    /// Set secret configuration for an API specification security scheme
293    #[command(
294        long_about = "Configure authentication secrets for API specifications.\n\n\
295                      This allows you to set environment variable mappings for security\n\
296                      schemes without modifying the OpenAPI specification file. These\n\
297                      settings take precedence over x-aperture-secret extensions.\n\n\
298                      Examples:\n  \
299                      aperture config set-secret myapi bearerAuth --env API_TOKEN\n  \
300                      aperture config set-secret myapi apiKey --env API_KEY\n  \
301                      aperture config set-secret myapi --interactive"
302    )]
303    SetSecret {
304        /// Name of the API specification
305        api_name: String,
306        /// Name of the security scheme (omit for interactive mode)
307        scheme_name: Option<String>,
308        /// Environment variable name containing the secret
309        #[arg(long, value_name = "VAR", help = "Environment variable name")]
310        env: Option<String>,
311        /// Interactive mode to configure all undefined secrets
312        #[arg(long, conflicts_with_all = ["scheme_name", "env"], help = "Configure secrets interactively")]
313        interactive: bool,
314    },
315    /// List configured secrets for an API specification
316    #[command(
317        long_about = "Display configured secret mappings for an API specification.\n\n\
318                      Shows which security schemes are configured with environment\n\
319                      variables and which ones still rely on x-aperture-secret\n\
320                      extensions or are undefined.\n\n\
321                      Example:\n  \
322                      aperture config list-secrets myapi"
323    )]
324    ListSecrets {
325        /// Name of the API specification
326        api_name: String,
327    },
328    /// Remove a specific configured secret for an API specification
329    #[command(
330        long_about = "Remove a configured secret mapping for a specific security scheme.\n\n\
331                      This will remove the environment variable mapping for the specified\n\
332                      security scheme, causing it to fall back to x-aperture-secret\n\
333                      extensions or become undefined.\n\n\
334                      Examples:\n  \
335                      aperture config remove-secret myapi bearerAuth\n  \
336                      aperture config remove-secret myapi apiKey"
337    )]
338    RemoveSecret {
339        /// Name of the API specification
340        api_name: String,
341        /// Name of the security scheme to remove
342        scheme_name: String,
343    },
344    /// Clear all configured secrets for an API specification
345    #[command(
346        long_about = "Remove all configured secret mappings for an API specification.\n\n\
347                      This will remove all environment variable mappings for the API,\n\
348                      causing all security schemes to fall back to x-aperture-secret\n\
349                      extensions or become undefined. Use with caution.\n\n\
350                      Examples:\n  \
351                      aperture config clear-secrets myapi\n  \
352                      aperture config clear-secrets myapi --force"
353    )]
354    ClearSecrets {
355        /// Name of the API specification
356        api_name: String,
357        /// Skip confirmation prompt
358        #[arg(long, help = "Skip confirmation prompt")]
359        force: bool,
360    },
361    /// Re-initialize cached specifications
362    #[command(
363        long_about = "Regenerate binary cache files for API specifications.\n\n\
364                      This is useful when cache files become corrupted or when upgrading\n\
365                      between versions of Aperture that have incompatible cache formats.\n\
366                      You can reinitialize all specs or target a specific one.\n\n\
367                      Examples:\n  \
368                      aperture config reinit --all     # Reinitialize all specs\n  \
369                      aperture config reinit myapi     # Reinitialize specific spec"
370    )]
371    Reinit {
372        /// Name of the API specification to reinitialize (omit for --all)
373        context: Option<String>,
374        /// Reinitialize all cached specifications
375        #[arg(long, conflicts_with = "context", help = "Reinitialize all specs")]
376        all: bool,
377    },
378    /// Clear response cache
379    #[command(long_about = "Clear cached API responses to free up disk space.\n\n\
380                      You can clear cache for a specific API or all cached responses.\n\
381                      This is useful when you want to ensure fresh data from the API\n\
382                      or free up disk space.\n\n\
383                      Examples:\n  \
384                      aperture config clear-cache myapi     # Clear cache for specific API\n  \
385                      aperture config clear-cache --all     # Clear all cached responses")]
386    ClearCache {
387        /// Name of the API specification to clear cache for (omit for --all)
388        api_name: Option<String>,
389        /// Clear all cached responses
390        #[arg(long, conflicts_with = "api_name", help = "Clear all response cache")]
391        all: bool,
392    },
393    /// Show response cache statistics
394    #[command(long_about = "Display statistics about cached API responses.\n\n\
395                      Shows cache size, number of entries, and hit/miss rates.\n\
396                      Useful for monitoring cache effectiveness and disk usage.\n\n\
397                      Examples:\n  \
398                      aperture config cache-stats myapi     # Stats for specific API\n  \
399                      aperture config cache-stats           # Stats for all APIs")]
400    CacheStats {
401        /// Name of the API specification to show stats for (omit for all APIs)
402        api_name: Option<String>,
403    },
404}