apcore-cli 0.9.0__tar.gz → 0.10.1__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.9.0 → apcore_cli-0.10.1}/CHANGELOG.md +40 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/PKG-INFO +3 -3
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/pyproject.toml +3 -3
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/config.py +9 -6
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/factory.py +22 -28
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/init_cmd.py +6 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/output.py +1 -17
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/shell.py +0 -43
- apcore_cli-0.10.1/tests/shell_test_utils.py +49 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_e2e.py +2 -1
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_integration.py +2 -1
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_shell.py +2 -1
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_toolkit_integration.py +30 -67
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/.github/CODEOWNERS +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/.github/copilot-ignore +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/.github/workflows/ci.yml +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/.gitignore +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/.gitmessage +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/.pre-commit-config.yaml +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/CLAUDE.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/LICENSE +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/Makefile +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/README.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/examples/README.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/examples/extensions/math/add.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/examples/extensions/math/multiply.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/examples/extensions/sysutil/disk.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/examples/extensions/sysutil/env.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/examples/extensions/sysutil/info.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/examples/extensions/text/reverse.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/examples/extensions/text/upper.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/examples/extensions/text/wordcount.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/examples/run_examples.sh +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/planning/approval-gate.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/planning/config-resolver.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/planning/core-dispatcher.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/planning/discovery.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/planning/exposure-filtering.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/planning/grouped-commands.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/planning/output-formatter.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/planning/overview.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/planning/schema-parser.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/planning/security-manager.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/planning/shell-integration.md +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/planning/state.json +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/__init__.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/__main__.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/_sandbox_runner.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/approval.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/builtin_group.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/cli.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/discovery.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/display_helpers.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/exit_codes.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/exposure.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/ref_resolver.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/schema_parser.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/security/__init__.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/security/audit.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/security/auth.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/security/config_encryptor.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/security/sandbox.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/strategy.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/system_cmd.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/system_usage.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/src/apcore_cli/validate.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/__init__.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/conformance/__init__.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/conformance/test_apcli_visibility.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/conformance/test_snake_case_kwargs.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/conftest.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_apcli_integration.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_approval.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_bugfixes.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_builtin_group.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_cli.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_config.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_discovery.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_discovery_fe13.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_display_helpers.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_exit_codes.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_exposure.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_factory_fe13.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_init_cmd.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_list_command_filters.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_output.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_output_format_exec.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_output_format_markdown_skill.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_public_api.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_ref_resolver.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_sandbox_runner.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_schema_parser.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_security/__init__.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_security/test_audit.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_security/test_auth.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_security/test_config_encryptor.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_security/test_sandbox.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_strategy.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_system_cmd.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_system_usage.py +0 -0
- {apcore_cli-0.9.0 → apcore_cli-0.10.1}/tests/test_validate.py +0 -0
|
@@ -5,6 +5,46 @@ All notable changes to apcore-cli (Python SDK) will be documented in this file.
|
|
|
5
5
|
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
|
+
## [0.10.1] - 2026-06-15
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Required runtime bumped to apcore 0.24.0 and apcore-toolkit 0.8.1.** Dependency
|
|
13
|
+
floors in `pyproject.toml` raised from `apcore>=0.22.0` / `apcore-toolkit>=0.8.0`
|
|
14
|
+
to `apcore>=0.24.0` / `apcore-toolkit>=0.8.1`, tracking the aligned apcore 0.24.0
|
|
15
|
+
and apcore-toolkit 0.8.1 releases. **No source changes** — the full test suite
|
|
16
|
+
passes unchanged.
|
|
17
|
+
|
|
18
|
+
The apcore 0.22.0 → 0.24.0 delta does not touch any surface the CLI consumes:
|
|
19
|
+
- **Per-instance `ToggleState` isolation (#71)** — the CLI never constructs
|
|
20
|
+
`ToggleState`/`APCore` nor calls the free `is_module_disabled()`; module
|
|
21
|
+
toggling is delegated to the `system.control.toggle_feature` module via
|
|
22
|
+
`Executor.call()`.
|
|
23
|
+
- **Default AI error-recovery metadata (#70)** and **error `details` snake_case
|
|
24
|
+
key alignment (A-D-019)** — the CLI reads error fields (`details`, `suggestion`,
|
|
25
|
+
`ai_guidance`, `retryable`, `user_fixable`) via `getattr()` and passes `details`
|
|
26
|
+
through verbatim, so it is agnostic to both new defaults and inner-key casing.
|
|
27
|
+
- **`Registry.list()` / `get_definition()` / `Executor.call()` / `call_with_trace()`
|
|
28
|
+
/ `set_approval_handler()`** signatures are unchanged across the delta; descriptor
|
|
29
|
+
fields are read defensively with fallbacks.
|
|
30
|
+
- Out of scope and unused by the CLI: `CircuitBreakerMiddleware`, `A2ASubscriber`,
|
|
31
|
+
DLQ/`original_event`, `apcore.Config.validate()`, `Context.create()`, redaction
|
|
32
|
+
utilities, `EventEmitter`.
|
|
33
|
+
|
|
34
|
+
## [0.10.0] - 2026-05-18
|
|
35
|
+
|
|
36
|
+
### Changed — BREAKING
|
|
37
|
+
|
|
38
|
+
- **Removed graceful ImportError fallbacks for `apcore` and `apcore-toolkit` (resolves 6.2).** Both packages are declared as required runtime dependencies in `pyproject.toml` (`apcore>=0.21.0`, `apcore-toolkit>=0.7.0`) — the prior `try: from apcore_toolkit import X; except ImportError: logger.warning(...); return` pattern in `factory.py` and the `try: from apcore import Config; except (ImportError, AttributeError): return False` pattern in `config.py` were self-contradictory: the dep was hard-required by the package manifest but soft-degraded at runtime. The fallbacks have been removed; missing or too-old `apcore` / `apcore-toolkit` now fails fast at import time with `ModuleNotFoundError`, matching the manifest contract.
|
|
39
|
+
- Sites cleaned up: `factory.py:581-616` (DisplayResolver/RegistryWriter/ConventionScanner/BindingLoader), `output.py:144-146/270-272` (format_module/format_modules), `config.py:32-35` (apcore.Config).
|
|
40
|
+
- Toolkit symbols are now imported once at the top of `factory.py` and `output.py`.
|
|
41
|
+
- The `_TOOLKIT_MISSING_HINT` constant in `output.py` is removed (no longer reachable).
|
|
42
|
+
- **Tests updated**: two tests that asserted the removed fallback behaviour (`test_toolkit_missing_logs_warning_and_returns`, `test_binding_loader_missing_warns_but_continues`) are deleted; the import-time failure mode is covered by Python's standard `ModuleNotFoundError`. Remaining `_apply_toolkit_integration` tests now patch `apcore_cli.factory.{BindingLoader,DisplayResolver,RegistryWriter,ConventionScanner}` instead of `apcore_toolkit.X` (standard "patch where the name is used" Python mock convention now that the imports are static).
|
|
43
|
+
|
|
44
|
+
### Migration
|
|
45
|
+
|
|
46
|
+
- If your environment previously relied on the soft-degrade behaviour (apcore-cli running with `apcore-toolkit` uninstalled), install the package: `pip install apcore-toolkit>=0.7.0`. With the manifest already declaring it required, this should already be satisfied by any standard `pip install apcore-cli` invocation.
|
|
47
|
+
|
|
8
48
|
## [0.9.0] - 2026-05-13
|
|
9
49
|
|
|
10
50
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: apcore-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.1
|
|
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
|
|
@@ -21,8 +21,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
21
21
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Requires-Python: >=3.11
|
|
24
|
-
Requires-Dist: apcore-toolkit>=0.
|
|
25
|
-
Requires-Dist: apcore>=0.
|
|
24
|
+
Requires-Dist: apcore-toolkit>=0.8.1
|
|
25
|
+
Requires-Dist: apcore>=0.24.0
|
|
26
26
|
Requires-Dist: click>=8.1
|
|
27
27
|
Requires-Dist: cryptography>=41.0
|
|
28
28
|
Requires-Dist: jsonschema>=4.20
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "apcore-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.10.1"
|
|
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,8 +26,8 @@ classifiers = [
|
|
|
26
26
|
"Environment :: Console",
|
|
27
27
|
]
|
|
28
28
|
dependencies = [
|
|
29
|
-
"apcore>=0.
|
|
30
|
-
"apcore-toolkit>=0.
|
|
29
|
+
"apcore>=0.24.0",
|
|
30
|
+
"apcore-toolkit>=0.8.1",
|
|
31
31
|
"click>=8.1",
|
|
32
32
|
"jsonschema>=4.20",
|
|
33
33
|
"rich>=13.0",
|
|
@@ -26,13 +26,16 @@ def register_config_namespace() -> bool:
|
|
|
26
26
|
Public helper mirroring apcore-cli-typescript's ``registerConfigNamespace``
|
|
27
27
|
and apcore-cli-rust's equivalent registration call site so embedders can
|
|
28
28
|
invoke registration explicitly. Returns ``True`` when registration
|
|
29
|
-
succeeded
|
|
30
|
-
|
|
29
|
+
succeeded.
|
|
30
|
+
|
|
31
|
+
``apcore`` is a required runtime dependency
|
|
32
|
+
(``pyproject.toml`` pins ``apcore>=0.21.0``) so the import is performed
|
|
33
|
+
locally rather than guarded — a missing or too-old apcore is a hard
|
|
34
|
+
install error, not a silent no-op. The prior graceful-fallback path
|
|
35
|
+
was removed in the 6.2 fix.
|
|
31
36
|
"""
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
except (ImportError, AttributeError):
|
|
35
|
-
return False
|
|
37
|
+
from apcore import Config
|
|
38
|
+
|
|
36
39
|
Config.register_namespace(
|
|
37
40
|
name="apcore-cli",
|
|
38
41
|
schema=None,
|
|
@@ -18,6 +18,14 @@ from typing import Any
|
|
|
18
18
|
|
|
19
19
|
import click
|
|
20
20
|
|
|
21
|
+
# apcore-toolkit is a REQUIRED runtime dependency (pyproject.toml pins
|
|
22
|
+
# `apcore-toolkit>=0.7.0`). Static import — no ImportError fallback. If
|
|
23
|
+
# toolkit is missing the import fails at module load time with a clear
|
|
24
|
+
# ModuleNotFoundError, matching the pyproject contract. See CHANGELOG for
|
|
25
|
+
# the 6.2 fix that removed the prior graceful-fallback path.
|
|
26
|
+
from apcore_toolkit import BindingLoader, DisplayResolver, RegistryWriter
|
|
27
|
+
from apcore_toolkit.convention_scanner import ConventionScanner
|
|
28
|
+
|
|
21
29
|
from apcore_cli.builtin_group import ApcliGroup
|
|
22
30
|
from apcore_cli.cli import GroupedModuleGroup, set_audit_logger, set_verbose_help
|
|
23
31
|
from apcore_cli.config import ConfigResolver
|
|
@@ -571,25 +579,18 @@ def _apply_toolkit_integration(
|
|
|
571
579
|
single registration path ensures ``--allowed-prefix`` protection
|
|
572
580
|
applies to both sources consistently.
|
|
573
581
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
582
|
+
Toolkit symbols are imported statically at module top — the package
|
|
583
|
+
is a required runtime dep (``pyproject.toml`` pins
|
|
584
|
+
``apcore-toolkit>=0.7.0``). Operational failures inside scanner /
|
|
585
|
+
loader / writer calls still degrade to a WARNING.
|
|
577
586
|
"""
|
|
578
587
|
if commands_dir is None and binding_path is None:
|
|
579
588
|
return
|
|
580
589
|
|
|
581
|
-
try:
|
|
582
|
-
from apcore_toolkit import DisplayResolver, RegistryWriter
|
|
583
|
-
except ImportError:
|
|
584
|
-
logger.warning("apcore-toolkit not installed — toolkit features unavailable")
|
|
585
|
-
return
|
|
586
|
-
|
|
587
590
|
scanned: list[Any] = []
|
|
588
591
|
|
|
589
592
|
if commands_dir is not None:
|
|
590
593
|
try:
|
|
591
|
-
from apcore_toolkit.convention_scanner import ConventionScanner
|
|
592
|
-
|
|
593
594
|
scanner = ConventionScanner()
|
|
594
595
|
scanned.extend(scanner.scan(commands_dir))
|
|
595
596
|
except Exception as e:
|
|
@@ -597,23 +598,16 @@ def _apply_toolkit_integration(
|
|
|
597
598
|
|
|
598
599
|
if binding_path is not None:
|
|
599
600
|
try:
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
logger.info(
|
|
611
|
-
"BindingLoader: parsed %d module(s) from %s",
|
|
612
|
-
len(loaded),
|
|
613
|
-
binding_path,
|
|
614
|
-
)
|
|
615
|
-
except Exception as e:
|
|
616
|
-
logger.warning("BindingLoader failed on '%s': %s", binding_path, e)
|
|
601
|
+
loader = BindingLoader()
|
|
602
|
+
loaded = loader.load(binding_path)
|
|
603
|
+
scanned.extend(loaded)
|
|
604
|
+
logger.info(
|
|
605
|
+
"BindingLoader: parsed %d module(s) from %s",
|
|
606
|
+
len(loaded),
|
|
607
|
+
binding_path,
|
|
608
|
+
)
|
|
609
|
+
except Exception as e:
|
|
610
|
+
logger.warning("BindingLoader failed on '%s': %s", binding_path, e)
|
|
617
611
|
|
|
618
612
|
if not scanned:
|
|
619
613
|
return
|
|
@@ -7,6 +7,12 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
import click
|
|
9
9
|
|
|
10
|
+
# Templates emit `# TODO: implement` placeholders into the GENERATED user
|
|
11
|
+
# module. These are user-facing scaffolding — not SDK source-level TODOs —
|
|
12
|
+
# and intentionally match the parity templates in
|
|
13
|
+
# `../apcore-cli-typescript/src/init-cmd.ts` and
|
|
14
|
+
# `../apcore-cli-rust/src/init_cmd.rs`. Audit tools that grep for "TODO"
|
|
15
|
+
# should treat occurrences inside the template literals below as expected.
|
|
10
16
|
_DECORATOR_TEMPLATE = '''"""Module: {module_id}"""
|
|
11
17
|
|
|
12
18
|
from apcore import module
|
|
@@ -8,7 +8,7 @@ import sys
|
|
|
8
8
|
from typing import TYPE_CHECKING, Any
|
|
9
9
|
|
|
10
10
|
import click
|
|
11
|
-
from apcore_toolkit import format_csv, format_jsonl
|
|
11
|
+
from apcore_toolkit import format_csv, format_jsonl, format_module, format_modules
|
|
12
12
|
from rich.console import Console
|
|
13
13
|
from rich.panel import Panel
|
|
14
14
|
from rich.syntax import Syntax
|
|
@@ -23,12 +23,6 @@ if TYPE_CHECKING:
|
|
|
23
23
|
logger = logging.getLogger(__name__)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
_TOOLKIT_MISSING_HINT = (
|
|
27
|
-
"The 'markdown' and 'skill' output formats require the apcore-toolkit "
|
|
28
|
-
"extra. Install with: pip install 'apcore-cli[toolkit]'"
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
26
|
def _to_rows_for_tabular(value: Any) -> list[dict] | None:
|
|
33
27
|
"""Coerce an exec result into the row-shape expected by the toolkit's
|
|
34
28
|
tabular formatters (csv / jsonl). Returns ``None`` for shapes that don't
|
|
@@ -140,11 +134,6 @@ def format_module_list(
|
|
|
140
134
|
|
|
141
135
|
Console().print(table)
|
|
142
136
|
elif format in ("markdown", "skill"):
|
|
143
|
-
try:
|
|
144
|
-
from apcore_toolkit import format_modules
|
|
145
|
-
except ImportError as exc: # pragma: no cover - exercised via integration only
|
|
146
|
-
raise click.ClickException(_TOOLKIT_MISSING_HINT) from exc
|
|
147
|
-
|
|
148
137
|
scanned = [_descriptor_to_scanned(m) for m in modules]
|
|
149
138
|
click.echo(format_modules(scanned, style=format, display=True))
|
|
150
139
|
elif format in ("json", "csv", "yaml", "jsonl"):
|
|
@@ -266,11 +255,6 @@ def format_module_detail(module_def: ModuleDescriptor, format: str) -> None:
|
|
|
266
255
|
click.echo(f"\nTags: {', '.join(tags)}")
|
|
267
256
|
|
|
268
257
|
elif format in ("markdown", "skill"):
|
|
269
|
-
try:
|
|
270
|
-
from apcore_toolkit import format_module
|
|
271
|
-
except ImportError as exc: # pragma: no cover - exercised via integration only
|
|
272
|
-
raise click.ClickException(_TOOLKIT_MISSING_HINT) from exc
|
|
273
|
-
|
|
274
258
|
click.echo(format_module(_descriptor_to_scanned(module_def), style=format, display=True))
|
|
275
259
|
|
|
276
260
|
elif format == "json":
|
|
@@ -597,46 +597,3 @@ def register_completion_command(apcli_group: click.Group, prog_name: str = "apco
|
|
|
597
597
|
click.echo(generators[shell]())
|
|
598
598
|
|
|
599
599
|
_ = completion_cmd
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
def register_shell_commands(cli: click.Group, prog_name: str = "apcore-cli") -> None:
|
|
603
|
-
"""Legacy wrapper — registers ``completion`` and ``man`` on the given group.
|
|
604
|
-
|
|
605
|
-
FE-13 canonical wiring attaches ``completion`` to the ``apcli`` group via
|
|
606
|
-
:func:`register_completion_command`; ``man`` remains at the root per
|
|
607
|
-
spec §4.1 (meta-commands stay at root). This shim preserves the pre-v0.7
|
|
608
|
-
flat shape for existing tests.
|
|
609
|
-
"""
|
|
610
|
-
register_completion_command(cli, prog_name=prog_name)
|
|
611
|
-
|
|
612
|
-
@cli.command("man")
|
|
613
|
-
@click.argument("command")
|
|
614
|
-
@click.pass_context
|
|
615
|
-
def man_cmd(ctx: click.Context, command: str) -> None:
|
|
616
|
-
"""Generate a roff man page for COMMAND and print it to stdout.
|
|
617
|
-
|
|
618
|
-
\b
|
|
619
|
-
View immediately:
|
|
620
|
-
PROG man list | man -
|
|
621
|
-
PROG man describe | col -bx | less
|
|
622
|
-
|
|
623
|
-
Install system-wide:
|
|
624
|
-
PROG man list > /usr/local/share/man/man1/PROG-list.1
|
|
625
|
-
mandb # (Linux) or /usr/libexec/makewhatis # (macOS)
|
|
626
|
-
"""
|
|
627
|
-
parent = ctx.parent
|
|
628
|
-
if parent is None:
|
|
629
|
-
click.echo(f"Error: Unknown command '{command}'.", err=True)
|
|
630
|
-
sys.exit(2)
|
|
631
|
-
|
|
632
|
-
resolved_prog = ctx.find_root().info_name or prog_name
|
|
633
|
-
parent_group = parent.command
|
|
634
|
-
cmd = parent_group.commands.get(command) if isinstance(parent_group, click.Group) else None
|
|
635
|
-
|
|
636
|
-
known_builtins = {"completion", "describe", "exec", "init", "list", "man"}
|
|
637
|
-
if cmd is None and command not in known_builtins:
|
|
638
|
-
click.echo(f"Error: Unknown command '{command}'.", err=True)
|
|
639
|
-
sys.exit(2)
|
|
640
|
-
|
|
641
|
-
roff = _generate_man_page(command, cmd, resolved_prog)
|
|
642
|
-
click.echo(roff)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Test-only helper that builds the pre-v0.7 *flat* CLI shape.
|
|
2
|
+
|
|
3
|
+
The production CLI (``factory.create_cli``) registers ``completion`` under the
|
|
4
|
+
``apcli`` builtin group and exposes ``man`` as a root ``--man`` overlay
|
|
5
|
+
(``configure_man_help``). This helper instead reproduces the legacy flat shape —
|
|
6
|
+
``completion`` and ``man`` as root subcommands — purely so the completion
|
|
7
|
+
generators and the man-page builder can be exercised through a CLI in tests.
|
|
8
|
+
|
|
9
|
+
Relocated from ``apcore_cli.shell.register_shell_commands`` (audit D9-001): the
|
|
10
|
+
shim had zero production callers and was removed from the shipped package to
|
|
11
|
+
match apcore-cli-rust and apcore-cli-typescript, both of which dropped the
|
|
12
|
+
equivalent wrapper in v0.7.0 / FE-13. Keeping it here preserves the behavioral
|
|
13
|
+
coverage without shipping dead code.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
import click
|
|
19
|
+
|
|
20
|
+
from apcore_cli.shell import _generate_man_page, register_completion_command
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def register_shell_commands(cli: click.Group, prog_name: str = "apcore-cli") -> None:
|
|
24
|
+
"""Attach flat ``completion`` and ``man`` subcommands to *cli* (test-only)."""
|
|
25
|
+
register_completion_command(cli, prog_name=prog_name)
|
|
26
|
+
|
|
27
|
+
@cli.command("man")
|
|
28
|
+
@click.argument("command")
|
|
29
|
+
@click.pass_context
|
|
30
|
+
def man_cmd(ctx: click.Context, command: str) -> None:
|
|
31
|
+
"""Generate a roff man page for COMMAND and print it to stdout."""
|
|
32
|
+
parent = ctx.parent
|
|
33
|
+
if parent is None:
|
|
34
|
+
click.echo(f"Error: Unknown command '{command}'.", err=True)
|
|
35
|
+
sys.exit(2)
|
|
36
|
+
|
|
37
|
+
resolved_prog = ctx.find_root().info_name or prog_name
|
|
38
|
+
parent_group = parent.command
|
|
39
|
+
cmd = parent_group.commands.get(command) if isinstance(parent_group, click.Group) else None
|
|
40
|
+
|
|
41
|
+
known_builtins = {"completion", "describe", "exec", "init", "list", "man"}
|
|
42
|
+
if cmd is None and command not in known_builtins:
|
|
43
|
+
click.echo(f"Error: Unknown command '{command}'.", err=True)
|
|
44
|
+
sys.exit(2)
|
|
45
|
+
|
|
46
|
+
roff = _generate_man_page(command, cmd, resolved_prog)
|
|
47
|
+
click.echo(roff)
|
|
48
|
+
|
|
49
|
+
_ = man_cmd
|
|
@@ -15,7 +15,8 @@ from click.testing import CliRunner
|
|
|
15
15
|
|
|
16
16
|
from apcore_cli.cli import LazyModuleGroup, build_module_command, set_audit_logger
|
|
17
17
|
from apcore_cli.discovery import register_discovery_commands
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
from .shell_test_utils import register_shell_commands
|
|
19
20
|
|
|
20
21
|
# ---------------------------------------------------------------------------
|
|
21
22
|
# Real apcore module definitions
|
|
@@ -9,7 +9,8 @@ from click.testing import CliRunner
|
|
|
9
9
|
|
|
10
10
|
from apcore_cli.cli import LazyModuleGroup, build_module_command, set_audit_logger
|
|
11
11
|
from apcore_cli.discovery import register_discovery_commands
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
from .shell_test_utils import register_shell_commands
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def _make_module_def(
|
|
@@ -61,9 +61,9 @@ class TestApplyToolkitIntegration:
|
|
|
61
61
|
fake_writer_cls = MagicMock(return_value=fake_writer)
|
|
62
62
|
|
|
63
63
|
with (
|
|
64
|
-
patch("
|
|
65
|
-
patch("
|
|
66
|
-
patch("
|
|
64
|
+
patch("apcore_cli.factory.BindingLoader", fake_loader_cls),
|
|
65
|
+
patch("apcore_cli.factory.DisplayResolver", fake_resolver_cls),
|
|
66
|
+
patch("apcore_cli.factory.RegistryWriter", fake_writer_cls),
|
|
67
67
|
):
|
|
68
68
|
registry = MagicMock()
|
|
69
69
|
_apply_toolkit_integration(
|
|
@@ -92,9 +92,9 @@ class TestApplyToolkitIntegration:
|
|
|
92
92
|
fake_writer.write.return_value = []
|
|
93
93
|
|
|
94
94
|
with (
|
|
95
|
-
patch("
|
|
96
|
-
patch("
|
|
97
|
-
patch("
|
|
95
|
+
patch("apcore_cli.factory.BindingLoader", MagicMock(return_value=fake_loader)),
|
|
96
|
+
patch("apcore_cli.factory.DisplayResolver", MagicMock(return_value=MagicMock(resolve=lambda m, **kw: m))),
|
|
97
|
+
patch("apcore_cli.factory.RegistryWriter", MagicMock(return_value=fake_writer)),
|
|
98
98
|
):
|
|
99
99
|
_apply_toolkit_integration(
|
|
100
100
|
MagicMock(),
|
|
@@ -121,10 +121,10 @@ class TestApplyToolkitIntegration:
|
|
|
121
121
|
fake_writer.write.return_value = []
|
|
122
122
|
|
|
123
123
|
with (
|
|
124
|
-
patch("
|
|
125
|
-
patch("
|
|
126
|
-
patch("
|
|
127
|
-
patch("
|
|
124
|
+
patch("apcore_cli.factory.ConventionScanner", fake_scanner_cls),
|
|
125
|
+
patch("apcore_cli.factory.BindingLoader", MagicMock(return_value=fake_loader)),
|
|
126
|
+
patch("apcore_cli.factory.DisplayResolver", MagicMock(return_value=MagicMock(resolve=lambda m, **kw: m))),
|
|
127
|
+
patch("apcore_cli.factory.RegistryWriter", MagicMock(return_value=fake_writer)),
|
|
128
128
|
):
|
|
129
129
|
_apply_toolkit_integration(
|
|
130
130
|
MagicMock(),
|
|
@@ -138,54 +138,17 @@ class TestApplyToolkitIntegration:
|
|
|
138
138
|
assert scan_module in written
|
|
139
139
|
assert bind_module in written
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
with (
|
|
153
|
-
caplog.at_level(pylogging.WARNING, logger="apcore_cli"),
|
|
154
|
-
patch("builtins.__import__", side_effect=fail_toolkit),
|
|
155
|
-
):
|
|
156
|
-
_apply_toolkit_integration(
|
|
157
|
-
MagicMock(),
|
|
158
|
-
commands_dir="/cmds",
|
|
159
|
-
binding_path=None,
|
|
160
|
-
allowed_prefixes=None,
|
|
161
|
-
)
|
|
162
|
-
assert "apcore-toolkit not installed" in caplog.text
|
|
163
|
-
|
|
164
|
-
def test_binding_loader_missing_warns_but_continues(self, caplog):
|
|
165
|
-
"""toolkit < 0.5.0 lacks BindingLoader — match TS parity: warn + skip."""
|
|
166
|
-
import builtins
|
|
167
|
-
import logging as pylogging
|
|
168
|
-
|
|
169
|
-
real_import = builtins.__import__
|
|
170
|
-
|
|
171
|
-
def partial_import(name, globals_=None, locals_=None, fromlist=(), level=0):
|
|
172
|
-
# Simulate toolkit installed but missing BindingLoader: when the
|
|
173
|
-
# factory tries `from apcore_toolkit import BindingLoader`, raise.
|
|
174
|
-
if name == "apcore_toolkit" and "BindingLoader" in (fromlist or ()):
|
|
175
|
-
raise ImportError("BindingLoader not in 0.4.x")
|
|
176
|
-
return real_import(name, globals_, locals_, fromlist, level)
|
|
177
|
-
|
|
178
|
-
with (
|
|
179
|
-
caplog.at_level(pylogging.WARNING, logger="apcore_cli"),
|
|
180
|
-
patch("builtins.__import__", side_effect=partial_import),
|
|
181
|
-
):
|
|
182
|
-
_apply_toolkit_integration(
|
|
183
|
-
MagicMock(),
|
|
184
|
-
commands_dir=None,
|
|
185
|
-
binding_path="/bindings",
|
|
186
|
-
allowed_prefixes=None,
|
|
187
|
-
)
|
|
188
|
-
assert "BindingLoader unavailable" in caplog.text
|
|
141
|
+
# Removed in 0.10.0 (resolves 6.2): the prior graceful-fallback paths
|
|
142
|
+
# for "apcore-toolkit missing" and "apcore-toolkit < 0.5.0 (no
|
|
143
|
+
# BindingLoader)" no longer exist — apcore-toolkit >= 0.7.0 is a hard
|
|
144
|
+
# runtime dependency declared in pyproject.toml, so a missing or
|
|
145
|
+
# too-old toolkit is a ModuleNotFoundError at import time, not a
|
|
146
|
+
# silently logged WARNING. The two former tests
|
|
147
|
+
# (`test_toolkit_missing_logs_warning_and_returns` and
|
|
148
|
+
# `test_binding_loader_missing_warns_but_continues`) are intentionally
|
|
149
|
+
# deleted — they asserted behaviour that the fix removed. The
|
|
150
|
+
# import-time failure mode is already covered by Python's standard
|
|
151
|
+
# ModuleNotFoundError contract and does not need a bespoke test.
|
|
189
152
|
|
|
190
153
|
def test_binding_loader_failure_is_soft(self, caplog):
|
|
191
154
|
"""BindingLoader.load() raises → WARN, don't crash create_cli."""
|
|
@@ -199,9 +162,9 @@ class TestApplyToolkitIntegration:
|
|
|
199
162
|
|
|
200
163
|
with (
|
|
201
164
|
caplog.at_level(pylogging.WARNING, logger="apcore_cli"),
|
|
202
|
-
patch("
|
|
203
|
-
patch("
|
|
204
|
-
patch("
|
|
165
|
+
patch("apcore_cli.factory.BindingLoader", MagicMock(return_value=fake_loader)),
|
|
166
|
+
patch("apcore_cli.factory.DisplayResolver", MagicMock()),
|
|
167
|
+
patch("apcore_cli.factory.RegistryWriter", MagicMock()),
|
|
205
168
|
):
|
|
206
169
|
_apply_toolkit_integration(
|
|
207
170
|
MagicMock(),
|
|
@@ -219,9 +182,9 @@ class TestApplyToolkitIntegration:
|
|
|
219
182
|
fake_writer = MagicMock()
|
|
220
183
|
|
|
221
184
|
with (
|
|
222
|
-
patch("
|
|
223
|
-
patch("
|
|
224
|
-
patch("
|
|
185
|
+
patch("apcore_cli.factory.BindingLoader", MagicMock(return_value=fake_loader)),
|
|
186
|
+
patch("apcore_cli.factory.DisplayResolver", MagicMock()),
|
|
187
|
+
patch("apcore_cli.factory.RegistryWriter", MagicMock(return_value=fake_writer)),
|
|
225
188
|
):
|
|
226
189
|
_apply_toolkit_integration(
|
|
227
190
|
MagicMock(),
|
|
@@ -320,9 +283,9 @@ class TestBindingPathStandaloneE2E:
|
|
|
320
283
|
fake_writer.write.return_value = []
|
|
321
284
|
|
|
322
285
|
with (
|
|
323
|
-
patch("
|
|
324
|
-
patch("
|
|
325
|
-
patch("
|
|
286
|
+
patch("apcore_cli.factory.BindingLoader", MagicMock(return_value=fake_loader)),
|
|
287
|
+
patch("apcore_cli.factory.DisplayResolver", MagicMock(return_value=MagicMock(resolve=lambda m, **kw: m))),
|
|
288
|
+
patch("apcore_cli.factory.RegistryWriter", MagicMock(return_value=fake_writer)),
|
|
326
289
|
):
|
|
327
290
|
cli = create_cli(
|
|
328
291
|
extensions_dir=str(tmp_path),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|