strictcli 0.1.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 (39) hide show
  1. strictcli-0.1.0/.claude/settings.json +3 -0
  2. strictcli-0.1.0/.github/workflows/ci.yml +21 -0
  3. strictcli-0.1.0/.github/workflows/publish.yml +30 -0
  4. strictcli-0.1.0/.gitignore +15 -0
  5. strictcli-0.1.0/.rlsbl/bases/.claude/settings.json +3 -0
  6. strictcli-0.1.0/.rlsbl/bases/.github/workflows/ci.yml +21 -0
  7. strictcli-0.1.0/.rlsbl/bases/.github/workflows/publish.yml +30 -0
  8. strictcli-0.1.0/.rlsbl/bases/.gitignore +15 -0
  9. strictcli-0.1.0/.rlsbl/bases/.rlsbl/hooks/post-release.sh +8 -0
  10. strictcli-0.1.0/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +31 -0
  11. strictcli-0.1.0/.rlsbl/bases/CHANGELOG.md +5 -0
  12. strictcli-0.1.0/.rlsbl/bases/CLAUDE.md +20 -0
  13. strictcli-0.1.0/.rlsbl/bases/LICENSE +21 -0
  14. strictcli-0.1.0/.rlsbl/config.json +6 -0
  15. strictcli-0.1.0/.rlsbl/hashes.json +11 -0
  16. strictcli-0.1.0/.rlsbl/hooks/post-release.sh +8 -0
  17. strictcli-0.1.0/.rlsbl/hooks/pre-release.sh +31 -0
  18. strictcli-0.1.0/.rlsbl/version +1 -0
  19. strictcli-0.1.0/CHANGELOG.md +17 -0
  20. strictcli-0.1.0/CLAUDE.md +20 -0
  21. strictcli-0.1.0/LICENSE +21 -0
  22. strictcli-0.1.0/PKG-INFO +318 -0
  23. strictcli-0.1.0/README.md +300 -0
  24. strictcli-0.1.0/index.js +5 -0
  25. strictcli-0.1.0/package-lock.json +14 -0
  26. strictcli-0.1.0/package.json +25 -0
  27. strictcli-0.1.0/postinstall.js +17 -0
  28. strictcli-0.1.0/pyproject.toml +30 -0
  29. strictcli-0.1.0/strictcli/__init__.py +738 -0
  30. strictcli-0.1.0/tests/test_e2e.py +216 -0
  31. strictcli-0.1.0/tests/test_env.py +95 -0
  32. strictcli-0.1.0/tests/test_help.py +129 -0
  33. strictcli-0.1.0/tests/test_nesting.py +65 -0
  34. strictcli-0.1.0/tests/test_parser.py +178 -0
  35. strictcli-0.1.0/tests/test_registration.py +156 -0
  36. strictcli-0.1.0/tests/test_tags.py +103 -0
  37. strictcli-0.1.0/todo/.done/original-idea.md +36 -0
  38. strictcli-0.1.0/todo/deferred.md +15 -0
  39. strictcli-0.1.0/uv.lock +79 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ "hooks": {}
