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(
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 #[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 #[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 #[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 #[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 #[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 context: String,
229 },
230 #[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 context: String,
245 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
247 args: Vec<String>,
248 },
249 #[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 query: String,
262 #[arg(long, value_name = "API", help = "Search only in specified API")]
265 api: Option<String>,
266 #[arg(long, help = "Show detailed information for each result")]
268 verbose: bool,
269 },
270 #[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 #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
287 args: Vec<String>,
288 },
289 #[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: Option<String>,
304 tag: Option<String>,
306 operation: Option<String>,
308 #[arg(long, help = "Enhanced formatting with examples and tips")]
310 enhanced: bool,
311 },
312 #[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: Option<String>,
325 #[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 #[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: String,
349 file_or_url: String,
351 #[arg(long, help = "Replace the specification if it already exists")]
353 force: bool,
354 #[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 #[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 #[arg(long, help = "Show detailed information about each API")]
370 verbose: bool,
371 },
372 #[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: String,
383 },
384 #[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: String,
398 },
399 #[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: String,
412 url: String,
414 #[arg(long, value_name = "ENV", help = "Set URL for specific environment")]
416 env: Option<String>,
417 },
418 #[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: String,
431 },
432 #[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 #[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 api_name: String,
455 scheme_name: Option<String>,
457 #[arg(long, value_name = "VAR", help = "Environment variable name")]
459 env: Option<String>,
460 #[arg(long, conflicts_with_all = ["scheme_name", "env"], help = "Configure secrets interactively")]
462 interactive: bool,
463 },
464 #[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 api_name: String,
477 },
478 #[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 api_name: String,
492 scheme_name: String,
494 },
495 #[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 api_name: String,
509 #[arg(long, help = "Skip confirmation prompt")]
511 force: bool,
512 },
513 #[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 context: Option<String>,
527 #[arg(long, conflicts_with = "context", help = "Reinitialize all specs")]
529 all: bool,
530 },
531 #[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 api_name: Option<String>,
543 #[arg(long, conflicts_with = "api_name", help = "Clear all response cache")]
545 all: bool,
546 },
547 #[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 api_name: Option<String>,
558 },
559 #[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 key: String,
576 value: String,
578 },
579 #[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 key: String,
592 #[arg(long, help = "Output as JSON")]
594 json: bool,
595 },
596 #[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 #[arg(long, help = "Output as JSON")]
608 json: bool,
609 },
610 #[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 api_name: String,
626 #[arg(long, num_args = 2, value_names = ["ORIGINAL", "NEW_NAME"], conflicts_with = "operation")]
628 group: Option<Vec<String>>,
629 #[arg(long, value_name = "OPERATION_ID")]
631 operation: Option<String>,
632 #[arg(long, requires = "operation", value_name = "NAME")]
634 name: Option<String>,
635 #[arg(long = "op-group", requires = "operation", value_name = "GROUP")]
637 op_group: Option<String>,
638 #[arg(long, requires = "operation", value_name = "ALIAS")]
640 alias: Option<String>,
641 #[arg(long, requires = "operation", value_name = "ALIAS")]
643 remove_alias: Option<String>,
644 #[arg(long, requires = "operation")]
646 hidden: bool,
647 #[arg(long, requires = "operation", conflicts_with = "hidden")]
649 visible: bool,
650 },
651 #[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 api_name: String,
662 },
663 #[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 api_name: String,
676 #[arg(long, value_name = "ORIGINAL", conflicts_with = "operation")]
678 group: Option<String>,
679 #[arg(long, value_name = "OPERATION_ID")]
681 operation: Option<String>,
682 },
683}