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.
Files changed (179) hide show
  1. strictcli-0.22.0/.rlsbl/version +1 -0
  2. strictcli-0.22.0/CHANGELOG.md +46 -0
  3. strictcli-0.22.0/PKG-INFO +390 -0
  4. strictcli-0.22.0/README.md +372 -0
  5. {strictcli-0.20.1 → strictcli-0.22.0}/package-lock.json +2 -2
  6. {strictcli-0.20.1 → strictcli-0.22.0}/package.json +1 -1
  7. {strictcli-0.20.1 → strictcli-0.22.0}/pyproject.toml +1 -1
  8. {strictcli-0.20.1 → strictcli-0.22.0}/strictcli/__init__.py +2484 -324
  9. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_arg_default.py +2 -2
  10. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_at_prefix.py +1 -1
  11. strictcli-0.22.0/tests/test_call.py +552 -0
  12. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_check_discovery.py +132 -0
  13. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_check_public_api.py +64 -0
  14. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_check_runner.py +170 -0
  15. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_check_schema.py +57 -0
  16. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_command_tags.py +18 -5
  17. strictcli-0.22.0/tests/test_compound_types.py +1057 -0
  18. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_config.py +2 -2
  19. strictcli-0.22.0/tests/test_config_fields.py +995 -0
  20. strictcli-0.22.0/tests/test_context.py +354 -0
  21. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_deep_nesting.py +2 -2
  22. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_dependencies.py +18 -18
  23. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_dump_schema.py +352 -3
  24. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_e2e.py +5 -5
  25. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_env.py +1 -1
  26. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_flag_sets.py +5 -5
  27. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_global_flags.py +10 -10
  28. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_help.py +2 -2
  29. strictcli-0.22.0/tests/test_invoke.py +631 -0
  30. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_keyword_flags.py +3 -3
  31. strictcli-0.22.0/tests/test_mcp.py +632 -0
  32. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_mutex.py +28 -28
  33. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_parser.py +3 -3
  34. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_passthrough.py +5 -5
  35. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_registration.py +5 -5
  36. strictcli-0.22.0/tests/test_tool_export.py +923 -0
  37. strictcli-0.22.0/tests/test_typed_args.py +849 -0
  38. strictcli-0.22.0/tests/test_visibility.py +439 -0
  39. {strictcli-0.20.1 → strictcli-0.22.0}/uv.lock +1 -1
  40. strictcli-0.20.1/.rlsbl/bases/.claude/settings.json +0 -3
  41. strictcli-0.20.1/.rlsbl/bases/.github/workflows/ci.yml +0 -22
  42. strictcli-0.20.1/.rlsbl/bases/.github/workflows/publish.yml +0 -27
  43. strictcli-0.20.1/.rlsbl/bases/.gitignore +0 -15
  44. strictcli-0.20.1/.rlsbl/bases/.npmignore +0 -34
  45. strictcli-0.20.1/.rlsbl/bases/.rlsbl/hooks/post-release.sh +0 -8
  46. strictcli-0.20.1/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +0 -5
  47. strictcli-0.20.1/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +0 -8
  48. strictcli-0.20.1/.rlsbl/bases/.rlsbl/lint/npm.toml +0 -19
  49. strictcli-0.20.1/.rlsbl/bases/.rlsbl/lint/python.toml +0 -25
  50. strictcli-0.20.1/.rlsbl/bases/CHANGELOG.md +0 -5
  51. strictcli-0.20.1/.rlsbl/bases/CLAUDE.md +0 -20
  52. strictcli-0.20.1/.rlsbl/changes/.validated +0 -1
  53. strictcli-0.20.1/.rlsbl/changes/0.10.0.jsonl +0 -6
  54. strictcli-0.20.1/.rlsbl/changes/0.10.0.md +0 -6
  55. strictcli-0.20.1/.rlsbl/changes/0.11.0.jsonl +0 -7
  56. strictcli-0.20.1/.rlsbl/changes/0.11.0.md +0 -5
  57. strictcli-0.20.1/.rlsbl/changes/0.12.0.jsonl +0 -2
  58. strictcli-0.20.1/.rlsbl/changes/0.12.0.md +0 -5
  59. strictcli-0.20.1/.rlsbl/changes/0.13.0.jsonl +0 -3
  60. strictcli-0.20.1/.rlsbl/changes/0.13.0.md +0 -5
  61. strictcli-0.20.1/.rlsbl/changes/0.14.0.jsonl +0 -3
  62. strictcli-0.20.1/.rlsbl/changes/0.14.0.md +0 -5
  63. strictcli-0.20.1/.rlsbl/changes/0.15.0.jsonl +0 -6
  64. strictcli-0.20.1/.rlsbl/changes/0.15.0.md +0 -18
  65. strictcli-0.20.1/.rlsbl/changes/0.15.1.jsonl +0 -6
  66. strictcli-0.20.1/.rlsbl/changes/0.15.1.md +0 -18
  67. strictcli-0.20.1/.rlsbl/changes/0.16.0.jsonl +0 -9
  68. strictcli-0.20.1/.rlsbl/changes/0.16.0.md +0 -27
  69. strictcli-0.20.1/.rlsbl/changes/0.16.1.jsonl +0 -3
  70. strictcli-0.20.1/.rlsbl/changes/0.16.1.md +0 -8
  71. strictcli-0.20.1/.rlsbl/changes/0.17.0.jsonl +0 -3
  72. strictcli-0.20.1/.rlsbl/changes/0.17.0.md +0 -11
  73. strictcli-0.20.1/.rlsbl/changes/0.18.0.jsonl +0 -4
  74. strictcli-0.20.1/.rlsbl/changes/0.18.0.md +0 -14
  75. strictcli-0.20.1/.rlsbl/changes/0.19.0.jsonl +0 -2
  76. strictcli-0.20.1/.rlsbl/changes/0.19.0.md +0 -5
  77. strictcli-0.20.1/.rlsbl/changes/0.20.0.jsonl +0 -4
  78. strictcli-0.20.1/.rlsbl/changes/0.20.0.md +0 -19
  79. strictcli-0.20.1/.rlsbl/changes/0.20.1.jsonl +0 -3
  80. strictcli-0.20.1/.rlsbl/changes/0.20.1.md +0 -14
  81. strictcli-0.20.1/.rlsbl/changes/0.4.0.jsonl +0 -29
  82. strictcli-0.20.1/.rlsbl/changes/0.4.0.md +0 -16
  83. strictcli-0.20.1/.rlsbl/changes/0.4.1.jsonl +0 -4
  84. strictcli-0.20.1/.rlsbl/changes/0.4.1.md +0 -5
  85. strictcli-0.20.1/.rlsbl/changes/0.5.0.jsonl +0 -3
  86. strictcli-0.20.1/.rlsbl/changes/0.5.0.md +0 -5
  87. strictcli-0.20.1/.rlsbl/changes/0.6.0.jsonl +0 -3
  88. strictcli-0.20.1/.rlsbl/changes/0.6.0.md +0 -5
  89. strictcli-0.20.1/.rlsbl/changes/0.6.1.jsonl +0 -6
  90. strictcli-0.20.1/.rlsbl/changes/0.6.1.md +0 -5
  91. strictcli-0.20.1/.rlsbl/changes/0.7.0.jsonl +0 -14
  92. strictcli-0.20.1/.rlsbl/changes/0.7.0.md +0 -10
  93. strictcli-0.20.1/.rlsbl/changes/0.7.1.jsonl +0 -12
  94. strictcli-0.20.1/.rlsbl/changes/0.7.1.md +0 -5
  95. strictcli-0.20.1/.rlsbl/changes/0.8.0.jsonl +0 -7
  96. strictcli-0.20.1/.rlsbl/changes/0.8.0.md +0 -5
  97. strictcli-0.20.1/.rlsbl/changes/0.8.1.jsonl +0 -2
  98. strictcli-0.20.1/.rlsbl/changes/0.8.1.md +0 -3
  99. strictcli-0.20.1/.rlsbl/changes/0.8.2.jsonl +0 -1
  100. strictcli-0.20.1/.rlsbl/changes/0.8.2.md +0 -3
  101. strictcli-0.20.1/.rlsbl/changes/0.8.3.jsonl +0 -1
  102. strictcli-0.20.1/.rlsbl/changes/0.8.3.md +0 -3
  103. strictcli-0.20.1/.rlsbl/changes/0.8.4.jsonl +0 -3
  104. strictcli-0.20.1/.rlsbl/changes/0.8.4.md +0 -5
  105. strictcli-0.20.1/.rlsbl/changes/0.8.5.jsonl +0 -7
  106. strictcli-0.20.1/.rlsbl/changes/0.8.5.md +0 -9
  107. strictcli-0.20.1/.rlsbl/changes/0.8.6.jsonl +0 -6
  108. strictcli-0.20.1/.rlsbl/changes/0.8.6.md +0 -5
  109. strictcli-0.20.1/.rlsbl/changes/0.8.7.jsonl +0 -5
  110. strictcli-0.20.1/.rlsbl/changes/0.8.7.md +0 -9
  111. strictcli-0.20.1/.rlsbl/changes/0.9.0.jsonl +0 -5
  112. strictcli-0.20.1/.rlsbl/changes/0.9.0.md +0 -5
  113. strictcli-0.20.1/.rlsbl/changes/0.9.1.jsonl +0 -9
  114. strictcli-0.20.1/.rlsbl/changes/0.9.1.md +0 -9
  115. strictcli-0.20.1/.rlsbl/changes/unreleased.jsonl +0 -0
  116. strictcli-0.20.1/.rlsbl/hooks/post-release.sh +0 -8
  117. strictcli-0.20.1/.rlsbl/hooks/pre-checks.sh +0 -10
  118. strictcli-0.20.1/.rlsbl/hooks/pre-release.sh +0 -13
  119. strictcli-0.20.1/.rlsbl/lint/npm.toml +0 -19
  120. strictcli-0.20.1/.rlsbl/lint/python.toml +0 -25
  121. strictcli-0.20.1/.rlsbl/releases/unreleased.toml +0 -0
  122. strictcli-0.20.1/.rlsbl/releases/v0.10.0.toml +0 -3
  123. strictcli-0.20.1/.rlsbl/releases/v0.11.0.toml +0 -3
  124. strictcli-0.20.1/.rlsbl/releases/v0.12.0.toml +0 -4
  125. strictcli-0.20.1/.rlsbl/releases/v0.13.0.toml +0 -4
  126. strictcli-0.20.1/.rlsbl/releases/v0.15.0.toml +0 -8
  127. strictcli-0.20.1/.rlsbl/releases/v0.15.1.toml +0 -8
  128. strictcli-0.20.1/.rlsbl/releases/v0.16.0.toml +0 -8
  129. strictcli-0.20.1/.rlsbl/releases/v0.16.1.toml +0 -8
  130. strictcli-0.20.1/.rlsbl/releases/v0.17.0.toml +0 -8
  131. strictcli-0.20.1/.rlsbl/releases/v0.18.0.toml +0 -8
  132. strictcli-0.20.1/.rlsbl/releases/v0.19.0.toml +0 -0
  133. strictcli-0.20.1/.rlsbl/releases/v0.20.0.toml +0 -8
  134. strictcli-0.20.1/.rlsbl/releases/v0.20.1.toml +0 -8
  135. strictcli-0.20.1/.rlsbl/releases/v0.8.5.toml +0 -3
  136. strictcli-0.20.1/.rlsbl/releases/v0.8.6.toml +0 -3
  137. strictcli-0.20.1/.rlsbl/releases/v0.8.7.toml +0 -3
  138. strictcli-0.20.1/.rlsbl/releases/v0.9.0.toml +0 -3
  139. strictcli-0.20.1/.rlsbl/releases/v0.9.1.toml +0 -3
  140. strictcli-0.20.1/.rlsbl/version +0 -1
  141. strictcli-0.20.1/CHANGELOG.md +0 -301
  142. strictcli-0.20.1/LICENSE +0 -21
  143. strictcli-0.20.1/PKG-INFO +0 -492
  144. strictcli-0.20.1/README.md +0 -474
  145. {strictcli-0.20.1 → strictcli-0.22.0}/.claude/settings.json +0 -0
  146. {strictcli-0.20.1 → strictcli-0.22.0}/.github/workflows/ci.yml +0 -0
  147. {strictcli-0.20.1 → strictcli-0.22.0}/.github/workflows/publish.yml +0 -0
  148. {strictcli-0.20.1 → strictcli-0.22.0}/.gitignore +0 -0
  149. {strictcli-0.20.1 → strictcli-0.22.0}/.npmignore +0 -0
  150. {strictcli-0.20.1 → strictcli-0.22.0}/.rlsbl/config.json +0 -0
  151. {strictcli-0.20.1 → strictcli-0.22.0}/.rlsbl/hashes.json +0 -0
  152. {strictcli-0.20.1 → strictcli-0.22.0}/.strictcli/schema.json +0 -0
  153. {strictcli-0.20.1 → strictcli-0.22.0}/CLAUDE.md +0 -0
  154. {strictcli-0.20.1/.rlsbl/bases → strictcli-0.22.0}/LICENSE +0 -0
  155. {strictcli-0.20.1 → strictcli-0.22.0}/index.js +0 -0
  156. {strictcli-0.20.1 → strictcli-0.22.0}/postinstall.js +0 -0
  157. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_auto_version.py +0 -0
  158. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_check_command.py +0 -0
  159. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_check_types.py +0 -0
  160. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_choices.py +0 -0
  161. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_command_help_suggestion.py +0 -0
  162. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_config_file_path.py +0 -0
  163. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_config_set_bugs.py +0 -0
  164. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_deprecated.py +0 -0
  165. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_exit_codes.py +0 -0
  166. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_float_type.py +0 -0
  167. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_int_type.py +0 -0
  168. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_nesting.py +0 -0
  169. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_repeatable.py +0 -0
  170. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_tagdsl.py +0 -0
  171. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_toml_loading.py +0 -0
  172. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_unique.py +0 -0
  173. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_utilities.py +0 -0
  174. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_validate.py +0 -0
  175. {strictcli-0.20.1 → strictcli-0.22.0}/tests/test_variadic.py +0 -0
  176. {strictcli-0.20.1 → strictcli-0.22.0}/todo/.defer/deferred.md +0 -0
  177. {strictcli-0.20.1/todo → strictcli-0.22.0/todo/.done}/keyword-collision-in-flag-param-name.md +0 -0
  178. {strictcli-0.20.1 → strictcli-0.22.0}/todo/.done/original-idea.md +0 -0
  179. {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