Skip to main content

apcore_cli/
lib.rs

1// apcore-cli — Command-line interface for apcore modules
2// Library root: re-exports all public API items.
3// Protocol spec: FE-01 through FE-11, SEC-01 through SEC-04
4
5pub mod approval;
6pub mod cli;
7pub mod config;
8pub mod discovery;
9pub mod display_helpers;
10pub mod fs_discoverer;
11pub mod init_cmd;
12pub mod output;
13pub mod ref_resolver;
14pub mod schema_parser;
15pub mod security;
16pub mod shell;
17pub mod strategy;
18pub mod system_cmd;
19pub mod validate;
20
21// Internal sandbox runner — not part of the public API surface, but must be
22// pub so the binary entry point (main.rs) can invoke run_sandbox_subprocess().
23#[doc(hidden)]
24pub mod _sandbox_runner;
25
26// Exit codes as defined in the API contract.
27pub const EXIT_SUCCESS: i32 = 0;
28pub const EXIT_MODULE_EXECUTE_ERROR: i32 = 1;
29pub const EXIT_INVALID_INPUT: i32 = 2;
30pub const EXIT_MODULE_NOT_FOUND: i32 = 44;
31pub const EXIT_SCHEMA_VALIDATION_ERROR: i32 = 45;
32pub const EXIT_APPROVAL_DENIED: i32 = 46;
33pub const EXIT_CONFIG_NOT_FOUND: i32 = 47;
34pub const EXIT_SCHEMA_CIRCULAR_REF: i32 = 48;
35pub const EXIT_ACL_DENIED: i32 = 77;
36// Config Bus errors (apcore >= 0.15.0)
37// All four namespace/env errors share exit code 78 per protocol spec —
38// the spec groups them into a single "config namespace error" category.
39pub const EXIT_CONFIG_NAMESPACE_RESERVED: i32 = 78;
40pub const EXIT_CONFIG_NAMESPACE_DUPLICATE: i32 = 78;
41pub const EXIT_CONFIG_ENV_PREFIX_CONFLICT: i32 = 78;
42pub const EXIT_CONFIG_ENV_MAP_CONFLICT: i32 = 78;
43pub const EXIT_CONFIG_MOUNT_ERROR: i32 = 66;
44pub const EXIT_CONFIG_BIND_ERROR: i32 = 65;
45pub const EXIT_ERROR_FORMATTER_DUPLICATE: i32 = 70;
46pub const EXIT_SIGINT: i32 = 130;
47
48// ---------------------------------------------------------------------------
49// CliConfig — high-level configuration for embedded CLI usage.
50// ---------------------------------------------------------------------------
51
52/// Configuration for creating a CLI that uses a pre-populated registry
53/// instead of filesystem discovery.
54///
55/// Frameworks that register modules at runtime (e.g. apflow's bridge) can
56/// build their own [`RegistryProvider`] + [`ModuleExecutor`] and pass them
57/// here to skip the default filesystem scan.
58///
59/// # Example
60/// ```ignore
61/// use std::sync::Arc;
62///
63/// let config = apcore_cli::CliConfig {
64///     prog_name: Some("myapp".to_string()),
65///     registry: Some(Arc::new(my_provider)),
66///     executor: Some(Arc::new(my_executor)),
67///     ..Default::default()
68/// };
69/// // Use config.registry / config.executor at dispatch time instead of
70/// // performing filesystem discovery with FsDiscoverer.
71/// ```
72pub struct CliConfig {
73    /// Override the program name shown in help text.
74    pub prog_name: Option<String>,
75    /// Override extensions directory (only used when `registry` is None).
76    pub extensions_dir: Option<String>,
77    /// Pre-populated registry provider. When set, skips filesystem discovery.
78    pub registry: Option<std::sync::Arc<dyn discovery::RegistryProvider>>,
79    /// Pre-built module executor. When set, skips executor construction.
80    pub executor: Option<std::sync::Arc<dyn cli::ModuleExecutor>>,
81    /// Extra custom commands to add to the CLI root. Each entry is a
82    /// `clap::Command` that will be registered as a subcommand.
83    pub extra_commands: Vec<clap::Command>,
84    /// Group depth for multi-level module grouping (default: 1).
85    /// Higher values allow deeper dotted-name grouping.
86    pub group_depth: usize,
87}
88
89impl Default for CliConfig {
90    fn default() -> Self {
91        Self {
92            prog_name: None,
93            extensions_dir: None,
94            registry: None,
95            executor: None,
96            extra_commands: Vec::new(),
97            group_depth: 1,
98        }
99    }
100}
101
102// Re-export primary public types at crate root.
103pub use approval::{check_approval, ApprovalError};
104pub use cli::{
105    build_module_command, build_module_command_with_limit, collect_input,
106    collect_input_from_reader, get_docs_url, is_verbose_help, set_audit_logger, set_docs_url,
107    set_executables, set_verbose_help, validate_module_id, GroupedModuleGroup, ModuleExecutor,
108};
109pub use config::ConfigResolver;
110pub use discovery::{
111    cmd_describe, cmd_list, cmd_list_enhanced, register_discovery_commands, ApCoreRegistryProvider,
112    DiscoveryError, ListOptions, RegistryProvider,
113};
114pub use display_helpers::{get_cli_display_fields, get_display};
115pub use init_cmd::{handle_init, init_command};
116// Test utilities — available but hidden from docs.
117// Gated behind cfg(test) for unit tests and the test-support feature for
118// integration tests. Excluded from production builds.
119#[cfg(any(test, feature = "test-support"))]
120#[doc(hidden)]
121pub use discovery::{mock_module, MockRegistry};
122pub use fs_discoverer::FsDiscoverer;
123pub use output::{format_exec_result, format_module_detail, format_module_list, resolve_format};
124pub use ref_resolver::resolve_refs;
125pub use schema_parser::{
126    extract_help_with_limit, reconvert_enum_values, schema_to_clap_args,
127    schema_to_clap_args_with_limit, BoolFlagPair, SchemaArgs, SchemaParserError, HELP_TEXT_MAX_LEN,
128};
129pub use security::{AuditLogger, AuthProvider, ConfigEncryptor, Sandbox};
130pub use shell::{
131    build_program_man_page, build_synopsis, cmd_completion, cmd_man, completion_command,
132    generate_man_page, has_man_flag, register_shell_commands, ShellError, KNOWN_BUILTINS,
133};
134pub use strategy::{
135    describe_pipeline_command, dispatch_describe_pipeline, register_pipeline_command,
136};
137pub use system_cmd::{register_system_commands, SYSTEM_COMMANDS};
138pub use validate::{
139    dispatch_validate, format_preflight_result, register_validate_command, validate_command,
140};
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn cli_config_default_has_all_none() {
148        let config = CliConfig::default();
149        assert!(config.prog_name.is_none());
150        assert!(config.extensions_dir.is_none());
151        assert!(config.registry.is_none());
152        assert!(config.executor.is_none());
153        assert!(config.extra_commands.is_empty());
154        assert_eq!(config.group_depth, 1);
155    }
156
157    #[test]
158    fn cli_config_accepts_pre_populated_registry() {
159        let mock_registry = discovery::MockRegistry::new(vec![]);
160        let config = CliConfig {
161            prog_name: Some("test-app".to_string()),
162            registry: Some(std::sync::Arc::new(mock_registry)),
163            ..Default::default()
164        };
165        assert_eq!(config.prog_name.as_deref(), Some("test-app"));
166        assert!(config.registry.is_some());
167        assert!(config.executor.is_none());
168    }
169}