Skip to main content

aperture_cli/cli/
mod.rs

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