strictcli 0.20.1__tar.gz → 0.22.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.
- strictcli-0.22.0/.rlsbl/version +1 -0
- strictcli-0.22.0/CHANGELOG.md +46 -0
- strictcli-0.22.0/PKG-INFO +390 -0
- strictcli-0.22.0/README.md +372 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/package-lock.json +2 -2
- {strictcli-0.20.1 → strictcli-0.22.0}/package.json +1 -1
- {strictcli-0.20.1 → strictcli-0.22.0}/pyproject.toml +1 -1
- {strictcli-0.20.1 → strictcli-0.22.0}/strictcli/__init__.py +2484 -324
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_arg_default.py +2 -2
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_at_prefix.py +1 -1
- strictcli-0.22.0/tests/test_call.py +552 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_check_discovery.py +132 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_check_public_api.py +64 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_check_runner.py +170 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_check_schema.py +57 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_command_tags.py +18 -5
- strictcli-0.22.0/tests/test_compound_types.py +1057 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_config.py +2 -2
- strictcli-0.22.0/tests/test_config_fields.py +995 -0
- strictcli-0.22.0/tests/test_context.py +354 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_deep_nesting.py +2 -2
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_dependencies.py +18 -18
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_dump_schema.py +352 -3
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_e2e.py +5 -5
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_env.py +1 -1
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_flag_sets.py +5 -5
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_global_flags.py +10 -10
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_help.py +2 -2
- strictcli-0.22.0/tests/test_invoke.py +631 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_keyword_flags.py +3 -3
- strictcli-0.22.0/tests/test_mcp.py +632 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_mutex.py +28 -28
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_parser.py +3 -3
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_passthrough.py +5 -5
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_registration.py +5 -5
- strictcli-0.22.0/tests/test_tool_export.py +923 -0
- strictcli-0.22.0/tests/test_typed_args.py +849 -0
- strictcli-0.22.0/tests/test_visibility.py +439 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/uv.lock +1 -1
- strictcli-0.20.1/.rlsbl/bases/.claude/settings.json +0 -3
- strictcli-0.20.1/.rlsbl/bases/.github/workflows/ci.yml +0 -22
- strictcli-0.20.1/.rlsbl/bases/.github/workflows/publish.yml +0 -27
- strictcli-0.20.1/.rlsbl/bases/.gitignore +0 -15
- strictcli-0.20.1/.rlsbl/bases/.npmignore +0 -34
- strictcli-0.20.1/.rlsbl/bases/.rlsbl/hooks/post-release.sh +0 -8
- strictcli-0.20.1/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +0 -5
- strictcli-0.20.1/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +0 -8
- strictcli-0.20.1/.rlsbl/bases/.rlsbl/lint/npm.toml +0 -19
- strictcli-0.20.1/.rlsbl/bases/.rlsbl/lint/python.toml +0 -25
- strictcli-0.20.1/.rlsbl/bases/CHANGELOG.md +0 -5
- strictcli-0.20.1/.rlsbl/bases/CLAUDE.md +0 -20
- strictcli-0.20.1/.rlsbl/changes/.validated +0 -1
- strictcli-0.20.1/.rlsbl/changes/0.10.0.jsonl +0 -6
- strictcli-0.20.1/.rlsbl/changes/0.10.0.md +0 -6
- strictcli-0.20.1/.rlsbl/changes/0.11.0.jsonl +0 -7
- strictcli-0.20.1/.rlsbl/changes/0.11.0.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.12.0.jsonl +0 -2
- strictcli-0.20.1/.rlsbl/changes/0.12.0.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.13.0.jsonl +0 -3
- strictcli-0.20.1/.rlsbl/changes/0.13.0.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.14.0.jsonl +0 -3
- strictcli-0.20.1/.rlsbl/changes/0.14.0.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.15.0.jsonl +0 -6
- strictcli-0.20.1/.rlsbl/changes/0.15.0.md +0 -18
- strictcli-0.20.1/.rlsbl/changes/0.15.1.jsonl +0 -6
- strictcli-0.20.1/.rlsbl/changes/0.15.1.md +0 -18
- strictcli-0.20.1/.rlsbl/changes/0.16.0.jsonl +0 -9
- strictcli-0.20.1/.rlsbl/changes/0.16.0.md +0 -27
- strictcli-0.20.1/.rlsbl/changes/0.16.1.jsonl +0 -3
- strictcli-0.20.1/.rlsbl/changes/0.16.1.md +0 -8
- strictcli-0.20.1/.rlsbl/changes/0.17.0.jsonl +0 -3
- strictcli-0.20.1/.rlsbl/changes/0.17.0.md +0 -11
- strictcli-0.20.1/.rlsbl/changes/0.18.0.jsonl +0 -4
- strictcli-0.20.1/.rlsbl/changes/0.18.0.md +0 -14
- strictcli-0.20.1/.rlsbl/changes/0.19.0.jsonl +0 -2
- strictcli-0.20.1/.rlsbl/changes/0.19.0.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.20.0.jsonl +0 -4
- strictcli-0.20.1/.rlsbl/changes/0.20.0.md +0 -19
- strictcli-0.20.1/.rlsbl/changes/0.20.1.jsonl +0 -3
- strictcli-0.20.1/.rlsbl/changes/0.20.1.md +0 -14
- strictcli-0.20.1/.rlsbl/changes/0.4.0.jsonl +0 -29
- strictcli-0.20.1/.rlsbl/changes/0.4.0.md +0 -16
- strictcli-0.20.1/.rlsbl/changes/0.4.1.jsonl +0 -4
- strictcli-0.20.1/.rlsbl/changes/0.4.1.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.5.0.jsonl +0 -3
- strictcli-0.20.1/.rlsbl/changes/0.5.0.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.6.0.jsonl +0 -3
- strictcli-0.20.1/.rlsbl/changes/0.6.0.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.6.1.jsonl +0 -6
- strictcli-0.20.1/.rlsbl/changes/0.6.1.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.7.0.jsonl +0 -14
- strictcli-0.20.1/.rlsbl/changes/0.7.0.md +0 -10
- strictcli-0.20.1/.rlsbl/changes/0.7.1.jsonl +0 -12
- strictcli-0.20.1/.rlsbl/changes/0.7.1.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.8.0.jsonl +0 -7
- strictcli-0.20.1/.rlsbl/changes/0.8.0.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.8.1.jsonl +0 -2
- strictcli-0.20.1/.rlsbl/changes/0.8.1.md +0 -3
- strictcli-0.20.1/.rlsbl/changes/0.8.2.jsonl +0 -1
- strictcli-0.20.1/.rlsbl/changes/0.8.2.md +0 -3
- strictcli-0.20.1/.rlsbl/changes/0.8.3.jsonl +0 -1
- strictcli-0.20.1/.rlsbl/changes/0.8.3.md +0 -3
- strictcli-0.20.1/.rlsbl/changes/0.8.4.jsonl +0 -3
- strictcli-0.20.1/.rlsbl/changes/0.8.4.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.8.5.jsonl +0 -7
- strictcli-0.20.1/.rlsbl/changes/0.8.5.md +0 -9
- strictcli-0.20.1/.rlsbl/changes/0.8.6.jsonl +0 -6
- strictcli-0.20.1/.rlsbl/changes/0.8.6.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.8.7.jsonl +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.8.7.md +0 -9
- strictcli-0.20.1/.rlsbl/changes/0.9.0.jsonl +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.9.0.md +0 -5
- strictcli-0.20.1/.rlsbl/changes/0.9.1.jsonl +0 -9
- strictcli-0.20.1/.rlsbl/changes/0.9.1.md +0 -9
- strictcli-0.20.1/.rlsbl/changes/unreleased.jsonl +0 -0
- strictcli-0.20.1/.rlsbl/hooks/post-release.sh +0 -8
- strictcli-0.20.1/.rlsbl/hooks/pre-checks.sh +0 -10
- strictcli-0.20.1/.rlsbl/hooks/pre-release.sh +0 -13
- strictcli-0.20.1/.rlsbl/lint/npm.toml +0 -19
- strictcli-0.20.1/.rlsbl/lint/python.toml +0 -25
- strictcli-0.20.1/.rlsbl/releases/unreleased.toml +0 -0
- strictcli-0.20.1/.rlsbl/releases/v0.10.0.toml +0 -3
- strictcli-0.20.1/.rlsbl/releases/v0.11.0.toml +0 -3
- strictcli-0.20.1/.rlsbl/releases/v0.12.0.toml +0 -4
- strictcli-0.20.1/.rlsbl/releases/v0.13.0.toml +0 -4
- strictcli-0.20.1/.rlsbl/releases/v0.15.0.toml +0 -8
- strictcli-0.20.1/.rlsbl/releases/v0.15.1.toml +0 -8
- strictcli-0.20.1/.rlsbl/releases/v0.16.0.toml +0 -8
- strictcli-0.20.1/.rlsbl/releases/v0.16.1.toml +0 -8
- strictcli-0.20.1/.rlsbl/releases/v0.17.0.toml +0 -8
- strictcli-0.20.1/.rlsbl/releases/v0.18.0.toml +0 -8
- strictcli-0.20.1/.rlsbl/releases/v0.19.0.toml +0 -0
- strictcli-0.20.1/.rlsbl/releases/v0.20.0.toml +0 -8
- strictcli-0.20.1/.rlsbl/releases/v0.20.1.toml +0 -8
- strictcli-0.20.1/.rlsbl/releases/v0.8.5.toml +0 -3
- strictcli-0.20.1/.rlsbl/releases/v0.8.6.toml +0 -3
- strictcli-0.20.1/.rlsbl/releases/v0.8.7.toml +0 -3
- strictcli-0.20.1/.rlsbl/releases/v0.9.0.toml +0 -3
- strictcli-0.20.1/.rlsbl/releases/v0.9.1.toml +0 -3
- strictcli-0.20.1/.rlsbl/version +0 -1
- strictcli-0.20.1/CHANGELOG.md +0 -301
- strictcli-0.20.1/LICENSE +0 -21
- strictcli-0.20.1/PKG-INFO +0 -492
- strictcli-0.20.1/README.md +0 -474
- {strictcli-0.20.1 → strictcli-0.22.0}/.claude/settings.json +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/.github/workflows/ci.yml +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/.github/workflows/publish.yml +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/.gitignore +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/.npmignore +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/.rlsbl/config.json +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/.rlsbl/hashes.json +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/.strictcli/schema.json +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/CLAUDE.md +0 -0
- {strictcli-0.20.1/.rlsbl/bases → strictcli-0.22.0}/LICENSE +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/index.js +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/postinstall.js +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_auto_version.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_check_command.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_check_types.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_choices.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_command_help_suggestion.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_config_file_path.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_config_set_bugs.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_deprecated.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_exit_codes.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_float_type.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_int_type.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_nesting.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_repeatable.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_tagdsl.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_toml_loading.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_unique.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_utilities.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_validate.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_variadic.py +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/todo/.defer/deferred.md +0 -0
- {strictcli-0.20.1/todo → strictcli-0.22.0/todo/.done}/keyword-collision-in-flag-param-name.md +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/todo/.done/original-idea.md +0 -0
- {strictcli-0.20.1 → strictcli-0.22.0}/todo/.done/public-check-runner-api.md +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.92.0
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<!-- Generated by rlsbl from .rlsbl/changes/ — do not edit -->
|
|
2
|
+
|
|
3
|
+
# Changelog
|
|
4
|
+
|
|
5
|
+
## 0.22.0
|
|
6
|
+
|
|
7
|
+
Required booleans, Context type, schema project_id guard, tag contract global flags fix
|
|
8
|
+
|
|
9
|
+
<details>
|
|
10
|
+
<summary>Context</summary>
|
|
11
|
+
|
|
12
|
+
Breaking change: Bool flags no longer auto-default to false. BoolFlag without explicit Default(false) is now required. New Context type for structured handler communication. Schema dump now validates project_id. TagContract now checks global flags.
|
|
13
|
+
|
|
14
|
+
</details>
|
|
15
|
+
|
|
16
|
+
### Breaking
|
|
17
|
+
|
|
18
|
+
- **Breaking.** Bool flags no longer auto-default to false. BoolFlag without explicit Default(false) is now required — user must pass --flag or --no-flag.
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- **New.** Schema dump validates project_id before writing — hard error if existing schema belongs to a different project.
|
|
23
|
+
- **New.** Context type with output methods (Info, Warn, Debug, Error, Emit) for structured handler communication.
|
|
24
|
+
- **New.** Comprehensive sub-project READMEs replacing severely stale documentation.
|
|
25
|
+
|
|
26
|
+
### Fixes
|
|
27
|
+
|
|
28
|
+
- **Fix.** TagContract now checks global flags, not just command-level flags.
|
|
29
|
+
|
|
30
|
+
## 0.21.0
|
|
31
|
+
|
|
32
|
+
### Features
|
|
33
|
+
|
|
34
|
+
- **New feature.** Programmatic invocation via `app.call()` and `app.acall()` with `InvokeError` exception for structured error handling.
|
|
35
|
+
- **New feature.** Schema versioning with `schema_version` field, constraint serialization (mutex, CoRequired, Requires, Implies), tag contracts, and arg defaults in `--dump-schema` output.
|
|
36
|
+
- **New feature.** Typed positional args with `type` (str/int/float/bool) and `choices` support, matching the flag type system.
|
|
37
|
+
- **New feature.** Command visibility: `hidden` and `interactive` declarations with help text filtering and schema serialization.
|
|
38
|
+
- **New feature.** First-class config fields with `config_field()` declarations, per-command binding, startup validation, `config init` template generation, and unknown-key rejection.
|
|
39
|
+
- **New feature.** Compound types `list[T]` and `dict[str, T]` on flags and args with projection-specific CLI parsing.
|
|
40
|
+
- **New feature.** Tool export: `json_schema()`, `as_tools()`, `Tool` dataclass, and router tool for AI agent integration.
|
|
41
|
+
- **New feature.** MCP projection via `serve_mcp()` and `--mcp` flag for Model Context Protocol tool serving.
|
|
42
|
+
- **New feature.** Optional `scope` field on check definitions for declarative pre-check context filtering, and `set_scope_adapter()` method on App for registering scope transformation callbacks.
|
|
43
|
+
|
|
44
|
+
### Fixes
|
|
45
|
+
|
|
46
|
+
- **Fix.** Validate unknown kwargs and enforce required globals in passthrough invoke path.
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: strictcli
|
|
3
|
+
Version: 0.22.0
|
|
4
|
+
Summary: A strict, zero-dependency CLI framework for Python
|
|
5
|
+
Project-URL: Homepage, https://github.com/smm-h/strictcli
|
|
6
|
+
Project-URL: Repository, https://github.com/smm-h/strictcli
|
|
7
|
+
Author-email: "S. M. Hosseini" <m.hosseini@veliu.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: argparse,cli,command-line,framework,rlsbl,strictcli
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# strictcli
|
|
20
|
+
|
|
21
|
+
A strict, zero-dependency CLI framework for Python.
|
|
22
|
+
|
|
23
|
+
strictcli makes you declare everything -- every command, flag, argument, and environment variable must have help text or the framework errors at registration time. Four types only: `str`, `bool`, `int`, `float`. No magic type inference, no implicit defaults.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
pip install strictcli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or with uv:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
uv add strictcli
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Requires Python 3.11+. Zero external dependencies.
|
|
38
|
+
|
|
39
|
+
## Quickstart
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
import strictcli
|
|
43
|
+
|
|
44
|
+
app = strictcli.App("greet", version="1.0.0", help="A greeting app")
|
|
45
|
+
|
|
46
|
+
@app.command("hello", help="Say hello")
|
|
47
|
+
@strictcli.flag("name", type=str, help="Who to greet")
|
|
48
|
+
@strictcli.flag("loud", type=bool, help="Shout it")
|
|
49
|
+
def hello(name, loud):
|
|
50
|
+
msg = f"Hello, {name}!"
|
|
51
|
+
print(msg.upper() if loud else msg)
|
|
52
|
+
|
|
53
|
+
app.run()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
$ python greet.py hello --name World
|
|
58
|
+
Hello, World!
|
|
59
|
+
|
|
60
|
+
$ python greet.py hello --name World --loud
|
|
61
|
+
HELLO, WORLD!
|
|
62
|
+
|
|
63
|
+
$ python greet.py hello --help
|
|
64
|
+
greet hello -- Say hello
|
|
65
|
+
|
|
66
|
+
Flags:
|
|
67
|
+
--name <str> Who to greet
|
|
68
|
+
--loud, --no-loud Shout it [default: false]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Features
|
|
72
|
+
|
|
73
|
+
### Commands and groups
|
|
74
|
+
|
|
75
|
+
Top-level commands with `@app.command`, nested groups with `app.group`. Groups nest recursively to arbitrary depth via `group.group`.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
db = app.group("db", help="Database operations")
|
|
79
|
+
schema = db.group("schema", help="Schema management")
|
|
80
|
+
|
|
81
|
+
@schema.command("migrate", help="Run migrations")
|
|
82
|
+
def migrate():
|
|
83
|
+
print("migrating")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Invoked as `myapp db schema migrate`.
|
|
87
|
+
|
|
88
|
+
### Four flag types
|
|
89
|
+
|
|
90
|
+
`str`, `bool`, `int`, and `float`. No magic coercion -- parse errors are clear and immediate.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
@strictcli.flag("port", type=int, help="Port number")
|
|
94
|
+
@strictcli.flag("threshold", type=float, help="Score threshold")
|
|
95
|
+
@strictcli.flag("verbose", type=bool, help="Verbose output")
|
|
96
|
+
@strictcli.flag("output", type=str, help="Output path", default="out.txt")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Bool flags default to `False`, support `--flag` / `--no-flag` negation (disable with `negatable=False`). Float parsing rejects NaN and Inf.
|
|
100
|
+
|
|
101
|
+
### Compound types
|
|
102
|
+
|
|
103
|
+
`list[T]` and `dict[str, T]` for collecting multiple values.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
@strictcli.flag("tags", type=list[str], help="Tags to apply", unique=True)
|
|
107
|
+
@strictcli.flag("env", type=dict[str, str], help="Environment variables")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
List flags accept `--tags a --tags b`. Dict flags accept `--env KEY=VALUE` pairs or JSON objects.
|
|
111
|
+
|
|
112
|
+
### Positional arguments
|
|
113
|
+
|
|
114
|
+
Two equivalent declaration forms. Arguments can be required, optional (with `required=False`), or variadic.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
# Decorator form
|
|
118
|
+
@app.command("show", help="Show a file")
|
|
119
|
+
@strictcli.arg("path", help="File to show")
|
|
120
|
+
def show(path): ...
|
|
121
|
+
|
|
122
|
+
# Inline form
|
|
123
|
+
@app.command("copy", help="Copy files", args=[
|
|
124
|
+
strictcli.Arg(name="src", help="Source"),
|
|
125
|
+
strictcli.Arg(name="dst", help="Destination"),
|
|
126
|
+
])
|
|
127
|
+
def copy(src, dst): ...
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Short flag aliases
|
|
131
|
+
|
|
132
|
+
Single-character shortcuts for any flag.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
@strictcli.flag("verbose", short="v", type=bool, help="Verbose output")
|
|
136
|
+
@strictcli.flag("output", short="o", type=str, help="Output path", default=".")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Environment variable binding
|
|
140
|
+
|
|
141
|
+
Flags can be backed by environment variables. Prefix enforcement keeps your config namespace clean.
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
app = strictcli.App("myapp", version="1.0.0", help="My app", env_prefix="MYAPP")
|
|
145
|
+
|
|
146
|
+
@strictcli.flag("region", type=str, help="Cloud region", env="MYAPP_REGION", default="us-east-1")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
All env vars must start with the declared prefix. Use `prefixed=False` for external env vars like `GITHUB_TOKEN`. Precedence: CLI > env > config > default.
|
|
150
|
+
|
|
151
|
+
Bool env vars accept `1|true|yes` / `0|false|no` (case-insensitive).
|
|
152
|
+
|
|
153
|
+
### FlagSets
|
|
154
|
+
|
|
155
|
+
Reusable bundles of flags shared across commands.
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
auth_flags = strictcli.FlagSet(
|
|
159
|
+
name="auth",
|
|
160
|
+
flags=[
|
|
161
|
+
strictcli.Flag(name="token", type=str, help="Auth token", default=""),
|
|
162
|
+
strictcli.Flag(name="insecure", type=bool, help="Skip TLS verification"),
|
|
163
|
+
],
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
@app.command("deploy", help="Deploy", flag_sets=[auth_flags])
|
|
167
|
+
def deploy(token, insecure): ...
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Mutually exclusive flag groups
|
|
171
|
+
|
|
172
|
+
Exactly one flag from the group must be provided.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
@app.command("log", help="Show logs", mutex=[
|
|
176
|
+
strictcli.MutexGroup(flags=[
|
|
177
|
+
strictcli.Flag(name="verbose", type=bool, help="Verbose output"),
|
|
178
|
+
strictcli.Flag(name="quiet", type=bool, help="Quiet output"),
|
|
179
|
+
]),
|
|
180
|
+
])
|
|
181
|
+
def log(verbose, quiet): ...
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Flag dependencies
|
|
185
|
+
|
|
186
|
+
Three relationship types, all passed via `dependencies=[...]`:
|
|
187
|
+
|
|
188
|
+
- `CoRequired(flags=["output", "format"])` -- all must appear together, or none
|
|
189
|
+
- `Requires(flag="verbose", depends_on="output")` -- one-way dependency
|
|
190
|
+
- `Implies(flag="verbose", implies="log_output", value=True)` -- auto-set a bool flag when another is provided; explicit contradictions are parse errors
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
@app.command("export", help="Export data", dependencies=[
|
|
194
|
+
strictcli.CoRequired(flags=["output", "format"]),
|
|
195
|
+
strictcli.Requires(flag="verbose", depends_on="output"),
|
|
196
|
+
strictcli.Implies(flag="verbose", implies="log_output", value=True),
|
|
197
|
+
])
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Global flags
|
|
201
|
+
|
|
202
|
+
App-level flags available to all commands, parsed before and after the command token.
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
app = strictcli.App("myapp", version="1.0.0", help="My app", flags=[
|
|
206
|
+
strictcli.Flag(name="verbose", type=bool, help="Verbose output"),
|
|
207
|
+
])
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Passthrough commands
|
|
211
|
+
|
|
212
|
+
Bypass all parsing -- handler gets raw args plus global flag values.
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
@app.command("run", help="Run a script", passthrough=True)
|
|
216
|
+
def run(args, verbose):
|
|
217
|
+
subprocess.run(args)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Repeatable flags
|
|
221
|
+
|
|
222
|
+
Flags that accumulate values across multiple occurrences. Requires explicit `unique=True` or `unique=False`.
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
@strictcli.flag("tag", type=str, help="Add a tag", repeatable=True, unique=True)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Choices
|
|
229
|
+
|
|
230
|
+
Restrict flag values to an allowed set.
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
@strictcli.flag("format", type=str, help="Output format", choices=["json", "csv", "xml"])
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Custom validation
|
|
237
|
+
|
|
238
|
+
Per-flag validation functions.
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
@strictcli.flag("port", type=int, help="Port number", validate=lambda v: 1 <= v <= 65535)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Deprecated commands
|
|
245
|
+
|
|
246
|
+
Register retired commands that print a message to stderr and exit 1.
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
app.deprecate("init", message="Use 'setup' instead")
|
|
250
|
+
db.deprecate("reset", message="Use 'db wipe' instead")
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Deprecated commands appear in help output under a `Deprecated:` section.
|
|
254
|
+
|
|
255
|
+
### Hidden commands and groups
|
|
256
|
+
|
|
257
|
+
Commands and groups can be hidden from help output while remaining functional.
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
@app.command("internal-debug", help="Debug internals", hidden=True)
|
|
261
|
+
def internal_debug(): ...
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### JSON config file support
|
|
265
|
+
|
|
266
|
+
Reads `~/.config/{name}/config.json` (or TOML). Auto-registers `config show/set/path/edit` subcommands.
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
app = strictcli.App("myapp", version="1.0.0", help="My app", config=True)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Precedence: CLI > env > config > default. Config fields can be declared with typed validation:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
app.config_field("serve.port", type=int, help="Server port", default=8080)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Schema dump
|
|
279
|
+
|
|
280
|
+
`--dump-schema` is auto-injected on every app. Writes `.strictcli/schema.json` describing the full CLI structure (commands, flags, args, groups, checks).
|
|
281
|
+
|
|
282
|
+
### Check system
|
|
283
|
+
|
|
284
|
+
First-class check/validation framework with double-entry security. Enabled via `checks_path=` pointing to a TOML file.
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
app = strictcli.App("myapp", version="1.0.0", help="My app", checks_path="checks.toml")
|
|
288
|
+
|
|
289
|
+
@app.check("lint")
|
|
290
|
+
def lint(context):
|
|
291
|
+
return strictcli.CheckResult(status="pass", message="All good")
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Checks are declared in TOML and registered in code -- both must agree. Auto-registers a `check` command with tag DSL filtering (`--tag "release & !slow"`), JSON output, and dependency resolution.
|
|
295
|
+
|
|
296
|
+
### Auto-version
|
|
297
|
+
|
|
298
|
+
`App(name="x", help="...")` without an explicit `version` auto-detects from `importlib.metadata`.
|
|
299
|
+
|
|
300
|
+
### Tool export
|
|
301
|
+
|
|
302
|
+
`app.as_tools()` exports non-hidden, non-interactive commands as `Tool` descriptors for LLM agents.
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
tools = app.as_tools()
|
|
306
|
+
# Each Tool has: name, description, parameters (JSON Schema), execute
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### MCP server
|
|
310
|
+
|
|
311
|
+
`app.serve_mcp()` runs a JSON-RPC 2.0 MCP server on stdin/stdout, exposing commands as tools for AI clients. Triggered via `--mcp` flag.
|
|
312
|
+
|
|
313
|
+
### Help and version
|
|
314
|
+
|
|
315
|
+
- `--help` / `-h` recognized anywhere in argv, at app, group, and command levels
|
|
316
|
+
- `--version` / `-v` prints app version
|
|
317
|
+
- Help is auto-generated with flag types, defaults, env var names, and choices
|
|
318
|
+
|
|
319
|
+
## Testing
|
|
320
|
+
|
|
321
|
+
`app.test(argv)` runs the CLI in-process and returns a `Result`:
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
result = app.test(["hello", "--name", "World", "--loud"])
|
|
325
|
+
|
|
326
|
+
assert result.exit_code == 0
|
|
327
|
+
assert "HELLO, WORLD!" in result.stdout
|
|
328
|
+
assert result.stderr == ""
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## API reference
|
|
332
|
+
|
|
333
|
+
### Core types
|
|
334
|
+
|
|
335
|
+
| Type | Description |
|
|
336
|
+
|------|-------------|
|
|
337
|
+
| `App` | Root CLI application |
|
|
338
|
+
| `Flag` | Flag declaration |
|
|
339
|
+
| `Arg` | Positional argument |
|
|
340
|
+
| `FlagSet` | Reusable flag bundle |
|
|
341
|
+
| `MutexGroup` | Mutually exclusive flags |
|
|
342
|
+
| `CoRequired` | Flags that must appear together |
|
|
343
|
+
| `Requires` | One flag depends on another |
|
|
344
|
+
| `Implies` | Auto-set a bool flag from another |
|
|
345
|
+
| `Result` | Return type of `app.test()` |
|
|
346
|
+
| `Tool` | LLM tool descriptor |
|
|
347
|
+
| `CheckResult` | Check execution result |
|
|
348
|
+
| `CheckContext` | Protocol for check context |
|
|
349
|
+
| `ConfigField` | Typed config file field |
|
|
350
|
+
|
|
351
|
+
### Decorators
|
|
352
|
+
|
|
353
|
+
| Decorator | Description |
|
|
354
|
+
|-----------|-------------|
|
|
355
|
+
| `@app.command(name, help=...)` | Register a command |
|
|
356
|
+
| `@strictcli.flag(name, type=, help=...)` | Declare a flag |
|
|
357
|
+
| `@strictcli.arg(name, help=...)` | Declare a positional argument |
|
|
358
|
+
| `@app.check(name)` | Register a check handler |
|
|
359
|
+
|
|
360
|
+
### App methods
|
|
361
|
+
|
|
362
|
+
| Method | Description |
|
|
363
|
+
|--------|-------------|
|
|
364
|
+
| `app.command(name, help=...)` | Register a command (decorator) |
|
|
365
|
+
| `app.group(name, help=...)` | Create a command group |
|
|
366
|
+
| `app.deprecate(name, message=...)` | Register a deprecated command |
|
|
367
|
+
| `app.run()` | Parse `sys.argv` and execute |
|
|
368
|
+
| `app.test(argv)` | Run in-process, return `Result` |
|
|
369
|
+
| `app.as_tools()` | Export commands as `Tool` descriptors |
|
|
370
|
+
| `app.serve_mcp()` | Run MCP server on stdin/stdout |
|
|
371
|
+
| `app.config_field(name, type=, help=...)` | Declare a typed config field |
|
|
372
|
+
| `app.check(name)` | Register a check handler (decorator) |
|
|
373
|
+
| `app.set_check_context(factory)` | Set the check context factory |
|
|
374
|
+
|
|
375
|
+
## Design principles
|
|
376
|
+
|
|
377
|
+
- **Help is mandatory.** Every command, flag, and argument must have help text. Missing help raises `ValueError` at registration time.
|
|
378
|
+
- **Four types only.** `str`, `bool`, `int`, `float` -- plus compound `list[T]` and `dict[str, T]`. No magic type coercion.
|
|
379
|
+
- **Handler signatures are validated.** Parameter names must match declared flags and args exactly. Extra or missing parameters raise `ValueError`.
|
|
380
|
+
- **Registration-time errors.** Misconfigurations fail loud and early, not at parse time.
|
|
381
|
+
- **Zero dependencies.** Standard library only.
|
|
382
|
+
|
|
383
|
+
## See also
|
|
384
|
+
|
|
385
|
+
- [strictcli monorepo](https://github.com/smm-h/strictcli) -- conformance tests, Go implementation, and project documentation
|
|
386
|
+
- [Go implementation](https://github.com/smm-h/strictcli/tree/main/go) -- same semantics, functional options API
|
|
387
|
+
|
|
388
|
+
## License
|
|
389
|
+
|
|
390
|
+
MIT
|