bitranox-template-py-cli 1.5.2__tar.gz → 1.5.4__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 (109) hide show
  1. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/.github/workflows/default_cicd_public.yml +84 -16
  2. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/CHANGELOG.md +16 -0
  3. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/Makefile +1 -1
  4. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/PKG-INFO +26 -16
  5. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/README.md +11 -1
  6. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/pyproject.toml +28 -23
  7. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/__init__conf__.py +1 -1
  8. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/commands/config.py +12 -11
  9. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/commands/email/_common.py +8 -7
  10. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/commands/email/send_email.py +7 -8
  11. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/commands/email/send_notification.py +5 -6
  12. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/commands/logging.py +2 -1
  13. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/root.py +6 -5
  14. bitranox_template_py_cli-1.5.4/src/bitranox_template_py_cli/adapters/cli/typed_click.py +37 -0
  15. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/email/transport.py +6 -4
  16. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_module_entry.py +2 -2
  17. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/.devcontainer/devcontainer.json +0 -0
  18. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/.devcontainer/settings.json +0 -0
  19. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/.env.example +0 -0
  20. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/.github/actions/extract-metadata/action.yml +0 -0
  21. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/.github/dependabot.yml +0 -0
  22. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/.github/workflows/codeql.yml +0 -0
  23. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/.github/workflows/default_release_public.yml +0 -0
  24. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/.gitignore +0 -0
  25. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/.qlty/qlty.toml +0 -0
  26. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/.snyk +0 -0
  27. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/CONFIG.md +0 -0
  28. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/CONTRIBUTING.md +0 -0
  29. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/DEVELOPMENT.md +0 -0
  30. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/INSTALL.md +0 -0
  31. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/LICENSE +0 -0
  32. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/SECURITY.md +0 -0
  33. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/codecov.yml +0 -0
  34. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/docs/adr/0001-memory-adapters-in-src.md +0 -0
  35. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/docs/systemdesign/module_reference.md +0 -0
  36. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/notebooks/Quickstart.ipynb +0 -0
  37. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/rename.sh +0 -0
  38. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/rename_dry.sh +0 -0
  39. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/reset_git_history.sh +0 -0
  40. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/__init__.py +0 -0
  41. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/__main__.py +0 -0
  42. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/__init__.py +0 -0
  43. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/__init__.py +0 -0
  44. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/commands/__init__.py +0 -0
  45. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/commands/email/__init__.py +0 -0
  46. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/commands/info.py +0 -0
  47. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/constants.py +0 -0
  48. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/context.py +0 -0
  49. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/exit_codes.py +0 -0
  50. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/main.py +0 -0
  51. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/cli/py.typed +0 -0
  52. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/config/__init__.py +0 -0
  53. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/config/defaultconfig.d/40-layered-config.toml +0 -0
  54. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/config/defaultconfig.d/50-mail.toml +0 -0
  55. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/config/defaultconfig.d/90-logging.toml +0 -0
  56. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/config/defaultconfig.toml +0 -0
  57. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/config/deploy.py +0 -0
  58. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/config/display.py +0 -0
  59. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/config/loader.py +0 -0
  60. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/config/overrides.py +0 -0
  61. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/config/permissions.py +0 -0
  62. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/config/py.typed +0 -0
  63. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/email/__init__.py +0 -0
  64. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/email/config.py +0 -0
  65. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/email/py.typed +0 -0
  66. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/email/sender.py +0 -0
  67. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/email/validation.py +0 -0
  68. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/logging/__init__.py +0 -0
  69. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/logging/py.typed +0 -0
  70. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/logging/setup.py +0 -0
  71. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/memory/__init__.py +0 -0
  72. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/memory/config.py +0 -0
  73. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/memory/email.py +0 -0
  74. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/memory/logging.py +0 -0
  75. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/adapters/py.typed +0 -0
  76. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/application/__init__.py +0 -0
  77. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/application/ports.py +0 -0
  78. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/application/py.typed +0 -0
  79. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/composition/__init__.py +0 -0
  80. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/composition/py.typed +0 -0
  81. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/domain/__init__.py +0 -0
  82. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/domain/behaviors.py +0 -0
  83. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/domain/enums.py +0 -0
  84. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/domain/errors.py +0 -0
  85. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/domain/py.typed +0 -0
  86. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/entry.py +0 -0
  87. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/src/bitranox_template_py_cli/py.typed +0 -0
  88. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/conftest.py +0 -0
  89. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_behaviors.py +0 -0
  90. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_cache_effectiveness.py +0 -0
  91. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_cli_config.py +0 -0
  92. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_cli_core.py +0 -0
  93. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_cli_email.py +0 -0
  94. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_cli_env_file.py +0 -0
  95. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_cli_exit_codes.py +0 -0
  96. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_cli_overrides.py +0 -0
  97. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_cli_validation.py +0 -0
  98. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_config_overrides.py +0 -0
  99. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_deploy_permissions.py +0 -0
  100. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_display.py +0 -0
  101. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_enums.py +0 -0
  102. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_errors.py +0 -0
  103. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_logging.py +0 -0
  104. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_mail.py +0 -0
  105. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_metadata.py +0 -0
  106. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_metadata_sync.py +0 -0
  107. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_ports.py +0 -0
  108. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_property_email.py +0 -0
  109. {bitranox_template_py_cli-1.5.2 → bitranox_template_py_cli-1.5.4}/tests/test_property_overrides.py +0 -0
