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 Json,
14 Yaml,
16 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 #[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 #[arg(long, global = true, help = "Output errors in JSON format")]
51 pub json_errors: bool,
52
53 #[arg(
56 long,
57 short = 'q',
58 global = true,
59 help = "Suppress informational output"
60 )]
61 pub quiet: bool,
62
63 #[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 #[arg(long, global = true, help = "Show request details without executing")]
74 pub dry_run: bool,
75
76 #[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 #[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 #[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 #[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 #[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 #[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 #[arg(
134 long,
135 global = true,
136 help = "Enable response caching (can speed up repeated requests)"
137 )]
138 pub cache: bool,
139
140 #[arg(
142 long,
143 global = true,
144 conflicts_with = "cache",
145 help = "Disable response caching"
146 )]
147 pub no_cache: bool,
148
149 #[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 #[arg(
160 long,
161 global = true,
162 help = "Use positional arguments for path parameters (legacy syntax)"
163 )]
164 pub positional_args: bool,
165
166 #[arg(
172 long,
173 global = true,
174 help = "Stream all pages as NDJSON (auto-detects pagination strategy)"
175 )]
176 pub auto_paginate: bool,
177
178 #[arg(
180 long,
181 global = true,
182 value_name = "N",
183 help = "Maximum retry attempts (0 = disabled, overrides config)"
184 )]
185 pub retry: Option<u32>,
186
187 #[arg(
189 long,
190 global = true,
191 value_name = "DURATION",
192 help = "Initial retry delay (e.g., '500ms', '1s', '2s')"
193 )]
194 pub retry_delay: Option<String>,
195
196 #[arg(
198 long,
199 global = true,
200 value_name = "DURATION",
201 help = "Maximum retry delay cap (e.g., '30s', '1m')"
202 )]
203 pub retry_max_delay: Option<String>,
204
205 #[arg(
207 long,
208 global = true,
209 help = "Allow retrying non-idempotent requests without idempotency key"
210 )]
211 pub force_retry: bool,
212
213 #[command(subcommand)]
214 pub command: Commands,
215}
216
217#[derive(Subcommand, Debug)]
218pub enum Commands {
219 #[command(long_about = "Manage your collection of OpenAPI specifications.\n\n\
221 Add specifications to make their operations available as commands,\n\
222 list currently registered specs, remove unused ones, or edit\n\
223 existing specifications in your default editor.")]
224 Config {
225 #[command(subcommand)]
226 command: ConfigCommands,
227 },
228 #[command(
230 long_about = "Display a tree-like summary of all available commands for an API.\n\n\
231 Shows operations organized by tags, making it easy to discover\n\
232 what functionality is available in a registered API specification.\n\
233 This provides an overview without having to use --help on each operation.\n\n\
234 Example:\n \
235 aperture list-commands myapi"
236 )]
237 ListCommands {
238 context: String,
241 },
242 #[command(
244 long_about = "Execute operations from a registered API specification.\n\n\
245 The context refers to the name you gave when adding the spec.\n\
246 Commands are dynamically generated based on the OpenAPI specification,\n\
247 organized by tags (e.g., 'users', 'posts', 'orders').\n\n\
248 Examples:\n \
249 aperture api myapi users get-user --id 123\n \
250 aperture api myapi posts create-post --body '{\"title\":\"Hello\"}'\n \
251 aperture api myapi --help # See available operations"
252 )]
253 Api {
254 context: String,
257 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
259 args: Vec<String>,
260 },
261 #[command(long_about = "Search for API operations by keyword or pattern.\n\n\
263 Search through all registered API specifications to find\n\
264 relevant operations. The search includes operation IDs,\n\
265 descriptions, paths, and HTTP methods.\n\n\
266 Examples:\n \
267 aperture search 'list users' # Find user listing operations\n \
268 aperture search 'POST create' # Find POST operations with 'create'\n \
269 aperture search issues --api sm # Search only in 'sm' API\n \
270 aperture search 'get.*by.*id' # Regex pattern search")]
271 Search {
272 query: String,
274 #[arg(long, value_name = "API", help = "Search only in specified API")]
277 api: Option<String>,
278 #[arg(long, help = "Show detailed information for each result")]
280 verbose: bool,
281 },
282 #[command(
284 name = "exec",
285 long_about = "Execute API operations using shortcuts instead of full paths.\n\n\
286 This command attempts to resolve shortcuts to their full command paths:\n\
287 - Direct operation IDs: getUserById --id 123\n\
288 - HTTP method + path: GET /users/123\n\
289 - Tag-based shortcuts: users list\n\n\
290 When multiple matches are found, you'll get suggestions to choose from.\n\n\
291 Examples:\n \
292 aperture exec getUserById --id 123\n \
293 aperture exec GET /users/123\n \
294 aperture exec users list"
295 )]
296 Exec {
297 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
299 args: Vec<String>,
300 },
301 #[command(
303 long_about = "Get comprehensive documentation for APIs and operations.\n\n\
304 This provides detailed information including parameters, examples,\n\
305 response schemas, and authentication requirements. Use it to learn\n\
306 about available functionality without trial and error.\n\n\
307 Examples:\n \
308 aperture docs # Interactive help menu\n \
309 aperture docs myapi # API overview\n \
310 aperture docs myapi users get-user # Detailed command help"
311 )]
312 Docs {
313 api: Option<String>,
316 tag: Option<String>,
318 operation: Option<String>,
320 #[arg(long, help = "Enhanced formatting with examples and tips")]
322 enhanced: bool,
323 },
324 #[command(
326 long_about = "Display comprehensive API overview with statistics and examples.\n\n\
327 Shows operation counts, method distribution, available categories,\n\
328 and sample commands to help you get started quickly with any API.\n\n\
329 Examples:\n \
330 aperture overview myapi\n \
331 aperture overview --all # Overview of all registered APIs"
332 )]
333 Overview {
334 api: Option<String>,
337 #[arg(long, conflicts_with = "api", help = "Show overview for all APIs")]
339 all: bool,
340 },
341}
342
343#[derive(Subcommand, Debug)]
344pub enum ConfigCommands {
345 #[command(
347 long_about = "Add an OpenAPI 3.x specification to your configuration.\n\n\
348 This validates the specification, extracts operations, and creates\n\
349 a cached representation for fast command generation. The spec name\n\
350 becomes the context for executing API operations.\n\n\
351 Supported formats: YAML (.yaml, .yml)\n\
352 Supported auth: API Key, Bearer Token\n\n\
353 Examples:\n \
354 aperture config add myapi ./openapi.yaml\n \
355 aperture config add myapi https://api.example.com/openapi.yaml"
356 )]
357 Add {
358 name: String,
361 file_or_url: String,
363 #[arg(long, help = "Replace the specification if it already exists")]
365 force: bool,
366 #[arg(
368 long,
369 help = "Reject entire spec if any endpoints have unsupported content types (e.g., multipart/form-data, XML). Default behavior skips unsupported endpoints with warnings."
370 )]
371 strict: bool,
372 },
373 #[command(
375 long_about = "Display all currently registered API specifications.\n\n\
376 Shows the names you can use as contexts with 'aperture api'.\n\
377 Use this to see what APIs are available for command generation."
378 )]
379 List {
380 #[arg(long, help = "Show detailed information about each API")]
382 verbose: bool,
383 },
384 #[command(
386 long_about = "Remove a registered API specification and its cached data.\n\n\
387 This removes both the original specification file and the\n\
388 generated cache, making the API operations unavailable.\n\
389 Use 'aperture config list' to see available specifications."
390 )]
391 Remove {
392 name: String,
395 },
396 #[command(
398 long_about = "Open an API specification in your default text editor.\n\n\
399 Uses the $EDITOR environment variable to determine which editor\n\
400 to use. After editing, you may need to re-add the specification\n\
401 to update the cached representation.\n\n\
402 Example:\n \
403 export EDITOR=vim\n \
404 aperture config edit myapi"
405 )]
406 Edit {
407 name: String,
410 },
411 #[command(long_about = "Set the base URL for an API specification.\n\n\
413 This overrides the base URL from the OpenAPI spec and the\n\
414 APERTURE_BASE_URL environment variable. You can set a general\n\
415 override or environment-specific URLs.\n\n\
416 Examples:\n \
417 aperture config set-url myapi https://api.example.com\n \
418 aperture config set-url myapi --env staging https://staging.example.com\n \
419 aperture config set-url myapi --env prod https://prod.example.com")]
420 SetUrl {
421 name: String,
424 url: String,
426 #[arg(long, value_name = "ENV", help = "Set URL for specific environment")]
428 env: Option<String>,
429 },
430 #[command(
432 long_about = "Display the base URL configuration for an API specification.\n\n\
433 Shows the configured base URL override and any environment-specific\n\
434 URLs. Also displays what URL would be used based on current\n\
435 environment settings.\n\n\
436 Example:\n \
437 aperture config get-url myapi"
438 )]
439 GetUrl {
440 name: String,
443 },
444 #[command(
446 long_about = "Display all configured base URLs across all API specifications.\n\n\
447 Shows general overrides and environment-specific configurations\n\
448 for each registered API. Useful for reviewing your URL settings\n\
449 at a glance."
450 )]
451 ListUrls {},
452 #[command(
454 long_about = "Configure authentication secrets for API specifications.\n\n\
455 This allows you to set environment variable mappings for security\n\
456 schemes without modifying the OpenAPI specification file. These\n\
457 settings take precedence over x-aperture-secret extensions.\n\n\
458 Examples:\n \
459 aperture config set-secret myapi bearerAuth --env API_TOKEN\n \
460 aperture config set-secret myapi apiKey --env API_KEY\n \
461 aperture config set-secret myapi --interactive"
462 )]
463 SetSecret {
464 api_name: String,
467 scheme_name: Option<String>,
469 #[arg(long, value_name = "VAR", help = "Environment variable name")]
471 env: Option<String>,
472 #[arg(long, conflicts_with_all = ["scheme_name", "env"], help = "Configure secrets interactively")]
474 interactive: bool,
475 },
476 #[command(
478 long_about = "Display configured secret mappings for an API specification.\n\n\
479 Shows which security schemes are configured with environment\n\
480 variables and which ones still rely on x-aperture-secret\n\
481 extensions or are undefined.\n\n\
482 Example:\n \
483 aperture config list-secrets myapi"
484 )]
485 ListSecrets {
486 api_name: String,
489 },
490 #[command(
492 long_about = "Remove a configured secret mapping for a specific security scheme.\n\n\
493 This will remove the environment variable mapping for the specified\n\
494 security scheme, causing it to fall back to x-aperture-secret\n\
495 extensions or become undefined.\n\n\
496 Examples:\n \
497 aperture config remove-secret myapi bearerAuth\n \
498 aperture config remove-secret myapi apiKey"
499 )]
500 RemoveSecret {
501 api_name: String,
504 scheme_name: String,
506 },
507 #[command(
509 long_about = "Remove all configured secret mappings for an API specification.\n\n\
510 This will remove all environment variable mappings for the API,\n\
511 causing all security schemes to fall back to x-aperture-secret\n\
512 extensions or become undefined. Use with caution.\n\n\
513 Examples:\n \
514 aperture config clear-secrets myapi\n \
515 aperture config clear-secrets myapi --force"
516 )]
517 ClearSecrets {
518 api_name: String,
521 #[arg(long, help = "Skip confirmation prompt")]
523 force: bool,
524 },
525 #[command(
527 long_about = "Regenerate binary cache files for API specifications.\n\n\
528 This is useful when cache files become corrupted or when upgrading\n\
529 between versions of Aperture that have incompatible cache formats.\n\
530 You can reinitialize all specs or target a specific one.\n\n\
531 Examples:\n \
532 aperture config reinit --all # Reinitialize all specs\n \
533 aperture config reinit myapi # Reinitialize specific spec"
534 )]
535 Reinit {
536 context: Option<String>,
539 #[arg(long, conflicts_with = "context", help = "Reinitialize all specs")]
541 all: bool,
542 },
543 #[command(long_about = "Clear cached API responses to free up disk space.\n\n\
545 You can clear cache for a specific API or all cached responses.\n\
546 This is useful when you want to ensure fresh data from the API\n\
547 or free up disk space.\n\n\
548 Examples:\n \
549 aperture config clear-cache myapi # Clear cache for specific API\n \
550 aperture config clear-cache --all # Clear all cached responses")]
551 ClearCache {
552 api_name: Option<String>,
555 #[arg(long, conflicts_with = "api_name", help = "Clear all response cache")]
557 all: bool,
558 },
559 #[command(long_about = "Display statistics about cached API responses.\n\n\
561 Shows cache size, number of entries, and hit/miss rates.\n\
562 Useful for monitoring cache effectiveness and disk usage.\n\n\
563 Examples:\n \
564 aperture config cache-stats myapi # Stats for specific API\n \
565 aperture config cache-stats # Stats for all APIs")]
566 CacheStats {
567 api_name: Option<String>,
570 },
571 #[command(long_about = "Set a global configuration setting value.\n\n\
573 Supports dot-notation for nested settings and type-safe validation.\n\
574 The configuration file comments and formatting are preserved.\n\n\
575 Available settings:\n \
576 default_timeout_secs (integer) - Default timeout for API requests\n \
577 agent_defaults.json_errors (boolean) - Output errors as JSON by default\n \
578 retry_defaults.max_attempts (integer) - Max retry attempts (0 = disabled)\n \
579 retry_defaults.initial_delay_ms (integer) - Initial retry delay in ms\n \
580 retry_defaults.max_delay_ms (integer) - Maximum retry delay cap in ms\n\n\
581 Examples:\n \
582 aperture config set default_timeout_secs 60\n \
583 aperture config set agent_defaults.json_errors true\n \
584 aperture config set retry_defaults.max_attempts 3")]
585 Set {
586 key: String,
588 value: String,
590 },
591 #[command(
593 long_about = "Get the current value of a global configuration setting.\n\n\
594 Supports dot-notation for nested settings.\n\
595 Use `config settings` to see all available keys.\n\n\
596 Examples:\n \
597 aperture config get default_timeout_secs\n \
598 aperture config get retry_defaults.max_attempts\n \
599 aperture config get default_timeout_secs --json"
600 )]
601 Get {
602 key: String,
604 #[arg(long, help = "Output as JSON")]
606 json: bool,
607 },
608 #[command(
610 long_about = "Display all available configuration settings and their current values.\n\n\
611 Shows the key name, current value, type, and description for each\n\
612 setting. Use this to discover available settings and their defaults.\n\n\
613 Examples:\n \
614 aperture config settings\n \
615 aperture config settings --json"
616 )]
617 Settings {
618 #[arg(long, help = "Output as JSON")]
620 json: bool,
621 },
622 #[command(
624 name = "set-mapping",
625 long_about = "Customize the CLI command tree for an API specification.\n\n\
626 Rename tag groups, rename operations, add aliases, or hide commands\n\
627 without modifying the original OpenAPI spec. Changes take effect\n\
628 after `config reinit`.\n\n\
629 Examples:\n \
630 aperture config set-mapping myapi --group \"User Management\" users\n \
631 aperture config set-mapping myapi --operation getUserById --name fetch\n \
632 aperture config set-mapping myapi --operation getUserById --alias get\n \
633 aperture config set-mapping myapi --operation deleteUser --hidden"
634 )]
635 SetMapping {
636 api_name: String,
638 #[arg(long, num_args = 2, value_names = ["ORIGINAL", "NEW_NAME"], conflicts_with = "operation")]
640 group: Option<Vec<String>>,
641 #[arg(long, value_name = "OPERATION_ID")]
643 operation: Option<String>,
644 #[arg(long, requires = "operation", value_name = "NAME")]
646 name: Option<String>,
647 #[arg(long = "op-group", requires = "operation", value_name = "GROUP")]
649 op_group: Option<String>,
650 #[arg(long, requires = "operation", value_name = "ALIAS")]
652 alias: Option<String>,
653 #[arg(long, requires = "operation", value_name = "ALIAS")]
655 remove_alias: Option<String>,
656 #[arg(long, requires = "operation")]
658 hidden: bool,
659 #[arg(long, requires = "operation", conflicts_with = "hidden")]
661 visible: bool,
662 },
663 #[command(
665 name = "list-mappings",
666 long_about = "Display all custom command mappings for an API specification.\n\n\
667 Shows group renames, operation renames, aliases, and hidden flags.\n\n\
668 Example:\n \
669 aperture config list-mappings myapi"
670 )]
671 ListMappings {
672 api_name: String,
674 },
675 #[command(
677 name = "remove-mapping",
678 long_about = "Remove a custom command mapping for an API specification.\n\n\
679 Removes a group rename or an operation mapping. Changes take effect\n\
680 after `config reinit`.\n\n\
681 Examples:\n \
682 aperture config remove-mapping myapi --group \"User Management\"\n \
683 aperture config remove-mapping myapi --operation getUserById"
684 )]
685 RemoveMapping {
686 api_name: String,
688 #[arg(long, value_name = "ORIGINAL", conflicts_with = "operation")]
690 group: Option<String>,
691 #[arg(long, value_name = "OPERATION_ID")]
693 operation: Option<String>,
694 },
695}