apcore-cli 0.4.1__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/CHANGELOG.md +37 -0
  2. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/PKG-INFO +20 -5
  3. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/README.md +18 -3
  4. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/pyproject.toml +2 -2
  5. apcore_cli-0.6.0/src/apcore_cli/__init__.py +35 -0
  6. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/__main__.py +148 -55
  7. apcore_cli-0.6.0/src/apcore_cli/approval.py +235 -0
  8. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/cli.py +397 -15
  9. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/config.py +21 -3
  10. apcore_cli-0.6.0/src/apcore_cli/discovery.py +221 -0
  11. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/output.py +72 -12
  12. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/shell.py +51 -2
  13. apcore_cli-0.6.0/src/apcore_cli/strategy.py +135 -0
  14. apcore_cli-0.6.0/src/apcore_cli/system_cmd.py +318 -0
  15. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_config.py +44 -0
  16. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_discovery.py +1 -1
  17. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_integration.py +37 -0
  18. apcore_cli-0.4.1/src/apcore_cli/__init__.py +0 -9
  19. apcore_cli-0.4.1/src/apcore_cli/approval.py +0 -169
  20. apcore_cli-0.4.1/src/apcore_cli/discovery.py +0 -102
  21. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/.github/CODEOWNERS +0 -0
  22. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/.github/copilot-ignore +0 -0
  23. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/.github/workflows/ci.yml +0 -0
  24. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/.gitignore +0 -0
  25. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/.gitmessage +0 -0
  26. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/.pre-commit-config.yaml +0 -0
  27. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/CLAUDE.md +0 -0
  28. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/commands/ops.py +0 -0
  29. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/math/add.py +0 -0
  30. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/math/multiply.py +0 -0
  31. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/sysutil/disk.py +0 -0
  32. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/sysutil/env.py +0 -0
  33. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/sysutil/info.py +0 -0
  34. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/text/reverse.py +0 -0
  35. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/text/upper.py +0 -0
  36. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/text/wordcount.py +0 -0
  37. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/run_examples.sh +0 -0
  38. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/approval-gate.md +0 -0
  39. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/config-resolver.md +0 -0
  40. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/core-dispatcher.md +0 -0
  41. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/discovery.md +0 -0
  42. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/grouped-commands.md +0 -0
  43. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/output-formatter.md +0 -0
  44. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/overview.md +0 -0
  45. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/schema-parser.md +0 -0
  46. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/security-manager.md +0 -0
  47. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/shell-integration.md +0 -0
  48. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/state.json +0 -0
  49. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/_sandbox_runner.py +0 -0
  50. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/display_helpers.py +0 -0
  51. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/init_cmd.py +0 -0
  52. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/ref_resolver.py +0 -0
  53. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/schema_parser.py +0 -0
  54. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/security/__init__.py +0 -0
  55. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/security/audit.py +0 -0
  56. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/security/auth.py +0 -0
  57. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/security/config_encryptor.py +0 -0
  58. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/security/sandbox.py +0 -0
  59. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/__init__.py +0 -0
  60. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/conftest.py +0 -0
  61. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_approval.py +0 -0
  62. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_bugfixes.py +0 -0
  63. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_cli.py +0 -0
  64. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_e2e.py +0 -0
  65. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_init_cmd.py +0 -0
  66. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_output.py +0 -0
  67. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_ref_resolver.py +0 -0
  68. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_schema_parser.py +0 -0
  69. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_security/__init__.py +0 -0
  70. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_security/test_audit.py +0 -0
  71. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_security/test_auth.py +0 -0
  72. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_security/test_config_encryptor.py +0 -0
  73. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_security/test_sandbox.py +0 -0
  74. {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_shell.py +0 -0
@@ -6,6 +6,43 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
 
9
+ ## [0.6.0] - 2026-04-06
10
+
11
+ ### Changed
12
+
13
+ - **Dependency bump**: requires `apcore >= 0.17.1` (was `>= 0.15.1`). Adds Execution Pipeline Strategy, Config Bus enhancements, Pipeline v2 declarative step metadata, `minimal` strategy preset.
14
+ - **Schema parser**: Required schema properties now correctly enforced at CLI option level (was silently optional).
15
+ - **Approval gate**: Fixed inverted logic in annotation type guard; `check_approval()` now accepts `timeout` parameter.
16
+
17
+ ### Added
18
+
19
+ - **FE-11: Usability Enhancements** — 11 new capabilities:
20
+ - `--dry-run` preflight mode via `Executor.validate()`. Standalone `validate` command.
21
+ - System management commands: `health`, `usage`, `enable`, `disable`, `reload`, `config get`/`config set`. Graceful no-op when system modules unavailable.
22
+ - Enhanced error output: structured JSON with `ai_guidance`, `suggestion`, `retryable`, `user_fixable`, `details`. TTY hides machine-only fields.
23
+ - `--trace` pipeline visualization via `call_with_trace()`.
24
+ - `CliApprovalHandler` class implementing apcore `ApprovalHandler` protocol, wired to `Executor.set_approval_handler()`. `--approval-timeout`, `--approval-token` flags.
25
+ - `--stream` JSONL output via `Executor.stream()`.
26
+ - Enhanced `list` command: `--search`, `--status`, `--annotation`, `--sort`, `--reverse`, `--deprecated`, `--deps`.
27
+ - `--strategy` selection: `standard`, `internal`, `testing`, `performance`, `minimal`. `describe-pipeline` command.
28
+ - Output format extensions: `--format csv|yaml|jsonl`, `--fields` dot-path field selection.
29
+ - Multi-level grouping: `cli.group_depth` config key.
30
+ - Custom command extension: `create_cli(extra_commands=[...])` with collision detection.
31
+ - New error code: `CONFIG_ENV_MAP_CONFLICT`.
32
+ - New config keys: `cli.approval_timeout` (60), `cli.strategy` ("standard"), `cli.group_depth` (1).
33
+ - New environment variables: `APCORE_CLI_APPROVAL_TIMEOUT`, `APCORE_CLI_STRATEGY`, `APCORE_CLI_GROUP_DEPTH`.
34
+ - New files: `system_cmd.py`, `strategy.py`.
35
+
36
+ ---
37
+
38
+ ## [0.5.1] - 2026-04-03
39
+
40
+ ### Added
41
+ - **Pre-populated registry support** — `create_cli()` accepts optional `registry` and `executor` parameters. When a pre-populated `Registry` is provided, filesystem discovery is skipped entirely. This enables frameworks that register modules at runtime (e.g. apflow's bridge) to generate CLI commands from their existing registry without requiring an extensions directory.
42
+ - Passing `registry` alone auto-builds an `Executor`; passing `executor` without `registry` raises `ValueError`.
43
+
44
+ ---
45
+
9
46
  ## [0.4.1] - 2026-03-30
10
47
 
11
48
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apcore-cli
3
- Version: 0.4.1
3
+ Version: 0.6.0
4
4
  Summary: Terminal adapter for apcore — execute AI-Perceivable modules from the command line
5
5
  Project-URL: Homepage, https://aiperceivable.com
6
6
  Project-URL: Repository, https://github.com/aiperceivable/apcore-cli-python
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: 3.13
20
20
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
22
  Requires-Python: >=3.11
23
- Requires-Dist: apcore>=0.14.0
23
+ Requires-Dist: apcore>=0.17.1
24
24
  Requires-Dist: click>=8.1
25
25
  Requires-Dist: cryptography>=41.0
26
26
  Requires-Dist: jsonschema>=4.20
@@ -129,14 +129,29 @@ All modules are auto-discovered. CLI flags are auto-generated from each module's
129
129
  ### Programmatic approach (Python API)
130
130
 
131
131
  ```python
132
- from apcore import Registry, Executor
133
- from apcore_cli.__main__ import create_cli
132
+ from apcore_cli import create_cli
134
133
 
135
- # Build the CLI from your registry
134
+ # Build the CLI from an extensions directory (auto-discovers modules)
136
135
  cli = create_cli(extensions_dir="./extensions")
137
136
  cli(standalone_mode=True)
138
137
  ```
139
138
 
139
+ #### Pre-populated registry
140
+
141
+ Frameworks that register modules at runtime (e.g. apflow's bridge) can pass a pre-populated `Registry` directly, skipping filesystem discovery entirely:
142
+
143
+ ```python
144
+ from apcore_cli import create_cli
145
+
146
+ # registry is already populated by your framework
147
+ cli = create_cli(registry=registry, prog_name="myapp")
148
+ cli(standalone_mode=True)
149
+
150
+ # Executor is auto-built from the registry if omitted.
151
+ # You can also provide your own:
152
+ cli = create_cli(registry=registry, executor=executor, prog_name="myapp")
153
+ ```
154
+
140
155
  Or use the `LazyModuleGroup` directly with Click:
141
156
 
142
157
  ```python
@@ -92,14 +92,29 @@ All modules are auto-discovered. CLI flags are auto-generated from each module's
92
92
  ### Programmatic approach (Python API)
93
93
 
94
94
  ```python
95
- from apcore import Registry, Executor
96
- from apcore_cli.__main__ import create_cli
95
+ from apcore_cli import create_cli
97
96
 
98
- # Build the CLI from your registry
97
+ # Build the CLI from an extensions directory (auto-discovers modules)
99
98
  cli = create_cli(extensions_dir="./extensions")
100
99
  cli(standalone_mode=True)
101
100
  ```
102
101
 
102
+ #### Pre-populated registry
103
+
104
+ Frameworks that register modules at runtime (e.g. apflow's bridge) can pass a pre-populated `Registry` directly, skipping filesystem discovery entirely:
105
+
106
+ ```python
107
+ from apcore_cli import create_cli
108
+
109
+ # registry is already populated by your framework
110
+ cli = create_cli(registry=registry, prog_name="myapp")
111
+ cli(standalone_mode=True)
112
+
113
+ # Executor is auto-built from the registry if omitted.
114
+ # You can also provide your own:
115
+ cli = create_cli(registry=registry, executor=executor, prog_name="myapp")
116
+ ```
117
+
103
118
  Or use the `LazyModuleGroup` directly with Click:
104
119
 
105
120
  ```python
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "apcore-cli"
7
- version = "0.4.1"
7
+ version = "0.6.0"
8
8
  description = "Terminal adapter for apcore — execute AI-Perceivable modules from the command line"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -26,7 +26,7 @@ classifiers = [
26
26
  "Environment :: Console",
27
27
  ]
28
28
  dependencies = [
29
- "apcore>=0.14.0",
29
+ "apcore>=0.17.1",
30
30
  "click>=8.1",
31
31
  "jsonschema>=4.20",
32
32
  "rich>=13.0",
@@ -0,0 +1,35 @@
1
+ """apcore-cli: CLI adapter for the apcore module ecosystem."""
2
+
3
+ from importlib.metadata import PackageNotFoundError
4
+ from importlib.metadata import version as _get_version
5
+
6
+ try:
7
+ __version__ = _get_version("apcore-cli")
8
+ except PackageNotFoundError:
9
+ __version__ = "unknown"
10
+
11
+ # Config Bus namespace registration (apcore >= 0.15.0)
12
+ try:
13
+ from apcore import Config
14
+
15
+ Config.register_namespace(
16
+ name="apcore-cli",
17
+ schema=None,
18
+ env_prefix="APCORE_CLI",
19
+ defaults={
20
+ "stdin_buffer_limit": 10_485_760,
21
+ "auto_approve": False,
22
+ "help_text_max_length": 1000,
23
+ "logging_level": "WARNING",
24
+ "approval_timeout": 60,
25
+ "strategy": "standard",
26
+ "group_depth": 1,
27
+ },
28
+ )
29
+ except (ImportError, AttributeError):
30
+ pass # apcore < 0.15.0 or not installed
31
+
32
+ # Public API re-exports
33
+ from apcore_cli.__main__ import create_cli
34
+
35
+ __all__ = ["__version__", "create_cli"]
@@ -5,15 +5,22 @@ from __future__ import annotations
5
5
  import logging
6
6
  import os
7
7
  import sys
8
+ from importlib.metadata import PackageNotFoundError
9
+ from importlib.metadata import version as _get_version
10
+ from typing import Any
8
11
 
9
12
  import click
10
13
 
11
- from apcore_cli import __version__
12
14
  from apcore_cli.cli import GroupedModuleGroup, set_audit_logger, set_verbose_help
13
15
  from apcore_cli.config import ConfigResolver
14
16
  from apcore_cli.discovery import register_discovery_commands
15
17
  from apcore_cli.security.audit import AuditLogger
16
- from apcore_cli.shell import register_shell_commands
18
+ from apcore_cli.shell import configure_man_help, register_shell_commands
19
+
20
+ try:
21
+ __version__ = _get_version("apcore-cli")
22
+ except PackageNotFoundError:
23
+ __version__ = "unknown"
17
24
 
18
25
  logger = logging.getLogger("apcore_cli")
19
26
 
@@ -61,6 +68,9 @@ def create_cli(
61
68
  prog_name: str | None = None,
62
69
  commands_dir: str | None = None,
63
70
  binding_path: str | None = None,
71
+ registry: Any | None = None,
72
+ executor: Any | None = None,
73
+ extra_commands: list[Any] | None = None,
64
74
  ) -> click.Group:
65
75
  """Create the CLI application.
66
76
 
@@ -77,6 +87,12 @@ def create_cli(
77
87
  binding_path: Path to binding.yaml file or directory for display resolution.
78
88
  When set, applies DisplayResolver to convention-scanned modules
79
89
  (requires apcore-toolkit).
90
+ registry: Pre-populated apcore Registry instance. When provided, skips
91
+ filesystem discovery entirely. Useful for frameworks that register
92
+ modules at runtime (e.g. apflow's bridge).
93
+ executor: Pre-built apcore Executor instance. When provided alongside
94
+ registry, skips Executor construction. If omitted but registry
95
+ is provided, an Executor is built from the given registry.
80
96
  """
81
97
  if prog_name is None:
82
98
  prog_name = os.path.basename(sys.argv[0]) or "apcore-cli"
@@ -121,64 +137,91 @@ def create_cli(
121
137
  except (TypeError, ValueError):
122
138
  help_text_max_length = 1000
123
139
 
124
- ext_dir_missing = not os.path.exists(ext_dir)
125
- ext_dir_unreadable = not ext_dir_missing and not os.access(ext_dir, os.R_OK)
126
-
127
- if ext_dir_missing:
128
- click.echo(
129
- f"Error: Extensions directory not found: '{ext_dir}'. Set APCORE_EXTENSIONS_ROOT or verify the path.",
130
- err=True,
131
- )
132
- sys.exit(EXIT_CONFIG_NOT_FOUND)
140
+ if executor is not None and registry is None:
141
+ raise ValueError("executor requires registry pass both or neither")
133
142
 
134
- if ext_dir_unreadable:
135
- click.echo(
136
- f"Error: Cannot read extensions directory: '{ext_dir}'. Check permissions.",
137
- err=True,
138
- )
139
- sys.exit(EXIT_CONFIG_NOT_FOUND)
143
+ if registry is not None:
144
+ # Pre-populated registry provided — skip filesystem discovery.
145
+ try:
146
+ from apcore import Executor as _Executor
140
147
 
141
- try:
142
- from apcore import Executor, Registry
148
+ if executor is None:
149
+ executor = _Executor(registry)
150
+ logger.info("Using pre-populated registry (%d modules).", len(list(registry.list())))
151
+ except Exception as e:
152
+ click.echo(
153
+ f"Error: Failed to initialize executor from provided registry: {e}",
154
+ err=True,
155
+ )
156
+ sys.exit(EXIT_CONFIG_NOT_FOUND)
157
+ else:
158
+ # Standard path: discover modules from filesystem.
159
+ ext_dir_missing = not os.path.exists(ext_dir)
160
+ ext_dir_unreadable = not ext_dir_missing and not os.access(ext_dir, os.R_OK)
161
+
162
+ if ext_dir_missing:
163
+ click.echo(
164
+ f"Error: Extensions directory not found: '{ext_dir}'. Set APCORE_EXTENSIONS_ROOT or verify the path.",
165
+ err=True,
166
+ )
167
+ sys.exit(EXIT_CONFIG_NOT_FOUND)
168
+
169
+ if ext_dir_unreadable:
170
+ click.echo(
171
+ f"Error: Cannot read extensions directory: '{ext_dir}'. Check permissions.",
172
+ err=True,
173
+ )
174
+ sys.exit(EXIT_CONFIG_NOT_FOUND)
143
175
 
144
- registry = Registry(extensions_dir=ext_dir)
145
176
  try:
146
- logger.debug("Loading extensions from %s", ext_dir)
147
- count = registry.discover()
148
- logger.info("Initialized apcore-cli with %d modules.", count)
149
- except Exception as e:
150
- logger.warning("Discovery failed: %s", e)
177
+ from apcore import Executor as _Executor
178
+ from apcore import Registry as _Registry
151
179
 
152
- # Convention module discovery
153
- if commands_dir is not None:
180
+ registry = _Registry(extensions_dir=ext_dir)
154
181
  try:
155
- from apcore_toolkit import RegistryWriter
156
- from apcore_toolkit.convention_scanner import ConventionScanner
157
-
158
- conv_scanner = ConventionScanner()
159
- conv_modules = conv_scanner.scan(commands_dir)
160
- if conv_modules:
161
- if binding_path is not None:
162
- try:
163
- from apcore_toolkit import DisplayResolver
164
-
165
- display_resolver = DisplayResolver()
166
- conv_modules = display_resolver.resolve(conv_modules, binding_path=binding_path)
167
- logger.info("DisplayResolver: applied binding from %s", binding_path)
168
- except ImportError:
169
- logger.warning("DisplayResolver not available in apcore-toolkit")
170
- writer = RegistryWriter()
171
- writer.write(conv_modules, registry)
172
- logger.info("Convention scanner: registered %d modules from %s", len(conv_modules), commands_dir)
173
- except ImportError:
174
- logger.warning("apcore-toolkit not installed — convention module scanning unavailable")
182
+ logger.debug("Loading extensions from %s", ext_dir)
183
+ count = registry.discover()
184
+ logger.info("Initialized apcore-cli with %d modules.", count)
175
185
  except Exception as e:
176
- logger.warning("Convention module scanning failed: %s", e)
177
-
178
- executor = Executor(registry)
179
- except Exception as e:
180
- click.echo(f"Error: Failed to initialize registry: {e}", err=True)
181
- sys.exit(EXIT_CONFIG_NOT_FOUND)
186
+ logger.warning("Discovery failed: %s", e)
187
+
188
+ # Convention module discovery
189
+ if commands_dir is not None:
190
+ try:
191
+ from apcore_toolkit import RegistryWriter
192
+ from apcore_toolkit.convention_scanner import ConventionScanner
193
+
194
+ conv_scanner = ConventionScanner()
195
+ conv_modules = conv_scanner.scan(commands_dir)
196
+ if conv_modules:
197
+ if binding_path is not None:
198
+ try:
199
+ from apcore_toolkit import DisplayResolver
200
+
201
+ display_resolver = DisplayResolver()
202
+ conv_modules = display_resolver.resolve(conv_modules, binding_path=binding_path)
203
+ logger.info(
204
+ "DisplayResolver: applied binding from %s",
205
+ binding_path,
206
+ )
207
+ except ImportError:
208
+ logger.warning("DisplayResolver not available in apcore-toolkit")
209
+ writer = RegistryWriter()
210
+ writer.write(conv_modules, registry)
211
+ logger.info(
212
+ "Convention scanner: registered %d modules from %s",
213
+ len(conv_modules),
214
+ commands_dir,
215
+ )
216
+ except ImportError:
217
+ logger.warning("apcore-toolkit not installed — convention module scanning unavailable")
218
+ except Exception as e:
219
+ logger.warning("Convention module scanning failed: %s", e)
220
+
221
+ executor = _Executor(registry)
222
+ except Exception as e:
223
+ click.echo(f"Error: Failed to initialize registry: {e}", err=True)
224
+ sys.exit(EXIT_CONFIG_NOT_FOUND)
182
225
 
183
226
  # Initialize audit logger
184
227
  try:
@@ -187,6 +230,22 @@ def create_cli(
187
230
  except Exception as e:
188
231
  logger.warning("Failed to initialize audit logger: %s", e)
189
232
 
233
+ # Wire CliApprovalHandler to Executor (FE-11 §3.5)
234
+ try:
235
+ import contextlib
236
+
237
+ from apcore_cli.approval import CliApprovalHandler
238
+
239
+ approval_timeout = 60
240
+ with contextlib.suppress(TypeError, ValueError):
241
+ approval_timeout = int(config.resolve("cli.approval_timeout", env_var="APCORE_CLI_APPROVAL_TIMEOUT") or 60)
242
+ handler = CliApprovalHandler(auto_approve=False, timeout=approval_timeout)
243
+ if hasattr(executor, "set_approval_handler"):
244
+ executor.set_approval_handler(handler)
245
+ logger.debug("CliApprovalHandler wired to Executor (timeout=%ds).", approval_timeout)
246
+ except Exception as e:
247
+ logger.debug("Could not wire CliApprovalHandler: %s", e)
248
+
190
249
  @click.group(
191
250
  cls=GroupedModuleGroup,
192
251
  registry=registry,
@@ -250,17 +309,46 @@ def create_cli(
250
309
  ctx.obj["extensions_dir"] = ext_dir
251
310
  ctx.obj["verbose_help"] = verbose_help
252
311
 
253
- # Register discovery commands
312
+ # Register discovery commands (list, describe)
254
313
  register_discovery_commands(cli, registry)
255
314
 
315
+ # Register validate command (FE-11 §3.1)
316
+ from apcore_cli.discovery import register_validate_command
317
+
318
+ register_validate_command(cli, registry, executor)
319
+
320
+ # Register system management commands (FE-11 §3.2) — no-op if system modules unavailable
321
+ from apcore_cli.system_cmd import register_system_commands
322
+
323
+ register_system_commands(cli, executor)
324
+
325
+ # Register pipeline introspection command (FE-11 §3.8)
326
+ from apcore_cli.strategy import register_pipeline_command
327
+
328
+ register_pipeline_command(cli, executor)
329
+
256
330
  # Register shell integration commands
257
331
  register_shell_commands(cli, prog_name=prog_name)
258
332
 
333
+ # Register --help --man support
334
+ configure_man_help(cli, prog_name, __version__)
335
+
259
336
  # Register init scaffolding command
260
337
  from apcore_cli.init_cmd import register_init_command
261
338
 
262
339
  register_init_command(cli)
263
340
 
341
+ # Register extra commands from downstream projects (FE-11 §3.11)
342
+ if extra_commands:
343
+ from apcore_cli.cli import BUILTIN_COMMANDS
344
+
345
+ for cmd in extra_commands:
346
+ cmd_name = getattr(cmd, "name", None)
347
+ if cmd_name and cmd_name in BUILTIN_COMMANDS:
348
+ msg = f"Extra command '{cmd_name}' conflicts with built-in command."
349
+ raise ValueError(msg)
350
+ cli.add_command(cmd)
351
+
264
352
  return cli
265
353
 
266
354
 
@@ -274,7 +362,12 @@ def main(prog_name: str | None = None) -> None:
274
362
  ext_dir = _extract_extensions_dir()
275
363
  cmd_dir = _extract_commands_dir()
276
364
  bind_path = _extract_binding_path()
277
- cli = create_cli(extensions_dir=ext_dir, prog_name=prog_name, commands_dir=cmd_dir, binding_path=bind_path)
365
+ cli = create_cli(
366
+ extensions_dir=ext_dir,
367
+ prog_name=prog_name,
368
+ commands_dir=cmd_dir,
369
+ binding_path=bind_path,
370
+ )
278
371
  cli(standalone_mode=True)
279
372
 
280
373