@@ -11,24 +11,27 @@ on:
11
11
 
12
12
  jobs:
13
13
  setup:
14
- name: Extract Python versions from pyproject.toml
14
+ name: Extract Python versions and OS matrix from pyproject.toml
15
15
  runs-on: ubuntu-latest
16
16
  outputs:
17
- python-versions: ${{ steps.versions.outputs.python-versions }}
18
- python-latest: ${{ steps.versions.outputs.python-latest }}
17
+ python-versions: ${{ steps.metadata.outputs.python-versions }}
18
+ python-latest: ${{ steps.metadata.outputs.python-latest }}
19
+ os-matrix: ${{ steps.metadata.outputs.os-matrix }}
19
20
  steps:
20
21
  - uses: actions/checkout@v6
21
22
  - uses: actions/setup-python@v6
22
23
  with:
23
24
  python-version: "3.x"
24
- - name: Parse classifiers for Python versions
25
- id: versions
25
+ - name: Parse pyproject.toml for Python versions and OS matrix
26
+ id: metadata
26
27
  shell: python
27
28
  run: |
28
29
  import json, os, tomllib
29
30
 
30
31
  with open("pyproject.toml", "rb") as f:
31
32
  data = tomllib.load(f)
33
+
34
+ # Python versions from classifiers
32
35
  classifiers = data.get("project", {}).get("classifiers", [])
33
36
  prefix = "Programming Language :: Python :: "
34
37
  versions = [
@@ -38,9 +41,15 @@ jobs:
38
41
  ]
39
42
  if not versions:
40
43
  raise SystemExit("No Python X.Y versions found in classifiers")
44
+
45
+ # OS matrix from [tool.ci]
46
+ ci_config = data.get("tool", {}).get("ci", {})
47
+ os_list = ci_config.get("os", ["ubuntu-latest"])
48
+
41
49
  with open(os.environ["GITHUB_OUTPUT"], "a") as fh:
42
50
  fh.write(f"python-versions={json.dumps(versions)}\n")
43
51
  fh.write(f"python-latest={versions[-1]}\n")
52
+ fh.write(f"os-matrix={json.dumps(os_list)}\n")
44
53
 
45
54
  test:
46
55
  name: Tests (Python ${{ matrix.python }}, ${{ matrix.os }})
@@ -49,7 +58,7 @@ jobs:
49
58
  strategy:
50
59
  fail-fast: false
51
60
  matrix:
52
- os: [ubuntu-latest, macos-latest, windows-latest]
61
+ os: ${{ fromJSON(needs.setup.outputs.os-matrix) }}
53
62
  python: ${{ fromJSON(needs.setup.outputs.python-versions) }}
54
63
  env:
55
64
  PYO3_USE_ABI3_FORWARD_COMPATIBILITY: "1"
@@ -77,23 +86,83 @@ jobs:
77
86
  id: metadata
78
87
  uses: ./.github/actions/extract-metadata
79
88
 
80
- - name: Install journald prerequisites
89
+ - name: Install CI system dependencies (Ubuntu)
81
90
  if: runner.os == 'Linux'
82
- shell: bash
91
+ shell: python
83
92
  run: |
84
- sudo apt-get update
85
- sudo apt-get install -y python3-systemd
93
+ import subprocess
94
+ try:
95
+ import tomllib
96
+ except ModuleNotFoundError:
97
+ import pip._vendor.tomli as tomllib
98
+ with open("pyproject.toml", "rb") as f:
99
+ data = tomllib.load(f)
100
+ deps = data.get("tool", {}).get("ci", {}).get("system-dependencies-ubuntu", [])
101
+ if deps:
102
+ print(f"Installing system packages: {' '.join(deps)}")
103
+ subprocess.check_call(["sudo", "apt-get", "update"])
104
+ subprocess.check_call(["sudo", "apt-get", "install", "-y", *deps])
105
+ else:
106
+ print("No CI system dependencies configured for Ubuntu.")
107
+
108
+ - name: Install CI system dependencies (macOS)
109
+ if: runner.os == 'macOS'
110
+ shell: python
111
+ run: |
112
+ import subprocess
113
+ try:
114
+ import tomllib
115
+ except ModuleNotFoundError:
116
+ import pip._vendor.tomli as tomllib
117
+ with open("pyproject.toml", "rb") as f:
118
+ data = tomllib.load(f)
119
+ deps = data.get("tool", {}).get("ci", {}).get("system-dependencies-macos", [])
120
+ if deps:
121
+ print(f"Installing system packages: {' '.join(deps)}")
122
+ subprocess.check_call(["brew", "install", *deps])
123
+ else:
124
+ print("No CI system dependencies configured for macOS.")
86
125
 
87
126
  - name: Install dev deps
88
127
  shell: bash
89
128
  run: |
90
129
  uv pip install -e .[dev] --system
91
130
 
92
- - name: Install Windows Event Log prerequisites
131
+ - name: Install CI system dependencies (Windows/choco)
93
132
  if: runner.os == 'Windows'
94
- shell: bash
133
+ shell: python
95
134
  run: |
