apcore-cli 0.2.0__tar.gz → 0.2.2__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.2.0 → apcore_cli-0.2.2}/CHANGELOG.md +24 -0
- apcore_cli-0.2.2/CLAUDE.md +30 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/PKG-INFO +23 -55
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/README.md +16 -48
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/planning/schema-parser.md +1 -1
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/pyproject.toml +7 -7
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/__main__.py +12 -1
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/cli.py +15 -4
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/config.py +2 -1
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/schema_parser.py +5 -5
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_config.py +1 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_schema_parser.py +13 -2
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/.github/CODEOWNERS +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/.github/copilot-ignore +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/.github/workflows/ci.yml +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/.gitignore +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/.gitmessage +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/.pre-commit-config.yaml +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/examples/extensions/math/add.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/examples/extensions/math/multiply.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/examples/extensions/sysutil/disk.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/examples/extensions/sysutil/env.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/examples/extensions/sysutil/info.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/examples/extensions/text/reverse.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/examples/extensions/text/upper.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/examples/extensions/text/wordcount.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/examples/run_examples.sh +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/planning/approval-gate.md +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/planning/config-resolver.md +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/planning/core-dispatcher.md +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/planning/discovery.md +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/planning/output-formatter.md +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/planning/overview.md +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/planning/security-manager.md +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/planning/shell-integration.md +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/planning/state.json +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/__init__.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/_sandbox_runner.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/approval.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/discovery.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/output.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/ref_resolver.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/security/__init__.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/security/audit.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/security/auth.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/security/config_encryptor.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/security/sandbox.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/src/apcore_cli/shell.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/__init__.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/conftest.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_approval.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_bugfixes.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_cli.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_discovery.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_e2e.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_integration.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_output.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_ref_resolver.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_security/__init__.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_security/test_audit.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_security/test_auth.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_security/test_config_encryptor.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_security/test_sandbox.py +0 -0
- {apcore_cli-0.2.0 → apcore_cli-0.2.2}/tests/test_shell.py +0 -0
|
@@ -5,6 +5,30 @@ 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.2.2] - 2026-03-22
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Rebrand: aipartnerup → aiperceivable
|
|
12
|
+
|
|
13
|
+
## [0.2.1] - 2026-03-19
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Help text truncation limit increased from 200 to 1000 characters (configurable via `cli.help_text_max_length` config key)
|
|
17
|
+
- `_extract_help`: added `max_length: int = 1000` parameter (`schema_parser.py`)
|
|
18
|
+
- `schema_to_click_options`: added `max_help_length: int = 1000` parameter (`schema_parser.py`)
|
|
19
|
+
- `build_module_command`: added `help_text_max_length: int = 1000` parameter, threaded through to schema parser (`cli.py`)
|
|
20
|
+
- `LazyModuleGroup`: constructor accepts `help_text_max_length: int = 1000`, passes to `build_module_command` (`cli.py`)
|
|
21
|
+
- `create_cli`: resolves `cli.help_text_max_length` from `ConfigResolver` and passes to `LazyModuleGroup` (`__main__.py`)
|
|
22
|
+
- `format_exec_result`: nested dict/list values in table mode now rendered with `json.dumps` instead of `str()` (`output.py`)
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- `cli.help_text_max_length` config key (default: 1000) in `ConfigResolver.DEFAULTS` (`config.py`)
|
|
26
|
+
- `APCORE_CLI_HELP_TEXT_MAX_LENGTH` environment variable support for configuring help text max length
|
|
27
|
+
- `test_help_truncation_default`: tests default 1000-char truncation
|
|
28
|
+
- `test_help_no_truncation_within_limit`: tests no truncation at 999 chars
|
|
29
|
+
- `test_help_truncation_custom_max`: tests custom max_length parameter
|
|
30
|
+
- 263 tests (up from 261)
|
|
31
|
+
|
|
8
32
|
## [0.2.0] - 2026-03-16
|
|
9
33
|
|
|
10
34
|
### Added
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# CLAUDE.md — apcore-cli-python
|
|
2
|
+
|
|
3
|
+
## Build & Test
|
|
4
|
+
|
|
5
|
+
- `pytest` — run all tests. **Must pass before considering any task complete.**
|
|
6
|
+
- `pytest --cov` — run with coverage report.
|
|
7
|
+
- `ruff check .` — lint check.
|
|
8
|
+
- `ruff format .` — format all code. **Run after every code change.**
|
|
9
|
+
|
|
10
|
+
## Code Style
|
|
11
|
+
|
|
12
|
+
- Python 3.11+ with `from __future__ import annotations`.
|
|
13
|
+
- All code must pass `ruff check` and `ruff format --check`.
|
|
14
|
+
- Type annotations on all public function signatures.
|
|
15
|
+
- Use `click.echo()` for user-facing output, `logger.*` for debug/diagnostic output.
|
|
16
|
+
- Prefer `sys.exit(code)` with exit code constants over raising exceptions for CLI errors.
|
|
17
|
+
|
|
18
|
+
## Project Conventions
|
|
19
|
+
|
|
20
|
+
- Spec repo (single source of truth): `../apcore-cli/docs/`
|
|
21
|
+
- Package structure: `src/apcore_cli/` with `__init__.py` exporting `__version__` only.
|
|
22
|
+
- Entry point: `apcore_cli.__main__:main`.
|
|
23
|
+
- Security modules live in `src/apcore_cli/security/` sub-package.
|
|
24
|
+
- ConfigResolver.DEFAULTS values are Python-typed (str, int, bool).
|
|
25
|
+
- Tests organized by module: `tests/test_<module>.py`, security tests in `tests/test_security/`.
|
|
26
|
+
|
|
27
|
+
## Environment
|
|
28
|
+
|
|
29
|
+
- Python >= 3.11
|
|
30
|
+
- Key dependencies: click >= 8.0, rich >= 13.0, jsonschema >= 4.0, pyyaml >= 6.0
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: apcore-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Terminal adapter for apcore — execute AI-Perceivable modules from the command line
|
|
5
|
-
Project-URL: Homepage, https://
|
|
6
|
-
Project-URL: Repository, https://github.com/
|
|
7
|
-
Project-URL: Documentation, https://github.com/
|
|
8
|
-
Project-URL: Issues, https://github.com/
|
|
9
|
-
Author-email:
|
|
5
|
+
Project-URL: Homepage, https://aiperceivable.com
|
|
6
|
+
Project-URL: Repository, https://github.com/aiperceivable/apcore-cli-python
|
|
7
|
+
Project-URL: Documentation, https://github.com/aiperceivable/apcore-cli-python#readme
|
|
8
|
+
Project-URL: Issues, https://github.com/aiperceivable/apcore-cli-python/issues
|
|
9
|
+
Author-email: aiperceivable <team@aiperceivable.com>
|
|
10
10
|
License-Expression: Apache-2.0
|
|
11
11
|
Keywords: agent,ai,apcore,automation,cli,command-line,llm,shell,terminal,unix
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -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.13.
|
|
23
|
+
Requires-Dist: apcore>=0.13.1
|
|
24
24
|
Requires-Dist: click>=8.1
|
|
25
25
|
Requires-Dist: cryptography>=41.0
|
|
26
26
|
Requires-Dist: jsonschema>=4.20
|
|
@@ -36,7 +36,7 @@ Requires-Dist: ruff>=0.1; extra == 'dev'
|
|
|
36
36
|
Description-Content-Type: text/markdown
|
|
37
37
|
|
|
38
38
|
<div align="center">
|
|
39
|
-
<img src="https://raw.githubusercontent.com/
|
|
39
|
+
<img src="https://raw.githubusercontent.com/aiperceivable/apcore-cli/main/apcore-cli-logo.svg" alt="apcore-cli logo" width="200"/>
|
|
40
40
|
</div>
|
|
41
41
|
|
|
42
42
|
# apcore-cli
|
|
@@ -45,15 +45,15 @@ Terminal adapter for apcore. Execute AI-Perceivable modules from the command lin
|
|
|
45
45
|
|
|
46
46
|
[](LICENSE)
|
|
47
47
|
[](https://python.org)
|
|
48
|
-
[]()
|
|
49
49
|
|
|
50
50
|
| | |
|
|
51
51
|
|---|---|
|
|
52
|
-
| **Python SDK** | [github.com/
|
|
53
|
-
| **Spec repo** | [github.com/
|
|
54
|
-
| **apcore core** | [github.com/
|
|
52
|
+
| **Python SDK** | [github.com/aiperceivable/apcore-cli-python](https://github.com/aiperceivable/apcore-cli-python) |
|
|
53
|
+
| **Spec repo** | [github.com/aiperceivable/apcore-cli](https://github.com/aiperceivable/apcore-cli) |
|
|
54
|
+
| **apcore core** | [github.com/aiperceivable/apcore](https://github.com/aiperceivable/apcore) |
|
|
55
55
|
|
|
56
|
-
**apcore-cli** turns any [apcore](https://github.com/
|
|
56
|
+
**apcore-cli** turns any [apcore](https://github.com/aiperceivable/apcore)-based project into a fully featured CLI tool — with **zero code changes** to your existing modules.
|
|
57
57
|
|
|
58
58
|
```
|
|
59
59
|
┌──────────────────┐
|
|
@@ -94,7 +94,7 @@ Requires Python 3.11+ and `apcore >= 0.13.0`.
|
|
|
94
94
|
The repo includes 8 example modules you can run immediately:
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
|
-
git clone https://github.com/
|
|
97
|
+
git clone https://github.com/aiperceivable/apcore-cli-python.git
|
|
98
98
|
cd apcore-cli-python
|
|
99
99
|
pip install -e ".[dev]"
|
|
100
100
|
|
|
@@ -268,6 +268,7 @@ apcore-cli uses a 4-tier configuration precedence:
|
|
|
268
268
|
| `APCORE_LOGGING_LEVEL` | Global apcore log level (fallback when `APCORE_CLI_LOGGING_LEVEL` is unset) | `WARNING` |
|
|
269
269
|
| `APCORE_AUTH_API_KEY` | API key for remote registry authentication | *(unset)* |
|
|
270
270
|
| `APCORE_CLI_SANDBOX` | Set to `1` to enable subprocess sandboxing | *(unset)* |
|
|
271
|
+
| `APCORE_CLI_HELP_TEXT_MAX_LENGTH` | Maximum characters for CLI option help text before truncation | `1000` |
|
|
271
272
|
|
|
272
273
|
### Config File (`apcore.yaml`)
|
|
273
274
|
|
|
@@ -278,6 +279,8 @@ logging:
|
|
|
278
279
|
level: DEBUG
|
|
279
280
|
sandbox:
|
|
280
281
|
enabled: false
|
|
282
|
+
cli:
|
|
283
|
+
help_text_max_length: 1000
|
|
281
284
|
```
|
|
282
285
|
|
|
283
286
|
## Features
|
|
@@ -317,10 +320,10 @@ apcore-cli (the adapter)
|
|
|
317
320
|
|
|
|
318
321
|
+-- ConfigResolver 4-tier config precedence
|
|
319
322
|
+-- LazyModuleGroup Dynamic Click command generation
|
|
320
|
-
+--
|
|
321
|
-
+--
|
|
322
|
-
+--
|
|
323
|
-
+--
|
|
323
|
+
+-- schema_parser JSON Schema -> Click options
|
|
324
|
+
+-- ref_resolver $ref / allOf / anyOf / oneOf
|
|
325
|
+
+-- approval TTY-aware HITL approval
|
|
326
|
+
+-- output TTY-adaptive JSON/table output
|
|
324
327
|
+-- AuditLogger JSON Lines execution logging
|
|
325
328
|
+-- Sandbox Subprocess isolation
|
|
326
329
|
|
|
|
@@ -412,49 +415,14 @@ apcore-cli --extensions-dir ./extensions greet.hello --name Alice --greeting Hi
|
|
|
412
415
|
## Development
|
|
413
416
|
|
|
414
417
|
```bash
|
|
415
|
-
git clone https://github.com/
|
|
418
|
+
git clone https://github.com/aiperceivable/apcore-cli-python.git
|
|
416
419
|
cd apcore-cli-python
|
|
417
420
|
pip install -e ".[dev]"
|
|
418
|
-
pytest #
|
|
421
|
+
pytest # 263 tests
|
|
419
422
|
pytest --cov # with coverage report
|
|
420
423
|
bash examples/run_examples.sh # run all examples
|
|
421
424
|
```
|
|
422
425
|
|
|
423
|
-
### Project Structure
|
|
424
|
-
|
|
425
|
-
```
|
|
426
|
-
src/apcore_cli/
|
|
427
|
-
├── __init__.py # Package version
|
|
428
|
-
├── __main__.py # CLI entry point, wiring
|
|
429
|
-
├── cli.py # LazyModuleGroup, build_module_command, collect_input
|
|
430
|
-
├── config.py # ConfigResolver (4-tier precedence)
|
|
431
|
-
├── schema_parser.py # JSON Schema -> Click options
|
|
432
|
-
├── ref_resolver.py # $ref / allOf / anyOf / oneOf resolution
|
|
433
|
-
├── output.py # TTY-adaptive output formatting (rich)
|
|
434
|
-
├── discovery.py # list / describe commands
|
|
435
|
-
├── approval.py # HITL approval gate with timeout
|
|
436
|
-
├── shell.py # bash/zsh/fish completion + man pages
|
|
437
|
-
├── _sandbox_runner.py # Subprocess entry point for sandboxed execution
|
|
438
|
-
└── security/
|
|
439
|
-
├── __init__.py # Exports
|
|
440
|
-
├── auth.py # API key authentication
|
|
441
|
-
├── config_encryptor.py # Keyring + AES-256-GCM encrypted config
|
|
442
|
-
├── audit.py # JSON Lines audit logging
|
|
443
|
-
└── sandbox.py # Subprocess-based execution isolation
|
|
444
|
-
|
|
445
|
-
examples/
|
|
446
|
-
├── run_examples.sh # Run all examples end-to-end
|
|
447
|
-
└── extensions/
|
|
448
|
-
├── math/ # math.add, math.multiply
|
|
449
|
-
├── text/ # text.upper, text.reverse, text.wordcount
|
|
450
|
-
└── sysutil/ # sysutil.info, sysutil.env, sysutil.disk
|
|
451
|
-
|
|
452
|
-
planning/ # Implementation plans (TDD task breakdowns)
|
|
453
|
-
├── overview.md
|
|
454
|
-
├── state.json
|
|
455
|
-
└── *.md # Per-feature plans
|
|
456
|
-
```
|
|
457
|
-
|
|
458
426
|
## License
|
|
459
427
|
|
|
460
428
|
Apache-2.0
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/
|
|
2
|
+
<img src="https://raw.githubusercontent.com/aiperceivable/apcore-cli/main/apcore-cli-logo.svg" alt="apcore-cli logo" width="200"/>
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
5
|
# apcore-cli
|
|
@@ -8,15 +8,15 @@ Terminal adapter for apcore. Execute AI-Perceivable modules from the command lin
|
|
|
8
8
|
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](https://python.org)
|
|
11
|
-
[]()
|
|
12
12
|
|
|
13
13
|
| | |
|
|
14
14
|
|---|---|
|
|
15
|
-
| **Python SDK** | [github.com/
|
|
16
|
-
| **Spec repo** | [github.com/
|
|
17
|
-
| **apcore core** | [github.com/
|
|
15
|
+
| **Python SDK** | [github.com/aiperceivable/apcore-cli-python](https://github.com/aiperceivable/apcore-cli-python) |
|
|
16
|
+
| **Spec repo** | [github.com/aiperceivable/apcore-cli](https://github.com/aiperceivable/apcore-cli) |
|
|
17
|
+
| **apcore core** | [github.com/aiperceivable/apcore](https://github.com/aiperceivable/apcore) |
|
|
18
18
|
|
|
19
|
-
**apcore-cli** turns any [apcore](https://github.com/
|
|
19
|
+
**apcore-cli** turns any [apcore](https://github.com/aiperceivable/apcore)-based project into a fully featured CLI tool — with **zero code changes** to your existing modules.
|
|
20
20
|
|
|
21
21
|
```
|
|
22
22
|
┌──────────────────┐
|
|
@@ -57,7 +57,7 @@ Requires Python 3.11+ and `apcore >= 0.13.0`.
|
|
|
57
57
|
The repo includes 8 example modules you can run immediately:
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
git clone https://github.com/
|
|
60
|
+
git clone https://github.com/aiperceivable/apcore-cli-python.git
|
|
61
61
|
cd apcore-cli-python
|
|
62
62
|
pip install -e ".[dev]"
|
|
63
63
|
|
|
@@ -231,6 +231,7 @@ apcore-cli uses a 4-tier configuration precedence:
|
|
|
231
231
|
| `APCORE_LOGGING_LEVEL` | Global apcore log level (fallback when `APCORE_CLI_LOGGING_LEVEL` is unset) | `WARNING` |
|
|
232
232
|
| `APCORE_AUTH_API_KEY` | API key for remote registry authentication | *(unset)* |
|
|
233
233
|
| `APCORE_CLI_SANDBOX` | Set to `1` to enable subprocess sandboxing | *(unset)* |
|
|
234
|
+
| `APCORE_CLI_HELP_TEXT_MAX_LENGTH` | Maximum characters for CLI option help text before truncation | `1000` |
|
|
234
235
|
|
|
235
236
|
### Config File (`apcore.yaml`)
|
|
236
237
|
|
|
@@ -241,6 +242,8 @@ logging:
|
|
|
241
242
|
level: DEBUG
|
|
242
243
|
sandbox:
|
|
243
244
|
enabled: false
|
|
245
|
+
cli:
|
|
246
|
+
help_text_max_length: 1000
|
|
244
247
|
```
|
|
245
248
|
|
|
246
249
|
## Features
|
|
@@ -280,10 +283,10 @@ apcore-cli (the adapter)
|
|
|
280
283
|
|
|
|
281
284
|
+-- ConfigResolver 4-tier config precedence
|
|
282
285
|
+-- LazyModuleGroup Dynamic Click command generation
|
|
283
|
-
+--
|
|
284
|
-
+--
|
|
285
|
-
+--
|
|
286
|
-
+--
|
|
286
|
+
+-- schema_parser JSON Schema -> Click options
|
|
287
|
+
+-- ref_resolver $ref / allOf / anyOf / oneOf
|
|
288
|
+
+-- approval TTY-aware HITL approval
|
|
289
|
+
+-- output TTY-adaptive JSON/table output
|
|
287
290
|
+-- AuditLogger JSON Lines execution logging
|
|
288
291
|
+-- Sandbox Subprocess isolation
|
|
289
292
|
|
|
|
@@ -375,49 +378,14 @@ apcore-cli --extensions-dir ./extensions greet.hello --name Alice --greeting Hi
|
|
|
375
378
|
## Development
|
|
376
379
|
|
|
377
380
|
```bash
|
|
378
|
-
git clone https://github.com/
|
|
381
|
+
git clone https://github.com/aiperceivable/apcore-cli-python.git
|
|
379
382
|
cd apcore-cli-python
|
|
380
383
|
pip install -e ".[dev]"
|
|
381
|
-
pytest #
|
|
384
|
+
pytest # 263 tests
|
|
382
385
|
pytest --cov # with coverage report
|
|
383
386
|
bash examples/run_examples.sh # run all examples
|
|
384
387
|
```
|
|
385
388
|
|
|
386
|
-
### Project Structure
|
|
387
|
-
|
|
388
|
-
```
|
|
389
|
-
src/apcore_cli/
|
|
390
|
-
├── __init__.py # Package version
|
|
391
|
-
├── __main__.py # CLI entry point, wiring
|
|
392
|
-
├── cli.py # LazyModuleGroup, build_module_command, collect_input
|
|
393
|
-
├── config.py # ConfigResolver (4-tier precedence)
|
|
394
|
-
├── schema_parser.py # JSON Schema -> Click options
|
|
395
|
-
├── ref_resolver.py # $ref / allOf / anyOf / oneOf resolution
|
|
396
|
-
├── output.py # TTY-adaptive output formatting (rich)
|
|
397
|
-
├── discovery.py # list / describe commands
|
|
398
|
-
├── approval.py # HITL approval gate with timeout
|
|
399
|
-
├── shell.py # bash/zsh/fish completion + man pages
|
|
400
|
-
├── _sandbox_runner.py # Subprocess entry point for sandboxed execution
|
|
401
|
-
└── security/
|
|
402
|
-
├── __init__.py # Exports
|
|
403
|
-
├── auth.py # API key authentication
|
|
404
|
-
├── config_encryptor.py # Keyring + AES-256-GCM encrypted config
|
|
405
|
-
├── audit.py # JSON Lines audit logging
|
|
406
|
-
└── sandbox.py # Subprocess-based execution isolation
|
|
407
|
-
|
|
408
|
-
examples/
|
|
409
|
-
├── run_examples.sh # Run all examples end-to-end
|
|
410
|
-
└── extensions/
|
|
411
|
-
├── math/ # math.add, math.multiply
|
|
412
|
-
├── text/ # text.upper, text.reverse, text.wordcount
|
|
413
|
-
└── sysutil/ # sysutil.info, sysutil.env, sysutil.disk
|
|
414
|
-
|
|
415
|
-
planning/ # Implementation plans (TDD task breakdowns)
|
|
416
|
-
├── overview.md
|
|
417
|
-
├── state.json
|
|
418
|
-
└── *.md # Per-feature plans
|
|
419
|
-
```
|
|
420
|
-
|
|
421
389
|
## License
|
|
422
390
|
|
|
423
391
|
Apache-2.0
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
- `tests/test_schema_parser.py`:
|
|
84
84
|
- `test_help_from_x_llm_description`: `x-llm-description` present → used over `description`.
|
|
85
85
|
- `test_help_from_description`: Only `description` → used.
|
|
86
|
-
- `
|
|
86
|
+
- `test_help_truncation_default`: Description > 1000 chars (default) → truncated to 997 + "...".
|
|
87
87
|
- `test_help_none`: No description fields → `None`.
|
|
88
88
|
- `test_flag_collision_detection`: Properties `foo_bar` and `foo-bar` both map to `--foo-bar` → exit 48.
|
|
89
89
|
|
|
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "apcore-cli"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
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"
|
|
11
11
|
requires-python = ">=3.11"
|
|
12
12
|
authors = [
|
|
13
|
-
{ name = "
|
|
13
|
+
{ name = "aiperceivable", email = "team@aiperceivable.com" },
|
|
14
14
|
]
|
|
15
15
|
keywords = ["apcore", "cli", "terminal", "ai", "agent", "llm", "command-line", "automation", "shell", "unix"]
|
|
16
16
|
classifiers = [
|
|
@@ -26,7 +26,7 @@ classifiers = [
|
|
|
26
26
|
"Environment :: Console",
|
|
27
27
|
]
|
|
28
28
|
dependencies = [
|
|
29
|
-
"apcore>=0.13.
|
|
29
|
+
"apcore>=0.13.1",
|
|
30
30
|
"click>=8.1",
|
|
31
31
|
"jsonschema>=4.20",
|
|
32
32
|
"rich>=13.0",
|
|
@@ -48,10 +48,10 @@ dev = [
|
|
|
48
48
|
apcore-cli = "apcore_cli.__main__:main"
|
|
49
49
|
|
|
50
50
|
[project.urls]
|
|
51
|
-
Homepage = "https://
|
|
52
|
-
Repository = "https://github.com/
|
|
53
|
-
Documentation = "https://github.com/
|
|
54
|
-
Issues = "https://github.com/
|
|
51
|
+
Homepage = "https://aiperceivable.com"
|
|
52
|
+
Repository = "https://github.com/aiperceivable/apcore-cli-python"
|
|
53
|
+
Documentation = "https://github.com/aiperceivable/apcore-cli-python#readme"
|
|
54
|
+
Issues = "https://github.com/aiperceivable/apcore-cli-python/issues"
|
|
55
55
|
|
|
56
56
|
[tool.hatch.build.targets.wheel]
|
|
57
57
|
packages = ["src/apcore_cli"]
|
|
@@ -65,16 +65,26 @@ def create_cli(extensions_dir: str | None = None, prog_name: str | None = None)
|
|
|
65
65
|
apcore_level = _default_level if _default_level <= logging.INFO else logging.ERROR
|
|
66
66
|
logging.getLogger("apcore").setLevel(apcore_level)
|
|
67
67
|
|
|
68
|
+
config = ConfigResolver()
|
|
69
|
+
|
|
68
70
|
if extensions_dir is not None:
|
|
69
71
|
ext_dir = extensions_dir
|
|
70
72
|
else:
|
|
71
|
-
config = ConfigResolver()
|
|
72
73
|
ext_dir = config.resolve(
|
|
73
74
|
"extensions.root",
|
|
74
75
|
cli_flag="--extensions-dir",
|
|
75
76
|
env_var="APCORE_EXTENSIONS_ROOT",
|
|
76
77
|
)
|
|
77
78
|
|
|
79
|
+
help_text_max_length = config.resolve(
|
|
80
|
+
"cli.help_text_max_length",
|
|
81
|
+
env_var="APCORE_CLI_HELP_TEXT_MAX_LENGTH",
|
|
82
|
+
)
|
|
83
|
+
try:
|
|
84
|
+
help_text_max_length = int(help_text_max_length)
|
|
85
|
+
except (TypeError, ValueError):
|
|
86
|
+
help_text_max_length = 1000
|
|
87
|
+
|
|
78
88
|
ext_dir_missing = not os.path.exists(ext_dir)
|
|
79
89
|
ext_dir_unreadable = not ext_dir_missing and not os.access(ext_dir, os.R_OK)
|
|
80
90
|
|
|
@@ -119,6 +129,7 @@ def create_cli(extensions_dir: str | None = None, prog_name: str | None = None)
|
|
|
119
129
|
cls=LazyModuleGroup,
|
|
120
130
|
registry=registry,
|
|
121
131
|
executor=executor,
|
|
132
|
+
help_text_max_length=help_text_max_length,
|
|
122
133
|
name=prog_name,
|
|
123
134
|
help="CLI adapter for the apcore module ecosystem.",
|
|
124
135
|
)
|
|
@@ -41,10 +41,17 @@ def set_audit_logger(audit_logger: AuditLogger | None) -> None:
|
|
|
41
41
|
class LazyModuleGroup(click.Group):
|
|
42
42
|
"""Custom Click Group that lazily loads apcore modules as subcommands."""
|
|
43
43
|
|
|
44
|
-
def __init__(
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
registry: Registry,
|
|
47
|
+
executor: Executor,
|
|
48
|
+
help_text_max_length: int = 1000,
|
|
49
|
+
**kwargs: Any,
|
|
50
|
+
) -> None:
|
|
45
51
|
super().__init__(**kwargs)
|
|
46
52
|
self._registry = registry
|
|
47
53
|
self._executor = executor
|
|
54
|
+
self._help_text_max_length = help_text_max_length
|
|
48
55
|
self._module_cache: dict[str, click.Command] = {}
|
|
49
56
|
|
|
50
57
|
def list_commands(self, ctx: click.Context) -> list[str]:
|
|
@@ -70,7 +77,7 @@ class LazyModuleGroup(click.Group):
|
|
|
70
77
|
if module_def is None:
|
|
71
78
|
return None
|
|
72
79
|
|
|
73
|
-
cmd = build_module_command(module_def, self._executor)
|
|
80
|
+
cmd = build_module_command(module_def, self._executor, help_text_max_length=self._help_text_max_length)
|
|
74
81
|
self._module_cache[cmd_name] = cmd
|
|
75
82
|
return cmd
|
|
76
83
|
|
|
@@ -101,7 +108,11 @@ def _get_module_id(module_def: ModuleDescriptor) -> str:
|
|
|
101
108
|
return module_def.module_id
|
|
102
109
|
|
|
103
110
|
|
|
104
|
-
def build_module_command(
|
|
111
|
+
def build_module_command(
|
|
112
|
+
module_def: ModuleDescriptor,
|
|
113
|
+
executor: Executor,
|
|
114
|
+
help_text_max_length: int = 1000,
|
|
115
|
+
) -> click.Command:
|
|
105
116
|
"""Build a Click command from an apcore module definition.
|
|
106
117
|
|
|
107
118
|
Generates Click options from the module's input_schema, wires up
|
|
@@ -137,7 +148,7 @@ def build_module_command(module_def: ModuleDescriptor, executor: Executor) -> cl
|
|
|
137
148
|
else:
|
|
138
149
|
resolved_schema = input_schema
|
|
139
150
|
|
|
140
|
-
schema_options = schema_to_click_options(resolved_schema)
|
|
151
|
+
schema_options = schema_to_click_options(resolved_schema, max_help_length=help_text_max_length)
|
|
141
152
|
|
|
142
153
|
def callback(**kwargs: Any) -> None:
|
|
143
154
|
# Separate built-in options from schema-generated kwargs
|
|
@@ -18,10 +18,11 @@ class ConfigResolver:
|
|
|
18
18
|
|
|
19
19
|
DEFAULTS: dict[str, Any] = {
|
|
20
20
|
"extensions.root": "./extensions",
|
|
21
|
-
"logging.level": "
|
|
21
|
+
"logging.level": "WARNING",
|
|
22
22
|
"sandbox.enabled": False,
|
|
23
23
|
"cli.stdin_buffer_limit": 10_485_760, # 10 MB
|
|
24
24
|
"cli.auto_approve": False,
|
|
25
|
+
"cli.help_text_max_length": 1000,
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
def __init__(
|
|
@@ -50,19 +50,19 @@ def _map_type(prop_name: str, prop_schema: dict) -> Any:
|
|
|
50
50
|
return result
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def _extract_help(prop_schema: dict) -> str | None:
|
|
53
|
+
def _extract_help(prop_schema: dict, max_length: int = 1000) -> str | None:
|
|
54
54
|
"""Extract help text from schema property, preferring x-llm-description."""
|
|
55
55
|
text = prop_schema.get("x-llm-description")
|
|
56
56
|
if not text:
|
|
57
57
|
text = prop_schema.get("description")
|
|
58
58
|
if not text:
|
|
59
59
|
return None
|
|
60
|
-
if len(text) >
|
|
61
|
-
return text[:
|
|
60
|
+
if max_length > 0 and len(text) > max_length:
|
|
61
|
+
return text[: max_length - 3] + "..."
|
|
62
62
|
return text
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
def schema_to_click_options(schema: dict) -> list[click.Option]:
|
|
65
|
+
def schema_to_click_options(schema: dict, max_help_length: int = 1000) -> list[click.Option]:
|
|
66
66
|
"""Convert JSON Schema properties to a list of Click options."""
|
|
67
67
|
properties = schema.get("properties", {})
|
|
68
68
|
required_list = schema.get("required", [])
|
|
@@ -93,7 +93,7 @@ def schema_to_click_options(schema: dict) -> list[click.Option]:
|
|
|
93
93
|
|
|
94
94
|
click_type = _map_type(prop_name, prop_schema)
|
|
95
95
|
is_required = prop_name in required_list
|
|
96
|
-
_help_base = _extract_help(prop_schema)
|
|
96
|
+
_help_base = _extract_help(prop_schema, max_length=max_help_length)
|
|
97
97
|
# Append [required] to help text for user clarity; do NOT set required=True
|
|
98
98
|
# at the Click level because that would block --input - (STDIN) from working.
|
|
99
99
|
# Schema-level required validation happens in the callback via jsonschema.validate().
|
|
@@ -205,9 +205,20 @@ class TestHelpAndCollisions:
|
|
|
205
205
|
result = _extract_help({"description": "Regular help"})
|
|
206
206
|
assert result == "Regular help"
|
|
207
207
|
|
|
208
|
-
def
|
|
209
|
-
long_text = "x" *
|
|
208
|
+
def test_help_truncation_default(self):
|
|
209
|
+
long_text = "x" * 1100
|
|
210
210
|
result = _extract_help({"description": long_text})
|
|
211
|
+
assert len(result) == 1000
|
|
212
|
+
assert result.endswith("...")
|
|
213
|
+
|
|
214
|
+
def test_help_no_truncation_within_limit(self):
|
|
215
|
+
text = "x" * 999
|
|
216
|
+
result = _extract_help({"description": text})
|
|
217
|
+
assert result == text
|
|
218
|
+
|
|
219
|
+
def test_help_truncation_custom_max(self):
|
|
220
|
+
long_text = "x" * 300
|
|
221
|
+
result = _extract_help({"description": long_text}, max_length=200)
|
|
211
222
|
assert len(result) == 200
|
|
212
223
|
assert result.endswith("...")
|
|
213
224
|
|
|
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
|