3
+ }
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ # requires-python: >= 3.11
15
+ python-version: ["3.12", "3.13", "3.14"]
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+ - uses: astral-sh/setup-uv@v7
19
+ - run: uv python install ${{ matrix.python-version }}
20
+ - run: uv sync
21
+ - run: uv run python -c "import strictcli"
@@ -0,0 +1,30 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+ id-token: write
10
+
11
+ jobs:
12
+ pypi:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+ - uses: astral-sh/setup-uv@v7
17
+ - run: uv build
18
+ - uses: pypa/gh-action-pypi-publish@release/v1
19
+
20
+ npm:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v6
24
+ - uses: actions/setup-node@v6
25
+ with:
26
+ node-version: 24
27
+ registry-url: https://registry.npmjs.org
28
+ - run: npm publish --provenance --access public
29
+ env:
30
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,15 @@
1
+ node_modules/
2
+ __pycache__/
3
+ *.pyc
4
+ *.log
5
+ .DS_Store
6
+ coverage/
7
+ dist/
8
+ *.egg-info/
9
+ .rlsbl-notes-*.tmp
10
+ .rlsbl/lock
11
+ .credentials.json
12
+ .*-cache.json
13
+ .env
14
+ .env.local
15
+ *.local-only
@@ -0,0 +1,3 @@
1
+ {
2
+ "hooks": {}
3
+ }
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ # requires-python: >= 3.11
15
+ python-version: ["3.12", "3.13", "3.14"]
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+ - uses: astral-sh/setup-uv@v7
19
+ - run: uv python install ${{ matrix.python-version }}
20
+ - run: uv sync
21
+ - run: uv run python -c "import strictcli"
@@ -0,0 +1,30 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+ id-token: write
10
+
11
+ jobs:
12
+ pypi:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+ - uses: astral-sh/setup-uv@v7
17
+ - run: uv build
18
+ - uses: pypa/gh-action-pypi-publish@release/v1
19
+
20
+ npm:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v6
24
+ - uses: actions/setup-node@v6
25
+ with:
26
+ node-version: 24
27
+ registry-url: {{registryUrl}}
28
+ - run: npm publish --provenance --access public
29
+ env:
30
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,15 @@
1
+ node_modules/
2
+ __pycache__/
3
+ *.pyc
4
+ *.log
5
+ .DS_Store
6
+ coverage/
7
+ dist/
8
+ *.egg-info/
9
+ .rlsbl-notes-*.tmp
10
+ .rlsbl/lock
11
+ .credentials.json
12
+ .*-cache.json
13
+ .env
14
+ .env.local
15
+ *.local-only
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ # Post-release hook. Runs after a successful release (non-fatal).
3
+ # Environment: RLSBL_VERSION is set to the released version.
4
+ # Customize this for your project (e.g., local install, deploy, notify).
5
+
6
+ set -euo pipefail
7
+
8
+ echo "Post-release: v$RLSBL_VERSION"
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ # Pre-release validation hook.
3
+ # Runs before rlsbl creates a release. Exit non-zero to abort.
4
+ # Detects project type and runs appropriate checks automatically.
5
+
6
+ set -euo pipefail
7
+
8
+ echo "Running pre-release checks..."
9
+
10
+ if [ -f go.mod ]; then
11
+ echo " Go: vet + build + test"
12
+ go vet ./...
13
+ go build ./...
14
+ go test ./... -race -short -count=1
15
+ fi
16
+
17
+ if [ -f pyproject.toml ]; then
18
+ echo " Python: pytest"
19
+ if command -v uv &>/dev/null; then
20
+ uv run pytest
21
+ elif command -v pytest &>/dev/null; then
22
+ pytest
23
+ fi
24
+ fi
25
+
26
+ if [ -f package.json ] && node -e "process.exit(require('./package.json').scripts?.test ? 0 : 1)" 2>/dev/null; then
27
+ echo " npm: test"
28
+ npm test
29
+ fi
30
+
31
+ echo "Pre-release checks passed."
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial release
@@ -0,0 +1,20 @@
1
+ # cli
2
+
3
+ ## Release workflow
4
+
5
+ This project uses [rlsbl](https://github.com/smm-h/rlsbl) for release orchestration.
6
+
7
+ - Update CHANGELOG.md with a `## X.Y.Z` entry describing changes
8
+ - Run `rlsbl release [patch|minor|major]` to bump version and create a GitHub Release
9
+ - CI handles publishing automatically via the publish workflow
10
+ - Never publish manually — always use `rlsbl release`
11
+ - Configure Trusted Publishing on pypi.org for automated PyPI releases
12
+ - Use `rlsbl release --dry-run` to preview a release without making changes
13
+
14
+ ## Conventions
15
+
16
+ - No tokens or secrets in command-line arguments (use env vars or config files)
17
+ - All file writes to shared state should be atomic (write to tmp, then rename)
18
+ - External calls (APIs, CLI tools) must have timeouts and graceful fallbacks
19
+ - Use `npm link` (npm) or `uv pip install -e .` (Python) for local development
20
+ - CI runs smoke tests on every push; manual testing for UI/UX changes
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 smm-h
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ {
2
+ "targets": [
3
+ "pypi",
4
+ "npm"
5
+ ]
6
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ ".github/workflows/ci.yml": "d71e558f1bfd93b43a783054577698da34c3e45455c6039686e06a7449b2bf1b",
3
+ ".github/workflows/publish.yml": "5f4ac1bf0bdd4c70041e3d2502efaae28d29eca7575e8ff12fdaad0d3af5b17a",
4
+ "CHANGELOG.md": "7a6ec46141007c18090a0ff325e2dd68cd51588c4e0b5af7b580365e5eaaca85",
5
+ ".gitignore": "c89d76c6c43e7f0897b3472a15b7c0103e0be5b3a04c2b44e585fa48bdcda2bb",
6
+ "LICENSE": "e958892abc1dd5d991fca6d528e695d335f25773d373d2e0be5f4a9507d8f8d5",
7
+ "CLAUDE.md": "7294b791570185dc31020dadb092b5b79681ba8ee932fe9a3974e27eaae56c4f",
8
+ ".rlsbl/hooks/pre-release.sh": "9bae4134d0a4c302287766f9a4a2923b6c5706d2cc7078f553fec843b8cd5a06",
9
+ ".rlsbl/hooks/post-release.sh": "b455f8511e0b2655509ecf5dcb0ab7da5bb7c961f47910ff8e00cab5bd51f833",
10
+ ".claude/settings.json": "78922a784ee78e9e50587e93628cd3b9d4dfbe49087adc4514e6781cea38cbb9"
11
+ }
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ # Post-release hook. Runs after a successful release (non-fatal).
3
+ # Environment: RLSBL_VERSION is set to the released version.
4
+ # Customize this for your project (e.g., local install, deploy, notify).
5
+
6
+ set -euo pipefail
7
+
8
+ echo "Post-release: v$RLSBL_VERSION"
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ # Pre-release validation hook.
3
+ # Runs before rlsbl creates a release. Exit non-zero to abort.
4
+ # Detects project type and runs appropriate checks automatically.
5
+
6
+ set -euo pipefail
7
+
8
+ echo "Running pre-release checks..."
9
+
10
+ if [ -f go.mod ]; then
11
+ echo " Go: vet + build + test"
12
+ go vet ./...
13
+ go build ./...
14
+ go test ./... -race -short -count=1
15
+ fi
16
+
17
+ if [ -f pyproject.toml ]; then
18
+ echo " Python: pytest"
19
+ if command -v uv &>/dev/null; then
20
+ uv run pytest
21
+ elif command -v pytest &>/dev/null; then
22
+ pytest
23
+ fi
24
+ fi
25
+
26
+ if [ -f package.json ] && node -e "process.exit(require('./package.json').scripts?.test ? 0 : 1)" 2>/dev/null; then
27
+ echo " npm: test"
28
+ npm test
29
+ fi
30
+
31
+ echo "Pre-release checks passed."
@@ -0,0 +1 @@
1
+ 0.21.2
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Decorator-based command registration with `@app.command` and `@strictcli.flag`/`@strictcli.arg`
6
+ - npm thin wrapper: `npm install strictcli` installs the Python package via uv/pip
7
+ - Two-level command nesting via `app.group()` with `@group.command`
8
+ - First-class environment variable support with prefix enforcement and `prefixed=False` opt-out
9
+ - Tags: reusable flag bundles applied to commands
10
+ - Plain-text help generation at app, group, and command levels
11
+ - Automatic `--help`/`-h` and `--version`/`-v` handling
12
+ - `--no-X` negation for boolean flags with opt-out
13
+ - Short flag aliases (`-v`, `-t`, etc.)
14
+ - Mandatory help text on all elements (commands, groups, flags, args, env vars)
15
+ - Handler signature validation at registration time
16
+ - `app.run()` full lifecycle and `app.test(argv)` for testing
17
+ - `--` separator to stop flag parsing
@@ -0,0 +1,20 @@
1
+ # strictcli
2
+
3
+ ## Release workflow
4
+
5
+ This project uses [rlsbl](https://github.com/smm-h/rlsbl) for release orchestration.
6
+
7
+ - Update CHANGELOG.md with a `## X.Y.Z` entry describing changes
8
+ - Run `rlsbl release [patch|minor|major]` to bump version and create a GitHub Release
9
+ - CI handles publishing automatically via the publish workflow
10
+ - Never publish manually — always use `rlsbl release`
11
+ - Configure Trusted Publishing on pypi.org for automated PyPI releases
12
+ - Use `rlsbl release --dry-run` to preview a release without making changes
13
+
14
+ ## Conventions
15
+
16
+ - No tokens or secrets in command-line arguments (use env vars or config files)
17
+ - All file writes to shared state should be atomic (write to tmp, then rename)
18
+ - External calls (APIs, CLI tools) must have timeouts and graceful fallbacks
19
+ - Use `npm link` (npm) or `uv pip install -e .` (Python) for local development
20
+ - CI runs smoke tests on every push; manual testing for UI/UX changes
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 smm-h
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,318 @@
1
+ Metadata-Version: 2.4
2
+ Name: strictcli
3
+ Version: 0.1.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. Types are `str` and `bool` only; there is no magic type inference. Environment variables are first-class, with prefix enforcement to keep your config namespace clean.
24
+
25
+ ## Installation
26
+
27
+ ```
28
+ uv add strictcli
29
+ ```
30
+
31
+ Or:
32
+
33
+ ```
34
+ pip install strictcli
35
+ ```
36
+
37
+ Requires Python 3.11+.
38
+
39
+ ## Quickstart
40
+
41
+ ```python
42
+ # greet.py
43
+ import strictcli
44
+
45
+ app = strictcli.App(name="greet", version="0.1.0", help="a friendly greeter")
46
+
47
+ @app.command("hello", help="say hello", args=[strictcli.Arg(name="name", help="who to greet")])
48
+ @strictcli.flag("loud", short="l", type=bool, help="shout the greeting")
49
+ def hello(name, loud):
50
+ msg = f"Hello, {name}!"
51
+ if loud:
52
+ msg = msg.upper()
53
+ print(msg)
54
+
55
+ app.run()
56
+ ```
57
+
58
+ ```
59
+ $ python greet.py hello World
60
+ Hello, World!
61
+
62
+ $ python greet.py hello --loud World
63
+ HELLO, WORLD!
64
+
65
+ $ python greet.py hello --help
66
+ greet hello -- say hello
67
+
68
+ Arguments:
69
+ name who to greet
70
+
71
+ Flags:
72
+ --loud, --no-loud, -l shout the greeting [default: false]
73
+ ```
74
+
75
+ ## Commands and Groups
76
+
77
+ Register top-level commands with `@app.command`:
78
+
79
+ ```python
80
+ app = strictcli.App(name="myapp", version="1.0.0", help="manage deployments")
81
+
82
+ @app.command("status", help="show current status")
83
+ def status():
84
+ print("all systems go")
85
+ ```
86
+
87
+ Create groups for two-level nesting with `app.group`:
88
+
89
+ ```python
90
+ db = app.group("db", help="manage databases")
91
+
92
+ @db.command("migrate", help="run database migrations")
93
+ @strictcli.flag("dry-run", type=bool, help="preview without applying")
94
+ def migrate(dry_run):
95
+ if dry_run:
96
+ print("would run migrations")
97
+ else:
98
+ print("running migrations")
99
+
100
+ @db.command("seed", help="populate with sample data")
101
+ @strictcli.flag("count", type=str, help="number of records", default="100")
102
+ def seed(count):
103
+ print(f"seeding {count} records")
104
+ ```
105
+
106
+ ```
107
+ $ myapp db migrate --dry-run
108
+ would run migrations
109
+
110
+ $ myapp db seed --count 500
111
+ seeding 500 records
112
+ ```
113
+
114
+ ## Flags
115
+
116
+ Declare flags with the `@strictcli.flag` decorator. Every flag must have `help` text.
117
+
118
+ ### String flags
119
+
120
+ ```python
121
+ @app.command("build", help="build the project")
122
+ @strictcli.flag("output", short="o", type=str, help="output directory", default="dist")
123
+ def build(output):
124
+ print(f"building to {output}")
125
+ ```
126
+
127
+ String flags accept values via `--output dist` or `--output=dist`. A string flag without a `default` is required.
128
+
129
+ ### Bool flags
130
+
131
+ ```python
132
+ @app.command("deploy", help="deploy the app")
133
+ @strictcli.flag("force", short="f", type=bool, help="skip confirmation")
134
+ def deploy(force):
135
+ if force:
136
+ print("deploying without confirmation")
137
+ ```
138
+
139
+ Bool flags default to `False`. Pass `--force` to set `True`, or `--no-force` to explicitly set `False`. The `--no-` negation form is available by default for all bool flags; disable it with `negatable=False`.
140
+
141
+ ### Short aliases
142
+
143
+ Any flag can have a one-character short alias:
144
+
145
+ ```python
146
+ @strictcli.flag("verbose", short="v", type=bool, help="verbose output")
147
+ ```
148
+
149
+ This allows both `--verbose` and `-v`.
150
+
151
+ ### Required vs optional
152
+
153
+ - `str` flags with no `default` are required -- the parser errors if missing.
154
+ - `str` flags with a `default` are optional.
155
+ - `bool` flags always default to `False`.
156
+
157
+ ## Arguments
158
+
159
+ Positional arguments are declared with `strictcli.Arg`. There are two equivalent forms.
160
+
161
+ Using the `args=` parameter:
162
+
163
+ ```python
164
+ @app.command("copy", help="copy files", args=[
165
+ strictcli.Arg(name="src", help="source path"),
166
+ strictcli.Arg(name="dst", help="destination path"),
167
+ ])
168
+ def copy(src, dst):
169
+ print(f"copying {src} to {dst}")
170
+ ```
171
+
172
+ Using the `@strictcli.arg` decorator:
173
+
174
+ ```python
175
+ @app.command("show", help="show a file")
176
+ @strictcli.arg("path", help="file to show")
177
+ def show(path):
178
+ print(f"showing {path}")
179
+ ```
180
+
181
+ Arguments are matched in order. Use `required=False` for optional arguments. The `--` separator stops flag parsing, so everything after it becomes positional:
182
+
183
+ ```
184
+ $ myapp cmd -- --not-a-flag
185
+ ```
186
+
187
+ ## Environment Variables
188
+
189
+ Flags can be backed by environment variables with the `env` parameter:
190
+
191
+ ```python
192
+ app = strictcli.App(name="myapp", version="1.0.0", help="my app", env_prefix="MYAPP")
193
+
194
+ @app.command("deploy", help="deploy the app")
195
+ @strictcli.flag("region", type=str, help="cloud region", env="MYAPP_REGION", default="us-east-1")
196
+ def deploy(region):
197
+ print(f"deploying to {region}")
198
+ ```
199
+
200
+ ### Prefix enforcement
201
+
202
+ When `env_prefix` is set on the App, all env vars must start with that prefix. This is validated at registration time:
203
+
204
+ ```python
205
+ # This raises ValueError: env var 'REGION' must start with 'MYAPP_'
206
+ @strictcli.flag("region", type=str, help="region", env="REGION", default="x")
207
+ ```
208
+
209
+ ### External env vars
210
+
211
+ Use `prefixed=False` for env vars outside your app's namespace:
212
+
213
+ ```python
214
+ @strictcli.flag("token", type=str, help="auth token", env="GITHUB_TOKEN", prefixed=False, default="")
215
+ ```
216
+
217
+ ### Priority
218
+
219
+ Values resolve in this order: CLI argument > environment variable > default. If none of the three provides a value, the parser errors.
220
+
221
+ ### Bool env vars
222
+
223
+ Bool flags from env vars accept `1`, `true`, `yes` (case-insensitive) for `True` and `0`, `false`, `no` for `False`. Any other value is an error.
224
+
225
+ ## Tags
226
+
227
+ Tags are reusable bundles of flags that can be applied to multiple commands:
228
+
229
+ ```python
230
+ auth_tag = strictcli.Tag(
231
+ name="auth",
232
+ flags=[
233
+ strictcli.Flag(name="token", type=str, help="auth token", env="MYAPP_TOKEN", default=""),
234
+ strictcli.Flag(name="insecure", type=bool, help="skip TLS verification"),
235
+ ],
236
+ )
237
+
238
+ @app.command("deploy", help="deploy the app", tags=[auth_tag])
239
+ def deploy(token, insecure):
240
+ print(f"token={'set' if token else 'unset'}, insecure={insecure}")
241
+
242
+ @app.command("status", help="check status", tags=[auth_tag])
243
+ def status(token, insecure):
244
+ print(f"checking status")
245
+ ```
246
+
247
+ Both commands now have `--token` and `--insecure` flags. Tag flags appear in help output and are parsed like any other flag.
248
+
249
+ ## Help Output
250
+
251
+ Help is auto-generated at three levels. Pass `--help` or `-h` at any level, or invoke the app with no arguments.
252
+
253
+ **App level** (`myapp --help`):
254
+
255
+ ```
256
+ myapp v1.0.0 -- manage deployments
257
+
258
+ Commands:
259
+ deploy deploy the application
260
+
261
+ Groups:
262
+ db manage databases
263
+
264
+ Use 'myapp <command> --help' for more information.
265
+ ```
266
+
267
+ **Group level** (`myapp db --help`):
268
+
269
+ ```
270
+ myapp db -- manage databases
271
+
272
+ Commands:
273
+ migrate run database migrations
274
+ seed populate with sample data
275
+
276
+ Use 'myapp db <command> --help' for more information.
277
+ ```
278
+
279
+ **Command level** (`myapp deploy --help`):
280
+
281
+ ```
282
+ myapp deploy -- deploy the application
283
+
284
+ Arguments:
285
+ target deployment target
286
+
287
+ Flags:
288
+ --region, -r <str> cloud region [env: MYAPP_REGION] [default: us-east-1]
289
+ --force, --no-force, -f skip confirmation prompt [default: false]
290
+ ```
291
+
292
+ Version: `--version` or `-v` prints `myapp 1.0.0`.
293
+
294
+ ## Testing
295
+
296
+ `app.test(argv)` runs the CLI in-process and returns a `Result` with captured output:
297
+
298
+ ```python
299
+ result = app.test(["deploy", "--force", "production"])
300
+
301
+ assert result.exit_code == 0
302
+ assert "deploying" in result.stdout
303
+ assert result.stderr == ""
304
+ ```
305
+
306
+ The `Result` dataclass has three fields: `stdout`, `stderr`, and `exit_code`.
307
+
308
+ ## Strict by Design
309
+
310
+ strictcli is opinionated about strictness:
311
+
312
+ - **Help is mandatory.** Every command, flag, and argument must have help text. Missing help raises `ValueError` at registration time, not at runtime.
313
+ - **Only str and bool.** No int, float, or list types. Parse them yourself in the handler -- it is one line of code and makes the conversion visible.
314
+ - **Handler signatures are validated.** Every declared flag and arg must have a matching parameter in the handler function, and vice versa. Extra or missing parameters raise `ValueError`.
315
+ - **Env var prefixes are enforced.** If you set `env_prefix="MYAPP"`, every env-backed flag must use that prefix (or explicitly opt out with `prefixed=False`).
316
+ - **No hidden defaults.** Required flags fail loudly. Bool flags default to `False`. Everything else must be declared.
317
+
318
+ If you want automatic type coercion, subcommand hierarchies deeper than two levels, or rich terminal formatting, consider [argparse](https://docs.python.org/3/library/argparse.html), [click](https://click.palletsprojects.com/), or [typer](https://typer.tiangolo.com/).