96
- uv pip install pywin32 --system
135
+ import subprocess
136
+ try:
137
+ import tomllib
138
+ except ModuleNotFoundError:
139
+ import pip._vendor.tomli as tomllib
140
+ with open("pyproject.toml", "rb") as f:
141
+ data = tomllib.load(f)
142
+ deps = data.get("tool", {}).get("ci", {}).get("system-dependencies-windows-choco", [])
143
+ if deps:
144
+ print(f"Installing choco packages: {' '.join(deps)}")
145
+ subprocess.check_call(["choco", "install", "-y", *deps])
146
+ else:
147
+ print("No CI choco dependencies configured for Windows.")
148
+
149
+ - name: Install CI pip dependencies (Windows)
150
+ if: runner.os == 'Windows'
151
+ shell: python
152
+ run: |
153
+ import subprocess
154
+ try:
155
+ import tomllib
156
+ except ModuleNotFoundError:
157
+ import pip._vendor.tomli as tomllib
158
+ with open("pyproject.toml", "rb") as f:
159
+ data = tomllib.load(f)
160
+ deps = data.get("tool", {}).get("ci", {}).get("system-dependencies-windows-pip", [])
161
+ if deps:
162
+ print(f"Installing pip packages: {' '.join(deps)}")
163
+ subprocess.check_call(["uv", "pip", "install", *deps, "--system"])
164
+ else:
165
+ print("No CI pip dependencies configured for Windows.")
97
166
 
98
167
  # Self-hosted runners persist ruff cache via RUFF_CACHE_DIR volume mount,
99
168
  # so actions/cache is only needed on GitHub-hosted runners.
@@ -145,7 +214,7 @@ jobs:
145
214
  && steps.pytest.outcome == 'success'
146
215
  && matrix.os == 'ubuntu-latest'
147
216
  && matrix.python == needs.setup.outputs.python-latest
148
- uses: codecov/codecov-action@v5
217
+ uses: codecov/codecov-action@v7
149
218
  with:
150
219
  files: ${{ env.COV_REPORT_FILE }}
151
220
  fail_ci_if_error: false
@@ -201,8 +270,7 @@ jobs:
201
270
  exit 1
202
271
  fi
203
272
  if [[ "${{ steps.pipaudit.outcome }}" != "success" ]]; then
204
- echo "::error::pip-audit vulnerability scan failed"
205
- exit 1
273
+ echo "::warning::pip-audit vulnerability scan failed"
206
274
  fi
207
275
  echo "All checks passed!"
208
276
 
@@ -6,6 +6,22 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.5.4] 2026-06-14
10
+
11
+ ### Changed
12
+ - Added a `typed_click.py` facade wrapping rich-click's `option` / `version_option` decorators behind explicit, fully-known signatures, keeping the CLI strict-clean under pyright 1.1.410 (`reportUnknownMemberType`) without disabling the rule (ignore isolated to the facade).
13
+ - Bumped internal dependency floors: `lib_cli_exit_tools>=2.3.2`, `lib_log_rich>=6.3.5`, `lib_layered_config>=5.5.2`, `btx_lib_mail>=1.3.2`.
14
+
15
+ ## [1.5.3] - 2026-03-30
16
+
17
+ ### Changed
18
+ - Bumped Codecov GitHub Action to V6
19
+ - Updated CVE exclusion list: removed stale entries, added inline documentation for remaining exclusions
20
+ - pip-audit set to warning-only to reduce CI noise
21
+
22
+ ### Fixed
23
+ - Email transport: minor improvements to SMTP handling
24
+
9
25
  ## [1.5.2] - 2026-03-05
10
26
 
11
27
  ### Fixed
@@ -1,4 +1,4 @@
1
- # BMK MAKEFILE 2.9.0
1
+ # BMK MAKEFILE 2.9.4
2
2
  # do not alter this file - it might be overwritten on new versions of BMK
3
3
  # if You want to alter it, remove the first line # BMK MAKEFILE 1.0 - then it is a custom makefile and will not be overwritten
4
4
  # bmk Makefile — thin wrapper using `uv tool install` for persistent bmk
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bitranox_template_py_cli
3
- Version: 1.5.2
3
+ Version: 1.5.4
4
4
  Summary: Template CLI application with configuration management and structured logging
5
5
  Project-URL: Homepage, https://github.com/bitranox/bitranox_template_py_cli
6
6
  Project-URL: Repository, https://github.com/bitranox/bitranox_template_py_cli.git
@@ -21,33 +21,33 @@ Classifier: Programming Language :: Python :: 3.13
21
21
  Classifier: Programming Language :: Python :: 3.14
22
22
  Classifier: Typing :: Typed
23
23
  Requires-Python: >=3.10
24
- Requires-Dist: btx-lib-mail>=1.3.1
25
- Requires-Dist: lib-cli-exit-tools>=2.3.0
26
- Requires-Dist: lib-layered-config>=5.5.0
27
- Requires-Dist: lib-log-rich>=6.3.3
24
+ Requires-Dist: btx-lib-mail>=1.3.2
25
+ Requires-Dist: lib-cli-exit-tools>=2.3.2
26
+ Requires-Dist: lib-layered-config>=5.5.2
27
+ Requires-Dist: lib-log-rich>=6.3.5
28
28
  Requires-Dist: orjson>=3.11.7
29
29
  Requires-Dist: pydantic>=2.12.5
30
30
  Requires-Dist: rich-click>=1.9.7
31
31
  Provides-Extra: dev
32
32
  Requires-Dist: bandit>=1.9.4; extra == 'dev'
