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.
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/CHANGELOG.md +37 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/PKG-INFO +20 -5
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/README.md +18 -3
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/pyproject.toml +2 -2
- apcore_cli-0.6.0/src/apcore_cli/__init__.py +35 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/__main__.py +148 -55
- apcore_cli-0.6.0/src/apcore_cli/approval.py +235 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/cli.py +397 -15
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/config.py +21 -3
- apcore_cli-0.6.0/src/apcore_cli/discovery.py +221 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/output.py +72 -12
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/shell.py +51 -2
- apcore_cli-0.6.0/src/apcore_cli/strategy.py +135 -0
- apcore_cli-0.6.0/src/apcore_cli/system_cmd.py +318 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_config.py +44 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_discovery.py +1 -1
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_integration.py +37 -0
- apcore_cli-0.4.1/src/apcore_cli/__init__.py +0 -9
- apcore_cli-0.4.1/src/apcore_cli/approval.py +0 -169
- apcore_cli-0.4.1/src/apcore_cli/discovery.py +0 -102
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/.github/CODEOWNERS +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/.github/copilot-ignore +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/.github/workflows/ci.yml +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/.gitignore +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/.gitmessage +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/.pre-commit-config.yaml +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/CLAUDE.md +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/commands/ops.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/math/add.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/math/multiply.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/sysutil/disk.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/sysutil/env.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/sysutil/info.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/text/reverse.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/text/upper.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/extensions/text/wordcount.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/examples/run_examples.sh +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/approval-gate.md +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/config-resolver.md +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/core-dispatcher.md +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/discovery.md +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/grouped-commands.md +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/output-formatter.md +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/overview.md +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/schema-parser.md +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/security-manager.md +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/shell-integration.md +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/planning/state.json +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/_sandbox_runner.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/display_helpers.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/init_cmd.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/ref_resolver.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/schema_parser.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/security/__init__.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/security/audit.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/security/auth.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/security/config_encryptor.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/src/apcore_cli/security/sandbox.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/__init__.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/conftest.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_approval.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_bugfixes.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_cli.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_e2e.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_init_cmd.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_output.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_ref_resolver.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_schema_parser.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_security/__init__.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_security/test_audit.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_security/test_auth.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_security/test_config_encryptor.py +0 -0
- {apcore_cli-0.4.1 → apcore_cli-0.6.0}/tests/test_security/test_sandbox.py +0 -0
- {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.
|
|
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.
|
|
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
|
|
133
|
-
from apcore_cli.__main__ import create_cli
|
|
132
|
+
from apcore_cli import create_cli
|
|
134
133
|
|
|
135
|
-
# Build the CLI from
|
|
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
|
|
96
|
-
from apcore_cli.__main__ import create_cli
|
|
95
|
+
from apcore_cli import create_cli
|
|
97
96
|
|
|
98
|
-
# Build the CLI from
|
|
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.
|
|
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.
|
|
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
|
-
|
|
125
|
-
|
|
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
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
147
|
-
|
|
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
|
-
|
|
153
|
-
if commands_dir is not None:
|
|
180
|
+
registry = _Registry(extensions_dir=ext_dir)
|
|
154
181
|
try:
|
|
155
|
-
from
|
|
156
|
-
|
|
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("
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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(
|
|
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
|
|