33
- Requires-Dist: build>=1.4.0; extra == 'dev'
34
- Requires-Dist: codecov-cli>=11.2.6; extra == 'dev'
35
- Requires-Dist: httpx>=0.28.1; extra == 'dev'
36
- Requires-Dist: hypothesis>=6.151.9; extra == 'dev'
37
- Requires-Dist: import-linter>=2.10; extra == 'dev'
38
- Requires-Dist: jaraco-context>=6.1.0; extra == 'dev'
33
+ Requires-Dist: build>=1.4.2; extra == 'dev'
34
+ Requires-Dist: codecov-cli>=11.2.8; extra == 'dev'
35
+ Requires-Dist: httpx2>=2.2.0; extra == 'dev'
36
+ Requires-Dist: hypothesis>=6.151.10; extra == 'dev'
37
+ Requires-Dist: import-linter>=2.11; extra == 'dev'
38
+ Requires-Dist: jaraco-context>=6.1.2; extra == 'dev'
39
39
  Requires-Dist: pip-audit>=2.10.0; extra == 'dev'
40
40
  Requires-Dist: pynacl>=1.6.2; extra == 'dev'
41
41
  Requires-Dist: pyright[nodejs]>=1.1.408; extra == 'dev'
42
- Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
42
+ Requires-Dist: pytest-cov>=7.1.0; extra == 'dev'
43
43
  Requires-Dist: pytest>=9.0.2; extra == 'dev'
44
44
  Requires-Dist: python-multipart>=0.0.22; extra == 'dev'
45
45
  Requires-Dist: rtoml>=0.13.0; extra == 'dev'
46
- Requires-Dist: ruff>=0.15.4; extra == 'dev'
47
- Requires-Dist: textual>=8.0.2; extra == 'dev'
46
+ Requires-Dist: ruff>=0.15.8; extra == 'dev'
47
+ Requires-Dist: textual>=8.2.1; extra == 'dev'
48
48
  Requires-Dist: twine>=6.2.0; extra == 'dev'
49
49
  Requires-Dist: urllib3>=2.6.3; extra == 'dev'
50
- Requires-Dist: virtualenv>=21.1.0; extra == 'dev'
50
+ Requires-Dist: virtualenv>=21.2.0; extra == 'dev'
51
51
  Requires-Dist: wheel>=0.46.3; extra == 'dev'
52
52
  Description-Content-Type: text/markdown
53
53
 
@@ -63,7 +63,6 @@ Description-Content-Type: text/markdown
63
63
  [![Code Style: Ruff](https://img.shields.io/badge/Code%20Style-Ruff-46A3FF?logo=ruff&labelColor=000)](https://docs.astral.sh/ruff/)
64
64
  [![codecov](https://codecov.io/gh/bitranox/bitranox_template_py_cli/graph/badge.svg?token=UFBaUDIgRk)](https://codecov.io/gh/bitranox/bitranox_template_py_cli)
65
65
  [![Maintainability](https://qlty.sh/badges/041ba2c1-37d6-40bb-85a0-ec5a8a0aca0c/maintainability.svg)](https://qlty.sh/gh/bitranox/projects/bitranox_template_py_cli)
66
- [![Known Vulnerabilities](https://snyk.io/test/github/bitranox/bitranox_template_py_cli/badge.svg)](https://snyk.io/test/github/bitranox/bitranox_template_py_cli)
67
66
  [![security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
68
67
 
69
68
 
@@ -95,6 +94,14 @@ Description-Content-Type: text/markdown
95
94
  ```bash
96
95
  # macOS/Linux
97
96
  curl -LsSf https://astral.sh/uv/install.sh | sh
97
+
98
+ # Copy the actual binaries
99
+ cp /root/.local/bin/uv /usr/local/bin/uv
100
+ cp /root/.local/bin/uvx /usr/local/bin/uvx
101
+
102
+ # Ensure world-executable
103
+ chmod 755 /usr/local/bin/uv /usr/local/bin/uvx
104
+
98
105
  # Windows (PowerShell)
99
106
  powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
100
107
  ```
@@ -159,6 +166,9 @@ uv tool install bitranox_template_py_cli
159
166
  # Verify
160
167
  bitranox-template-py-cli --version
161
168
 
169
+ # deploy config files
170
+ bitranox-template-py-cli deploy-config --target app
171
+
162
172
  # Try it out
163
173
  bitranox-template-py-cli hello
164
174
  bitranox-template-py-cli info
@@ -10,7 +10,6 @@
10
10
  [![Code Style: Ruff](https://img.shields.io/badge/Code%20Style-Ruff-46A3FF?logo=ruff&labelColor=000)](https://docs.astral.sh/ruff/)
11
11
  [![codecov](https://codecov.io/gh/bitranox/bitranox_template_py_cli/graph/badge.svg?token=UFBaUDIgRk)](https://codecov.io/gh/bitranox/bitranox_template_py_cli)
12
12
  [![Maintainability](https://qlty.sh/badges/041ba2c1-37d6-40bb-85a0-ec5a8a0aca0c/maintainability.svg)](https://qlty.sh/gh/bitranox/projects/bitranox_template_py_cli)
13
- [![Known Vulnerabilities](https://snyk.io/test/github/bitranox/bitranox_template_py_cli/badge.svg)](https://snyk.io/test/github/bitranox/bitranox_template_py_cli)
14
13
  [![security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
15
14
 
16
15
 
@@ -42,6 +41,14 @@
42
41
  ```bash
43
42
  # macOS/Linux
44
43
  curl -LsSf https://astral.sh/uv/install.sh | sh
44
+
45
+ # Copy the actual binaries
46
+ cp /root/.local/bin/uv /usr/local/bin/uv
47
+ cp /root/.local/bin/uvx /usr/local/bin/uvx
48
+
49
+ # Ensure world-executable
50
+ chmod 755 /usr/local/bin/uv /usr/local/bin/uvx
51
+
45
52
  # Windows (PowerShell)
46
53
  powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
47
54
  ```
@@ -106,6 +113,9 @@ uv tool install bitranox_template_py_cli
106
113
  # Verify
107
114
  bitranox-template-py-cli --version
108
115
 
116
+ # deploy config files
117
+ bitranox-template-py-cli deploy-config --target app
118
+
109
119
  # Try it out
110
120
  bitranox-template-py-cli hello
111
121
  bitranox-template-py-cli info
@@ -1,15 +1,15 @@
1
1
  [project]
2
2
  name = "bitranox_template_py_cli"
3
- version = "1.5.2"
3
+ version = "1.5.4"
4
4
  description = "Template CLI application with configuration management and structured logging"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  dependencies = [
8
8
  "rich-click>=1.9.7",
9
- "lib_cli_exit_tools>=2.3.0",
10
- "lib_log_rich>=6.3.3",
11
- "lib_layered_config>=5.5.0",
12
- "btx_lib_mail>=1.3.1",
9
+ "lib_cli_exit_tools>=2.3.2",
10
+ "lib_log_rich>=6.3.5",
11
+ "lib_layered_config>=5.5.2",
12
+ "btx_lib_mail>=1.3.2",
13
13
  "pydantic>=2.12.5",
14
14
  "orjson>=3.11.7",
15
15
  ]
@@ -30,6 +30,15 @@ classifiers = [
30
30
  "Typing :: Typed",
31
31
  ]
32
32
 
33
+ # System packages required for CI runners (read by GitHub Actions workflow template)
34
+ # Format: "apt_package_name" for Ubuntu, future: {"apt": "...", "brew": "...", "choco": "..."}
35
+ [tool.ci]
36
+ system-dependencies-ubuntu = []
37
+ system-dependencies-macos = []
38
+ system-dependencies-windows-pip = []
39
+ system-dependencies-windows-choco = []
40
+ os = ["ubuntu-latest", "windows-latest", "macos-latest"]
41
+
33
42
  [project.urls]
34
43
  Homepage = "https://github.com/bitranox/bitranox_template_py_cli"
35
44
  Repository = "https://github.com/bitranox/bitranox_template_py_cli.git"
@@ -38,26 +47,26 @@ Issues = "https://github.com/bitranox/bitranox_template_py_cli/issues"
38
47
  [project.optional-dependencies]
39
48
  dev = [
40
49
  "pytest>=9.0.2",
41
- "pytest-cov>=7.0.0",
42
- "hypothesis>=6.151.9",
43
- "ruff>=0.15.4",
50
+ "pytest-cov>=7.1.0",
51
+ "hypothesis>=6.151.10",
52
+ "ruff>=0.15.8",
44
53
  "pyright[nodejs]>=1.1.408",
45
54
  "bandit>=1.9.4",
46
- "build>=1.4.0",
55
+ "build>=1.4.2",
47
56
  "twine>=6.2.0",
48
- "codecov-cli>=11.2.6",
57
+ "codecov-cli>=11.2.8",
49
58
  "pip-audit>=2.10.0",
50
- "textual>=8.0.2",
51
- "import-linter>=2.10",
59
+ "textual>=8.2.1",
60
+ "import-linter>=2.11",
52
61
  "rtoml>=0.13.0",
53
- "httpx>=0.28.1",
62
+ "httpx2>=2.2.0",
54
63
  # Transitive CVE pins
55
- "jaraco-context>=6.1.0", # CVE-2025-23949
64
+ "jaraco-context>=6.1.2", # CVE-2025-23949
56
65
  "python-multipart>=0.0.22", # CVE-2025-24486
57
66
  "wheel>=0.46.3", # CVE-2025-24049
58
67
  "pynacl>=1.6.2", # CVE-2025-69277
59
68
  "urllib3>=2.6.3", # CVE-2025-66471, CVE-2025-66418
60
- "virtualenv>=21.1.0", # CVE-2025-22702
69
+ "virtualenv>=21.2.0", # CVE-2025-22702
61
70
  ]
62
71
 
63
72
  [project.scripts]
@@ -248,14 +257,10 @@ layers = [
248
257
  # PYSEC-2022-42969: py library ReDoS — py 1.11.0 is installed in the development
249
258
  # environment but is not a dependency of this project; no fix available.
250
259
  ignore-vulns = [
251
- "PYSEC-2022-42969",
252
- "PYSEC-2022-43012", # setuptools 65.5.0 — env-only, not a project dependency
253
- "PYSEC-2025-49", # setuptools 65.5.0 — env-only, not a project dependency
254
- "CVE-2024-6345", # setuptools 65.5.0 — env-only, not a project dependency
255
- "CVE-2025-8869", # pip 24.3.1 — env-only, not a project dependency
256
- "CVE-2026-1703",
257
- "CVE-2026-25990",
258
- "CVE-2026-26007",
260
+ "PYSEC-2022-42969", # py 1.11.0 — ReDoS, env-only, no fix available
261
+ "CVE-2025-8869", # pip 24.3.1 — env-only, not a project dependency
262
+ "CVE-2026-1703", # pip 24.3.1 — env-only, not a project dependency
263
+ "CVE-2026-25990", # pillow 12.0.0 — env-only, fix in 12.1.1
259
264
  ]
260
265
 
261
266
  [tool.bashate]
@@ -40,7 +40,7 @@ name = "bitranox_template_py_cli"
40
40
  #: Human-readable summary shown in CLI help output.
41
41
  title = "Template CLI application with configuration management and structured logging"
42
42
  #: Current release version pulled from ``pyproject.toml`` by automation.
43
- version = "1.5.2"
43
+ version = "1.5.4"
44
44
  #: Repository homepage presented to users.
45
45
  homepage = "https://github.com/bitranox/bitranox_template_py_cli"
46
46
  #: Author attribution surfaced in CLI output.
@@ -25,25 +25,26 @@ from bitranox_template_py_cli.domain.enums import DeployTarget, OutputFormat
25
25
  from ..constants import CLICK_CONTEXT_SETTINGS
26
26
  from ..context import CLIContext, get_cli_context
27
27
  from ..exit_codes import ExitCode
28
+ from ..typed_click import option
28
29
 
29
30
  logger = logging.getLogger(__name__)
30
31
 
31
32
 
32
33
  @click.command("config", context_settings=CLICK_CONTEXT_SETTINGS)
33
- @click.option(
34
+ @option(
34
35
  "--format",
35
36
  "output_format",
36
37
  type=click.Choice([f.value for f in OutputFormat], case_sensitive=False),
37
38
  default=OutputFormat.HUMAN.value,
38
39
  help="Output format (human-readable or JSON)",
39
40
  )
40
- @click.option(
41
+ @option(
41
42
  "--section",
42
43
  type=str,
43
44
  default=None,
44
45
  help="Show only a specific configuration section (e.g., 'lib_log_rich')",
45
46
  )
46
- @click.option(
47
+ @option(
47
48
  "--profile",
48
49
  type=str,
49
50
  default=None,
@@ -134,7 +135,7 @@ def _parse_octal_mode(ctx: click.Context, param: click.Parameter, value: str | N
134
135
 
135
136
 
136
137
  @click.command("config-deploy", context_settings=CLICK_CONTEXT_SETTINGS)
137
- @click.option(
138
+ @option(
138
139
  "--target",
139
140
  "targets",
140
141
  type=click.Choice([t.value for t in DeployTarget], case_sensitive=False),
@@ -142,32 +143,32 @@ def _parse_octal_mode(ctx: click.Context, param: click.Parameter, value: str | N
142
143
  required=True,
143
144
  help="Target configuration layer(s) to deploy to (can specify multiple)",
144
145
  )
145
- @click.option(
146
+ @option(
146
147
  "--force",
147
148
  is_flag=True,
148
149
  default=False,
149
150
  help="Overwrite existing configuration files",
150
151
  )
151
- @click.option(
152
+ @option(
152
153
  "--profile",
153
154
  type=str,
154
155
  default=None,
155
156
  help="Override profile from root command (e.g., 'production', 'test')",
156
157
  )
157
- @click.option(
158
+ @option(
158
159
  "--permissions/--no-permissions",
159
160
  "set_permissions",
160
161
  default=None,
161
162
  help="Set Unix permissions (755/644 for app/host, 700/600 for user). Default: enabled.",
162
163
  )
163
- @click.option(
164
+ @option(
164
165
  "--dir-mode",
165
166
  type=str,
166
167
  default=None,
167
168
  callback=_parse_octal_mode,
168
169
  help="Override directory mode (octal, e.g., 750 or 0o750)",
169
170
  )
170
- @click.option(
171
+ @option(
171
172
  "--file-mode",
172
173
  type=str,
173
174
  default=None,
@@ -290,8 +291,8 @@ def _report_deployment_result(deployed_paths: list[Path], profile: str | None, s
290
291
 
291
292
 
292
293
  @click.command("config-generate-examples", context_settings=CLICK_CONTEXT_SETTINGS)
293
- @click.option("--destination", type=click.Path(file_okay=False), required=True, help="Directory to write example files")
294
- @click.option("--force", is_flag=True, default=False, help="Overwrite existing files")
294
+ @option("--destination", type=click.Path(file_okay=False), required=True, help="Directory to write example files")
295
+ @option("--force", is_flag=True, default=False, help="Overwrite existing files")
295
296
  @click.pass_context
296
297
  def cli_config_generate_examples(ctx: click.Context, destination: str, force: bool) -> None:
297
298
  """Generate example configuration files in a target directory.
@@ -22,6 +22,7 @@ from bitranox_template_py_cli.application.ports import LoadEmailConfigFromDict
22
22
  from bitranox_template_py_cli.domain.errors import ConfigurationError, DeliveryError
23
23
 
24
24
  from ...exit_codes import ExitCode
25
+ from ...typed_click import option
25
26
 
26
27
  logger = logging.getLogger(__name__)
27
28
 
@@ -80,23 +81,23 @@ def smtp_config_options(func: Callable[..., Any]) -> Callable[..., Any]:
80
81
  can be overridden at invocation time.
81
82
  """
82
83
  options = [
83
- click.option(
84
+ option(
84
85
  "--smtp-host",
85
86
  "smtp_hosts",
86
87
  multiple=True,
87
88
  default=(),
88
89
  help="Override SMTP host (can specify multiple; format host:port)",
89
90
  ),
90
- click.option("--smtp-username", default=None, help="Override SMTP authentication username"),
91
- click.option("--smtp-password", default=None, help="Override SMTP authentication password"),
92
- click.option("--use-starttls/--no-use-starttls", default=None, help="Override STARTTLS setting"),
93
- click.option("--timeout", "timeout", type=float, default=None, help="Override socket timeout in seconds"),
94
- click.option(
91
+ option("--smtp-username", default=None, help="Override SMTP authentication username"),
92
+ option("--smtp-password", default=None, help="Override SMTP authentication password"),
93
+ option("--use-starttls/--no-use-starttls", default=None, help="Override STARTTLS setting"),
94
+ option("--timeout", "timeout", type=float, default=None, help="Override socket timeout in seconds"),
95
+ option(
95
96
  "--raise-on-missing-attachments/--no-raise-on-missing-attachments",
96
97
  default=None,
97
98
  help="Override missing attachment handling",
98
99
  ),
99
- click.option(
100
+ option(
100
101
  "--raise-on-invalid-recipient/--no-raise-on-invalid-recipient",
101
102
  default=None,
102
103
  help="Override invalid recipient handling",
@@ -15,6 +15,7 @@ from pydantic import ValidationError
15
15
 
16
16
  from ...constants import CLICK_CONTEXT_SETTINGS
17
17
  from ...context import get_cli_context
18
+ from ...typed_click import option
18
19
  from ._common import (
19
20
  apply_validated_overrides,
20
21
  execute_with_email_error_handling,
@@ -53,20 +54,18 @@ def _log_send_email_start(
53
54
 
54
55
 
55
56
  @click.command("send-email", context_settings=CLICK_CONTEXT_SETTINGS)
56
- @click.option(
57
+ @option(
57
58
  "--to",
58
59
  "recipients",
59
60
  multiple=True,
60
61
  required=False,
61
62
  help="Recipient email address (can specify multiple; uses config default if not specified)",
62
63
  )
63
- @click.option("--subject", required=True, help="Email subject line")
64
- @click.option("--body", default="", help="Plain-text email body")
65
- @click.option("--body-html", default="", help="HTML email body (sent as multipart with plain text)")
66
- @click.option(
67
- "--from", "from_address", default=None, help="Override sender address (uses config default if not specified)"
68
- )
69
- @click.option(
64
+ @option("--subject", required=True, help="Email subject line")
65
+ @option("--body", default="", help="Plain-text email body")
66
+ @option("--body-html", default="", help="HTML email body (sent as multipart with plain text)")
67
+ @option("--from", "from_address", default=None, help="Override sender address (uses config default if not specified)")
68
+ @option(
70
69
  "--attachment",
71
70
  "attachments",
72
71
  multiple=True,
@@ -14,6 +14,7 @@ from pydantic import ValidationError
14
14
 
15
15
  from ...constants import CLICK_CONTEXT_SETTINGS
16
16
  from ...context import get_cli_context
17
+ from ...typed_click import option
17
18
  from ._common import (
18
19
  apply_validated_overrides,
19
20
  execute_with_email_error_handling,
@@ -27,18 +28,16 @@ logger = logging.getLogger(__name__)
27
28
 
28
29
 
29
30
  @click.command("send-notification", context_settings=CLICK_CONTEXT_SETTINGS)
30
- @click.option(
31
+ @option(
31
32
  "--to",
32
33
  "recipients",
33
34
  multiple=True,
34
35
  required=False,
35
36
  help="Recipient email address (can specify multiple; uses config default if not specified)",
36
37
  )
37
- @click.option("--subject", required=True, help="Notification subject line")
38
- @click.option("--message", required=True, help="Notification message (plain text)")
39
- @click.option(
40
- "--from", "from_address", default=None, help="Override sender address (uses config default if not specified)"
41
- )
38
+ @option("--subject", required=True, help="Notification subject line")
39
+ @option("--message", required=True, help="Notification message (plain text)")
40
+ @option("--from", "from_address", default=None, help="Override sender address (uses config default if not specified)")
42
41
  @smtp_config_options
43
42
  @click.pass_context
44
43
  def cli_send_notification(
@@ -11,10 +11,11 @@ from __future__ import annotations
11
11
  import rich_click as click
12
12
 
13
13
  from ..constants import CLICK_CONTEXT_SETTINGS
14
+ from ..typed_click import option
14
15
 
15
16
 
16
17
  @click.command("logdemo", context_settings=CLICK_CONTEXT_SETTINGS)
17
- @click.option("--theme", default="classic", help="Logging theme to preview")
18
+ @option("--theme", default="classic", help="Logging theme to preview")
18
19
  @click.pass_context
19
20
  def cli_logdemo(ctx: click.Context, theme: str) -> None:
20
21
  """Run a logging demonstration to preview log output."""
@@ -19,6 +19,7 @@ from bitranox_template_py_cli.adapters.config.overrides import apply_overrides
19
19
 
20
20
  from .constants import CLICK_CONTEXT_SETTINGS
21
21
  from .context import apply_traceback_preferences, store_cli_context
22
+ from .typed_click import option, version_option
22
23
 
23
24
  if TYPE_CHECKING:
24
25
  from bitranox_template_py_cli.composition import AppServices
@@ -49,24 +50,24 @@ def _apply_cli_overrides(config: Config, set_overrides: tuple[str, ...]) -> Conf
49
50
  context_settings=CLICK_CONTEXT_SETTINGS,
50
51
  invoke_without_command=True,
51
52
  )
52
- @click.version_option(
53
+ @version_option(
53
54
  version=__init__conf__.version,
54
55
  prog_name=__init__conf__.shell_command,
55
56
  message=f"{__init__conf__.shell_command} version {__init__conf__.version}",
56
57
  )
57
- @click.option(
58
+ @option(
58
59
  "--traceback/--no-traceback",
59
60
  is_flag=True,
60
61
  default=False,
61
62
  help="Show full Python traceback on errors",
62
63
  )
63
- @click.option(
64
+ @option(
64
65
  "--profile",
65
66
  type=str,
66
67
  default=None,
67
68
  help="Load configuration from a named profile (e.g., 'production', 'test')",
68
69
  )
69
- @click.option(
70
+ @option(
70
71
  "--set",
71
72
  "set_overrides",
72
73
  multiple=True,
@@ -74,7 +75,7 @@ def _apply_cli_overrides(config: Config, set_overrides: tuple[str, ...]) -> Conf
74
75
  metavar="SECTION.KEY=VALUE",
75
76
  help="Override a configuration setting (repeatable).",
76
77
  )
77
- @click.option(
78
+ @option(
78
79
  "--env-file",
79
80
  "env_file",
80
81
  type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),
@@ -0,0 +1,37 @@
1
+ """Strictly-typed wrappers for the rich_click decorators with unknown types.
2
+
3
+ rich_click ships ``py.typed``, but it re-exports click's ``option`` and
4
+ ``version_option`` decorators, whose ``type: ParamType[Unknown]`` parameter makes
5
+ pyright (strict mode) report ``reportUnknownMemberType`` at every call site.
6
+ Wrapping the two affected decorators here behind explicit, fully-known
7
+ signatures keeps the rest of the CLI layer strict-clean without disabling the
8
+ rule. This module is the single boundary that touches the untyped surface, so
9
+ the only ``# pyright: ignore`` for this third-party gap lives here.
10
+
11
+ Other click members (``command``, ``group``, ``echo``, ``Context``, ``Path`` …)
12
+ type cleanly and are still used directly as ``click.X`` at call sites.
13
+ """
14
+
15
+ from collections.abc import Callable
16
+ from typing import Any
17
+
18
+ import rich_click as click
19
+
20
+ # Click decorators turn a command function (or another decorator's result) into
21
+ # a wrapped callable. ``Any`` is fully known to pyright (unlike ``Unknown``), so
22
+ # this alias is strict-clean and works both as ``@option(...)`` and as a value
23
+ # collected into a list (see email/_common.py's shared-options pattern).
24
+ _CommandDecorator = Callable[[Callable[..., Any]], Callable[..., Any]]
25
+
26
+
27
+ def option(*param_decls: str, **attrs: Any) -> _CommandDecorator:
28
+ """Typed wrapper over :func:`rich_click.option`. See module docstring."""
29
+ return click.option(*param_decls, **attrs) # pyright: ignore[reportUnknownMemberType]
30
+
31
+
32
+ def version_option(*param_decls: str, **attrs: Any) -> _CommandDecorator:
33
+ """Typed wrapper over :func:`rich_click.version_option`. See module docstring."""
34
+ return click.version_option(*param_decls, **attrs) # pyright: ignore[reportUnknownMemberType]
35
+
36
+
37
+ __all__ = ["option", "version_option"]
@@ -22,13 +22,15 @@ logger = logging.getLogger(__name__)
22
22
  # Keywords that may indicate sensitive data in exception messages
23
23
  _SENSITIVE_KEYWORDS = frozenset(
24
24
  {
25
- "password",
26
- "credential",
25
+ "api_key",
27
26
  "auth",
28
- "secret",
29
- "token",
27
+ "bearer",
28
+ "credential",
30
29
  "key",
31
30
  "login",
31
+ "password",
32
+ "secret",
33
+ "token",
32
34
  }
33
35
  )
34
36
 
@@ -111,7 +111,7 @@ def test_module_entry_subprocess_help() -> None:
111
111
  This tests the true CLI invocation path that end-users would experience,
112
112
  complementing the runpy-based tests that run in-process.
113
113
  """
114
- result = subprocess.run( # noqa: S603
114
+ result = subprocess.run(
115
115
  [sys.executable, "-m", "bitranox_template_py_cli", "--help"],
116
116
  capture_output=True,
117
117
  timeout=30,
@@ -128,7 +128,7 @@ def test_module_entry_subprocess_help() -> None:
128
128
  @pytest.mark.os_agnostic
129
129
  def test_module_entry_subprocess_version() -> None:
130
130
  """Verify `python -m bitranox_template_py_cli --version` outputs version."""
131
- result = subprocess.run( # noqa: S603
131
+ result = subprocess.run(
132
132
  [sys.executable, "-m", "bitranox_template_py_cli", "--version"],
133
133
  capture_output=True,
134
134
  timeout=30,