lib-cli-exit-tools 1.6.0__tar.gz → 2.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 (96) hide show
  1. lib_cli_exit_tools-2.1.0/.env.example +11 -0
  2. lib_cli_exit_tools-2.1.0/.github/dependabot.yml +22 -0
  3. lib_cli_exit_tools-2.1.0/.github/workflows/ci.yml +162 -0
  4. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/.github/workflows/codeql.yml +4 -5
  5. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/.github/workflows/release.yml +2 -2
  6. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/AGENTS.md +5 -6
  7. lib_cli_exit_tools-2.1.0/AGENTS_README.md +176 -0
  8. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/CHANGELOG.md +27 -0
  9. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/CONTRIBUTING.md +5 -16
  10. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/DEVELOPMENT.md +23 -31
  11. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/INSTALL.md +5 -27
  12. lib_cli_exit_tools-2.1.0/Makefile +54 -0
  13. lib_cli_exit_tools-1.6.0/README.md → lib_cli_exit_tools-2.1.0/PKG-INFO +267 -19
  14. lib_cli_exit_tools-1.6.0/PKG-INFO → lib_cli_exit_tools-2.1.0/README.md +231 -55
  15. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/codecov.yml +0 -2
  16. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/docs/system-design/module_reference.md +14 -14
  17. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/notebooks/Quickstart.ipynb +1 -1
  18. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/pyproject.toml +15 -17
  19. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/_utils.py +271 -169
  20. lib_cli_exit_tools-2.1.0/scripts/build.py +40 -0
  21. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/bump.py +4 -9
  22. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/bump_major.py +2 -6
  23. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/bump_minor.py +2 -6
  24. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/bump_patch.py +2 -6
  25. lib_cli_exit_tools-2.1.0/scripts/bump_version.py +80 -0
  26. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/clean.py +2 -2
  27. lib_cli_exit_tools-2.1.0/scripts/cli.py +213 -0
  28. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/dev.py +2 -8
  29. lib_cli_exit_tools-2.1.0/scripts/help.py +41 -0
  30. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/install.py +2 -8
  31. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/menu.py +36 -126
  32. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/push.py +24 -21
  33. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/release.py +26 -63
  34. lib_cli_exit_tools-2.1.0/scripts/run_cli.py +154 -0
  35. lib_cli_exit_tools-2.1.0/scripts/target_metadata.py +174 -0
  36. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/test.py +232 -126
  37. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/version_current.py +1 -6
  38. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/src/lib_cli_exit_tools/__init__.py +1 -0
  39. lib_cli_exit_tools-2.1.0/src/lib_cli_exit_tools/__init__conf__.py +67 -0
  40. lib_cli_exit_tools-2.1.0/src/lib_cli_exit_tools/__main__.py +13 -0
  41. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/src/lib_cli_exit_tools/adapters/signals.py +19 -30
  42. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/src/lib_cli_exit_tools/application/runner.py +229 -45
  43. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/src/lib_cli_exit_tools/cli.py +11 -15
  44. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/src/lib_cli_exit_tools/core/exit_codes.py +88 -29
  45. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/src/lib_cli_exit_tools/lib_cli_exit_tools.py +2 -0
  46. lib_cli_exit_tools-2.1.0/tests/conftest.py +57 -0
  47. lib_cli_exit_tools-2.1.0/tests/test_adapters_signals.py +65 -0
  48. lib_cli_exit_tools-2.1.0/tests/test_application_runner.py +385 -0
  49. lib_cli_exit_tools-2.1.0/tests/test_behaviors.py +99 -0
  50. lib_cli_exit_tools-2.1.0/tests/test_cli.py +164 -0
  51. lib_cli_exit_tools-2.1.0/tests/test_core_configuration.py +47 -0
  52. lib_cli_exit_tools-2.1.0/tests/test_core_exit_codes.py +265 -0
  53. lib_cli_exit_tools-2.1.0/tests/test_metadata.py +47 -0
  54. lib_cli_exit_tools-2.1.0/tests/test_module_entry.py +137 -0
  55. lib_cli_exit_tools-2.1.0/tests/test_scripts.py +233 -0
  56. lib_cli_exit_tools-2.1.0/tests/test_signals_integration.py +122 -0
  57. lib_cli_exit_tools-1.6.0/.env.example +0 -18
  58. lib_cli_exit_tools-1.6.0/.github/workflows/ci.yml +0 -268
  59. lib_cli_exit_tools-1.6.0/Makefile +0 -59
  60. lib_cli_exit_tools-1.6.0/packaging/README.md +0 -68
  61. lib_cli_exit_tools-1.6.0/packaging/brew/Formula/lib-cli-exit-tools.rb +0 -30
  62. lib_cli_exit_tools-1.6.0/packaging/conda/recipe/meta.yaml +0 -46
  63. lib_cli_exit_tools-1.6.0/packaging/nix/flake.nix +0 -74
  64. lib_cli_exit_tools-1.6.0/scripts/build.py +0 -78
  65. lib_cli_exit_tools-1.6.0/scripts/bump_version.py +0 -614
  66. lib_cli_exit_tools-1.6.0/scripts/cli.py +0 -158
  67. lib_cli_exit_tools-1.6.0/scripts/generate_nix_flake.py +0 -231
  68. lib_cli_exit_tools-1.6.0/scripts/run_cli.py +0 -51
  69. lib_cli_exit_tools-1.6.0/src/lib_cli_exit_tools/__init__conf__.py +0 -220
  70. lib_cli_exit_tools-1.6.0/src/lib_cli_exit_tools/__main__.py +0 -11
  71. lib_cli_exit_tools-1.6.0/tests/adapters/test_signals_poetry.py +0 -119
  72. lib_cli_exit_tools-1.6.0/tests/application/test_runner_poetry.py +0 -385
  73. lib_cli_exit_tools-1.6.0/tests/application/test_signal_poetry_posix.py +0 -84
  74. lib_cli_exit_tools-1.6.0/tests/application/test_signal_poetry_windows.py +0 -93
  75. lib_cli_exit_tools-1.6.0/tests/cli/test_cli_poetry.py +0 -305
  76. lib_cli_exit_tools-1.6.0/tests/conftest.py +0 -28
  77. lib_cli_exit_tools-1.6.0/tests/core/test_configuration.py +0 -73
  78. lib_cli_exit_tools-1.6.0/tests/core/test_exit_code_poetry.py +0 -192
  79. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/.codacy/cli-config.yaml +0 -0
  80. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/.codacy/cli.sh +0 -0
  81. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/.codacy/codacy.yaml +0 -0
  82. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/.codacy/tools-configs/lizard.yaml +0 -0
  83. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/.codacy/tools-configs/trivy.yaml +0 -0
  84. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/.devcontainer/devcontainer.json +0 -0
  85. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/.devcontainer/settings.json +0 -0
  86. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/.gitignore +0 -0
  87. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/.qlty/qlty.toml +0 -0
  88. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/.snyk +0 -0
  89. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/LICENSE +0 -0
  90. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/__init__.py +0 -0
  91. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/scripts/__main__.py +0 -0
  92. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/src/lib_cli_exit_tools/adapters/__init__.py +0 -0
  93. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/src/lib_cli_exit_tools/application/__init__.py +0 -0
  94. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/src/lib_cli_exit_tools/core/__init__.py +0 -0
  95. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/src/lib_cli_exit_tools/core/configuration.py +0 -0
  96. {lib_cli_exit_tools-1.6.0 → lib_cli_exit_tools-2.1.0}/src/lib_cli_exit_tools/py.typed +0 -0
@@ -0,0 +1,11 @@
1
+ # Copy to .env and fill values as needed.
2
+
3
+ # Codecov upload token (required for private repos; optional for public repos)
4
+ CODECOV_TOKEN=
5
+
6
+ # PyPI API token for release workflow (.github/workflows/release.yml)
7
+ # Format: pypi-AgENdGVzdC5weXBpLm9yZwIk...
8
+ PYPI_API_TOKEN=
9
+
10
+ # Optional: GitHub token for API-limited tasks (e.g., higher rate limits on dependency audits)
11
+ GITHUB_TOKEN=
@@ -0,0 +1,22 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ allow:
8
+ - dependency-type: "direct"
9
+ labels:
10
+ - "dependencies"
11
+ commit-message:
12
+ prefix: "deps"
13
+ include: "scope"
14
+ - package-ecosystem: "github-actions"
15
+ directory: "/"
16
+ schedule:
17
+ interval: "weekly"
18
+ labels:
19
+ - "dependencies"
20
+ commit-message:
21
+ prefix: "deps"
22
+ include: "scope"
@@ -0,0 +1,162 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, master ]
6
+ pull_request:
7
+ branches: [ main, master ]
8
+
9
+ jobs:
10
+ test:
11
+ name: Tests (Python ${{ matrix.python }}, ${{ matrix.os }})
12
+ runs-on: ${{ matrix.os }}
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ os: [ubuntu-latest, macos-latest, windows-latest]
17
+ # Test against Python 3.13 and the latest 3.x available on Actions runners
18
+ python: ["3.13", "3.x"]
19
+ steps:
20
+ - uses: actions/checkout@v5
21
+ - uses: actions/setup-python@v6
22
+ with:
23
+ python-version: ${{ matrix.python }}
24
+ - name: Extract project metadata
25
+ shell: python
26
+ run: |
27
+ import os
28
+ import tomllib
29
+ from pathlib import Path
30
+
31
+ data = tomllib.loads(Path('pyproject.toml').read_text('utf-8'))
32
+ project = data['project']['name']
33
+ module = project.replace('-', '_')
34
+ dash = project.replace('_', '-')
35
+ scripts = list(data['project'].get('scripts', {}).keys())
36
+ cli_bin = scripts[0] if scripts else dash
37
+
38
+ with open(os.environ['GITHUB_ENV'], 'a', encoding='utf-8') as env:
39
+ env.write(f"PROJECT_NAME={project}\n")
40
+ env.write(f"PACKAGE_MODULE={module}\n")
41
+ env.write(f"CLI_BIN={cli_bin}\n")
42
+ - name: Install make on Windows
43
+ if: runner.os == 'Windows'
44
+ shell: pwsh
45
+ run: |
46
+ choco install -y make
47
+ echo "C:\\ProgramData\\chocolatey\\bin" >> $env:GITHUB_PATH
48
+ - name: Upgrade pip
49
+ shell: bash
50
+ run: python -m pip install --upgrade pip
51
+
52
+ - name: Install dev deps
53
+ shell: bash
54
+ run: |
55
+ pip install -e .[dev]
56
+
57
+ - name: Install Windows Event Log prerequisites
58
+ if: runner.os == 'Windows'
59
+ shell: bash
60
+ run: |
61
+ pip install pywin32
62
+ - name: Run full test suite (lint, types, tests, coverage, codecov)
63
+ shell: bash
64
+ env:
65
+ TEST_VERBOSE: "1"
66
+ run: make test
67
+ - name: Build wheel/sdist
68
+ shell: bash
69
+ run: python -m build
70
+ - name: Verify wheel install in clean env
71
+ shell: bash
72
+ run: |
73
+ python -m venv .venv_wheel
74
+ . .venv_wheel/bin/activate 2>/dev/null || . .venv_wheel/Scripts/activate 2>/dev/null
75
+ pip install dist/*.whl
76
+ "$CLI_BIN" --version 2>/dev/null || python -m "$PACKAGE_MODULE" --version
77
+
78
+ pipx-uv:
79
+ name: pipx/uv verification (ubuntu)
80
+ runs-on: ubuntu-latest
81
+ steps:
82
+ - uses: actions/checkout@v5
83
+ - uses: actions/setup-python@v6
84
+ with:
85
+ python-version: "3.13"
86
+ - name: Extract project metadata
87
+ shell: python
88
+ run: |
89
+ import os
90
+ import tomllib
91
+ from pathlib import Path
92
+
93
+ data = tomllib.loads(Path('pyproject.toml').read_text('utf-8'))
94
+ project = data['project']['name']
95
+ module = project.replace('-', '_')
96
+ dash = project.replace('_', '-')
97
+ scripts = list(data['project'].get('scripts', {}).keys())
98
+ cli_bin = scripts[0] if scripts else dash
99
+
100
+ with open(os.environ['GITHUB_ENV'], 'a', encoding='utf-8') as env:
101
+ env.write(f"PROJECT_NAME={project}\n")
102
+ env.write(f"PACKAGE_MODULE={module}\n")
103
+ env.write(f"CLI_BIN={cli_bin}\n")
104
+ - name: Build wheel
105
+ run: |
106
+ python -m pip install --upgrade pip build
107
+ python -m build
108
+ - name: pipx install from wheel
109
+ run: |
110
+ python -m pip install pipx
111
+ pipx install dist/*.whl
112
+ "$CLI_BIN" --version 2>/dev/null || python -m "$PACKAGE_MODULE" --version
113
+ - name: Install uv
114
+ uses: astral-sh/setup-uv@v7
115
+ with:
116
+ enable-cache: true
117
+ - name: uv tool install
118
+ run: |
119
+ uv tool install .
120
+ "$CLI_BIN" --version 2>/dev/null || python -m "$PACKAGE_MODULE" --version
121
+
122
+ notebooks:
123
+ name: Execute notebooks (ubuntu, Python 3.13)
124
+ runs-on: ubuntu-latest
125
+ steps:
126
+ - uses: actions/checkout@v5
127
+ - uses: actions/setup-python@v6
128
+ with:
129
+ python-version: "3.13"
130
+ - name: Install notebook runner deps
131
+ run: |
132
+ python -m pip install --upgrade pip
133
+ pip install nbclient nbformat ipykernel jupyter_client
134
+ python -m ipykernel install --user --name python3 --display-name "Python 3"
135
+ - name: Execute Quickstart notebook
136
+ env:
137
+ PIP_DISABLE_PIP_VERSION_CHECK: "1"
138
+ PIP_NO_INPUT: "1"
139
+ run: |
140
+ python - << 'PY'
141
+ from pathlib import Path
142
+
143
+ import nbformat
144
+ from nbclient import NotebookClient
145
+
146
+ nb_path = Path('notebooks/Quickstart.ipynb')
147
+ if not nb_path.exists():
148
+ raise SystemExit(f"Notebook not found: {nb_path}")
149
+
150
+ notebook = nbformat.read(nb_path, as_version=4)
151
+ client = NotebookClient(
152
+ notebook,
153
+ timeout=900,
154
+ kernel_name='python3',
155
+ allow_errors=False,
156
+ )
157
+ client.execute()
158
+
159
+ out_path = Path('notebooks/Quickstart-executed.ipynb')
160
+ nbformat.write(notebook, out_path)
161
+ print(f"Executed notebook written to: {out_path}")
162
+ PY
@@ -23,18 +23,17 @@ jobs:
23
23
 
24
24
  steps:
25
25
  - name: Checkout repository
26
- uses: actions/checkout@v4
26
+ uses: actions/checkout@v5
27
27
 
28
28
  - name: Initialize CodeQL
29
- uses: github/codeql-action/init@v3
29
+ uses: github/codeql-action/init@v4
30
30
  with:
31
31
  languages: ${{ matrix.language }}
32
32
 
33
33
  - name: Autobuild
34
- uses: github/codeql-action/autobuild@v3
34
+ uses: github/codeql-action/autobuild@v4
35
35
 
36
36
  - name: Perform CodeQL Analysis
37
- uses: github/codeql-action/analyze@v3
37
+ uses: github/codeql-action/analyze@v4
38
38
  with:
39
39
  category: "/language:${{ matrix.language }}"
40
-
@@ -14,7 +14,7 @@ jobs:
14
14
  permissions:
15
15
  contents: read
16
16
  steps:
17
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v5
18
18
  - name: Verify PyPI token is set
19
19
  run: |
20
20
  if [ -z "${{ secrets.PYPI_API_TOKEN }}" ]; then
@@ -22,7 +22,7 @@ jobs:
22
22
  echo "See: https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions"
23
23
  exit 1
24
24
  fi
25
- - uses: actions/setup-python@v5
25
+ - uses: actions/setup-python@v6
26
26
  with:
27
27
  python-version: '3.13'
28
28
  - name: Build artifacts
@@ -45,14 +45,13 @@ when writing or refracturing Python scripts, apply those Rules :
45
45
 
46
46
  - `src/lib_cli_exit_tools/`: Python package.
47
47
  - `scripts/`: shared automation (build/test/release) reused from scaffold.
48
- - `packaging/`: Conda, Homebrew, and Nix specs kept in sync via scripts.
49
- - `tests/`: placeholder suite skipping until logging features exist.
48
+ - `tests/`: behaviour/spec integration suite covering CLI, metadata, and automation workflows.
50
49
 
51
50
  ### Versioning & Releases
52
51
 
53
52
  - Single source of truth for the package version is `pyproject.toml` (`[project].version`).
54
- - Runtime code reads metadata via `importlib.metadata`; do not duplicate the version in code files.
55
- - On a version bump, update only `pyproject.toml` and the `CHANGELOG.md` entry; runtime code should continue to read the version via `importlib.metadata` (no separate `__init__conf__` file).
53
+ - Automation rewrites `src/lib_cli_exit_tools/__init__conf__.py` from `pyproject.toml`, so runtime code imports generated constants instead of querying `importlib.metadata`.
54
+ - After updating project metadata (version, summary, URLs, authors) run `make test` (or `python -m scripts.test`) to regenerate the metadata module before committing.
56
55
  - Tag releases `vX.Y.Z` and push tags; CI will build artifacts and publish when configured.
57
56
 
58
57
  ### Common Make Targets (Alphabetical)
@@ -60,7 +59,7 @@ when writing or refracturing Python scripts, apply those Rules :
60
59
 
61
60
  | Target | One-line description |
62
61
  |-------------------|--------------------------------------------------------------------------------|
63
- | `build` | Build wheel/sdist and attempt Conda/Brew/Nix builds (auto-installs tools). |
62
+ | `build` | Build wheel/sdist; validates the Python artifacts locally. |
64
63
  | `bump` | Bump version (VERSION=X.Y.Z or PART=major\|minor\|patch) and update changelog. |
65
64
  | `bump-major` | Increment major version ((X+1).0.0). |
66
65
  | `bump-minor` | Increment minor version (X.Y.Z → X.(Y+1).0). |
@@ -71,7 +70,7 @@ when writing or refracturing Python scripts, apply those Rules :
71
70
  | `install` | Editable install. |
72
71
  | `menu` | Interactive TUI menu (make menu). |
73
72
  | `push` | Commit changes once and push to GitHub (no CI monitoring). |
74
- | `release` | Tag vX.Y.Z, push, sync packaging, run gh release if available. |
73
+ | `release` | Tag vX.Y.Z, push, and run the release helper (PyPI upload when configured). |
75
74
  | `run` | Run module entry (`python -m ... --help`). |
76
75
  | `test` | Lint, format, type-check, run tests with coverage, upload to Codecov. |
77
76
  | `version-current` | Print current version from `pyproject.toml`. |
@@ -0,0 +1,176 @@
1
+ # AGENTS README — Using `lib_cli_exit_tools`
2
+
3
+ This guide targets coding agents (Codex, Claude-CLI, etc.) that need to consume the
4
+ `lib_cli_exit_tools` library while building or refactoring Python CLIs. It focuses
5
+ on runtime usage rather than project maintenance.
6
+
7
+ ## 1. Install
8
+
9
+ ```bash
10
+ pip install lib_cli_exit_tools
11
+ ```
12
+
13
+ If you are operating inside a virtual environment, ensure the environment is
14
+ active before installing so the console scripts (`lib_cli_exit_tools`,
15
+ `cli-exit-tools`, `lib-cli-exit-tools`) land on PATH.
16
+
17
+ ## 2. Minimal Usage
18
+
19
+ Wrap an existing Click command with the bundled orchestration so signals and
20
+ exit codes work consistently:
21
+
22
+ ```python
23
+ from __future__ import annotations
24
+
25
+ import click
26
+ from lib_cli_exit_tools import run_cli
27
+
28
+
29
+ @click.command()
30
+ def hello() -> None:
31
+ click.echo("Hello world")
32
+
33
+
34
+ if __name__ == "__main__":
35
+ raise SystemExit(run_cli(hello))
36
+ ```
37
+
38
+ `run_cli` intercepts `SIGINT`, `SIGTERM`/`SIGBREAK`, and standard exceptions,
39
+ translating them into predictable exit codes for shells and calling scripts.
40
+
41
+ ## 3. Temporary Overrides with `cli_session`
42
+
43
+ Enable traceback output or other configuration for a single invocation:
44
+
45
+ ```python
46
+ from collections.abc import Sequence
47
+ from lib_cli_exit_tools import cli_session
48
+
49
+
50
+ def run_command(argv: Sequence[str] | None = None, *, verbose: bool = False) -> int:
51
+ overrides = {"traceback": verbose}
52
+ with cli_session(overrides=overrides) as execute:
53
+ return execute(hello, argv=argv)
54
+ ```
55
+
56
+ Outside the `with` block, global configuration reverts to its previous state,
57
+ so you can safely toggle verbose diagnostics based on flags or environment.
58
+
59
+ ## 4. Global Configuration Surface
60
+
61
+ The module-level `config` object exposes runtime switches. Mutate them once
62
+ at process startup if you need persistent behaviour changes:
63
+
64
+ ```python
65
+ from lib_cli_exit_tools import config
66
+
67
+ config.traceback = True # always render full tracebacks
68
+ config.exit_code_style = "sysexits" # use BSD sysexits mapping
69
+ config.broken_pipe_exit_code = 0 # treat BrokenPipeError as success
70
+ ```
71
+
72
+ For scoped overrides (tests, nested CLIs), prefer `config_overrides`:
73
+
74
+ ```python
75
+ from lib_cli_exit_tools import config_overrides
76
+
77
+ with config_overrides(traceback=True, traceback_force_color=True):
78
+ run_something()
79
+ # state restored automatically
80
+ ```
81
+
82
+ ## 5. Direct Exit-Code Translation
83
+
84
+ If you catch exceptions manually, reuse the resolver:
85
+
86
+ ```python
87
+ from lib_cli_exit_tools import get_system_exit_code, print_exception_message
88
+
89
+ try:
90
+ risky_operation()
91
+ except Exception as exc:
92
+ print_exception_message()
93
+ raise SystemExit(get_system_exit_code(exc))
94
+ ```
95
+
96
+ ## 6. Custom Signal Handling
97
+
98
+ You can supply your own `SignalSpec` list and installer when calling `run_cli`:
99
+
100
+ ```python
101
+ import signal
102
+ from contextlib import ExitStack
103
+
104
+ from lib_cli_exit_tools import SignalSpec, default_signal_specs, run_cli
105
+
106
+ CUSTOM_SIG = getattr(signal, "SIGUSR1", None)
107
+
108
+
109
+ def custom_specs() -> list[SignalSpec]:
110
+ specs = default_signal_specs()
111
+ if CUSTOM_SIG is not None:
112
+ specs.append(
113
+ SignalSpec(
114
+ signum=CUSTOM_SIG,
115
+ exception=RuntimeError,
116
+ message="USR1 received",
117
+ exit_code=75,
118
+ )
119
+ )
120
+ return specs
121
+
122
+
123
+ def install_specs(specs: list[SignalSpec] | None) -> callable[[], None]:
124
+ stack = ExitStack()
125
+ for spec in specs or []:
126
+ def _handler(signum: int, frame: object | None, *, spec: SignalSpec = spec) -> None:
127
+ raise spec.exception(spec.message)
128
+
129
+ try:
130
+ previous = signal.getsignal(spec.signum)
131
+ signal.signal(spec.signum, _handler)
132
+ except OSError:
133
+ continue
134
+ stack.callback(signal.signal, spec.signum, previous)
135
+ return stack.close
136
+
137
+
138
+ exit_code = run_cli(
139
+ hello,
140
+ signal_specs=custom_specs(),
141
+ signal_installer=install_specs,
142
+ )
143
+ ```
144
+
145
+ The installer must return a zero-argument callable that restores prior handlers.
146
+ `run_cli` invokes it automatically during teardown.
147
+
148
+ ## 7. Testing Your Integration
149
+
150
+ When you wire the library into your project, add behaviour tests that mirror the
151
+ bundled suite:
152
+
153
+ ```bash
154
+ PYTHONPATH=src python -m pytest tests/test_my_cli.py
155
+ ```
156
+
157
+ Focus on asserting exit codes and stderr text under failures or signals.
158
+
159
+ ## 8. Troubleshooting
160
+
161
+ | Symptom | Fix |
162
+ | --- | --- |
163
+ | Tracebacks not showing despite `--traceback` | Ensure you call `cli_session` or set `config.traceback = True` before running the command. |
164
+ | SIGINT doesn’t produce exit code 130 | Verify the command is executed via `run_cli` and no other signal handlers override the one supplied by the library. |
165
+ | Windows CTRL+BREAK ignored | Include `SignalSpec` for `SIGBREAK` or rely on `default_signal_specs()` which already accounts for it when available. |
166
+ | “Unknown configuration fields” error | Check the keyword names passed into `config_overrides`; they must match the dataclass fields (`traceback`, `exit_code_style`, `broken_pipe_exit_code`, `traceback_force_color`). |
167
+
168
+ ## 9. Need a Quick Reference?
169
+
170
+ - **run_cli**: Orchestrates Click commands with signal-aware exit handling.
171
+ - **cli_session**: Context manager wrapping `run_cli` that applies temporary config overrides and returns a callable bound to the standard exception handler.
172
+ - **config/reset_config**: Global defaults; call `reset_config()` in tests to avoid leakage.
173
+ - **get_system_exit_code**: Standalone mapping from `BaseException` to numeric exit codes.
174
+ - **SignalSpec + default_signal_specs**: Build or extend the signal handling roster for `run_cli`.
175
+
176
+ Use these primitives to retrofit deterministic exit behaviour into any Click-based CLI your agent constructs.
@@ -3,6 +3,33 @@
3
3
  All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
4
 
5
5
 
6
+ ## [2.1.0] - 2025-10-13
7
+
8
+ ### Changed
9
+ - `scripts.build` now calls `sync_metadata_module()` before invoking `python -m build`, ensuring wheels and sdists ship freshly generated metadata.
10
+ - Added an explicit CLI test covering the `--version` flag so console-script aliases stay aligned with generated constants.
11
+ - Updated README, INSTALL instructions, and the Quickstart notebook to document `lib-cli-exit-tools` as the canonical console entry point alongside its aliases.
12
+
13
+ ## [2.0.0] - 2025-10-12
14
+
15
+ ### Changed
16
+ - Modernised runtime and automation tooling to rely on native Python 3.13 APIs, including updated signal adapters and simplified exception rendering.
17
+ - Raised development dependency floors (codecov-cli, import-linter, bandit, pip-audit, pyright, pytest, pytest-asyncio, ruff, textual) to the latest stable releases.
18
+ - Refined documentation to reflect the current behaviour-focused test suite and active automation scripts.
19
+ - Brought script modules and metadata fallbacks back into coverage reporting and removed ad-hoc sys.path manipulation from the automation launcher.
20
+ - Adopted PEP 604 union typing throughout the CLI runner and facade helpers to underline the Python 3.13-only baseline and to keep function signatures declarative.
21
+ - Updated packaging metadata to require `build>=1.3.0`, `pytest-cov>=7.0.0`, `twine>=6.2.0`, and `hatchling>=1.27.0`, matching the latest stable releases verified via `pip index`.
22
+ - Restored real signal integration coverage and Hypothesis-backed exit-code property tests to guard regression-prone paths.
23
+
24
+ ### Removed
25
+ - Retired Conda/Homebrew/Nix packaging automation and updated documentation to reflect the PyPI-only distribution path.
26
+ - Dropped legacy ImportError-based script fallbacks now that automation entry points run as a proper package.
27
+
28
+ ### Documentation
29
+ - Highlighted the Python 3.13 baseline, dependency refresh, and updated CI action set in the README.
30
+ - Documented the revived signal integration suite and property-based tests in the system module reference.
31
+
32
+
6
33
  ## [1.6.0] - 2025-10-08
7
34
 
8
35
  ### Added
@@ -21,22 +21,11 @@ Thanks for helping improve lib_cli_exit_tools! This guide keeps changes small, s
21
21
  - Honour the documented system prompts: small functions, type hints throughout, and doctests that explain intent.
22
22
  - No sweeping refactors in unrelated modules; keep pull requests reviewable.
23
23
 
24
- ## 4) Build & Packaging
25
-
26
- ### Packaging Sync (Conda/Brew/Nix)
27
-
28
- - Sync is automatic on common workflows:
29
- - `make test` and `make push` run a sync step that aligns packaging files under `packaging/` with `pyproject.toml`.
30
- - `make bump` also updates packaging files when bumping the version.
31
- - What gets synced from `pyproject.toml`:
32
- - Version and minimum Python (`requires-python`).
33
- - Runtime dependencies (from `[project].dependencies`).
34
- - Conda: rewrites `requirements: run` with `python >=X.Y` plus all deps (exact `==` pins kept; ranges copied as-is).
35
- - Homebrew: ensures a `resource "<dep>"` for each dep; if pinned, uses that version; otherwise pins to the latest PyPI release, updating `url` + `sha256`.
36
- - Nix: updates the package version, `pkgs.pythonXYZPackages`/`pkgs.pythonXYZ` to match the min Python, and `propagatedBuildInputs` to include all deps as `pypkgs.<name>`.
37
- - Network note: Homebrew resource updates fetch PyPI metadata; if offline, those updates may be skipped.
38
- - If packaging drifts, run: `python scripts/bump_version.py --sync-packaging`.
39
- - CI enforces consistency on tags via a `packaging-consistency` job.
24
+ ## 4) Build & Release
25
+
26
+ - `make build` produces the wheel/sdist that CI later uploads to PyPI.
27
+ - `make release` tags the repository and runs the release helper (publishes to PyPI when `PYPI_API_TOKEN` is configured).
28
+ - Third-party packaging targets (Conda, Homebrew, Nix) are no longer maintained.
40
29
 
41
30
  ## 5) Tests & Style
42
31
 
@@ -18,7 +18,7 @@ This guide aggregates everything maintainers need for building, testing, and rel
18
18
  | `bump-major` | Bump major version ((X+1).0.0) |
19
19
  | `clean` | Remove caches, build artifacts, and coverage |
20
20
  | `push` | Commit all changes once and push to GitHub (no CI monitoring) |
21
- | `build` | Build wheel/sdist and attempt conda, brew, and nix builds (auto-installs tools if missing) |
21
+ | `build` | Build wheel/sdist artifacts for PyPI distribution |
22
22
  | `menu` | Interactive TUI to run targets and edit parameters (requires dev dep: textual) |
23
23
 
24
24
  ### Target Parameters (env vars)
@@ -36,7 +36,7 @@ This guide aggregates everything maintainers need for building, testing, and rel
36
36
  - `TEST_VERBOSE=1` — echo each command executed by the test harness
37
37
  - Also respects `CODECOV_TOKEN` when needed for uploads
38
38
  - `run`
39
- - No parameters via `make` (always shows `--help`). For custom args: `python scripts/run_cli.py -- <args>`.
39
+ - No parameters via `make` (always shows `--help`). For custom args: `python -m scripts.run_cli -- <args>`.
40
40
  - `version-current`
41
41
  - No parameters
42
42
  - `bump`
@@ -52,10 +52,10 @@ This guide aggregates everything maintainers need for building, testing, and rel
52
52
  - `push`
53
53
  - `REMOTE=<name>` (default: `origin`) — git remote to push to
54
54
  - `build`
55
- - No parameters via `make`. Advanced: use the script directly, e.g. `python scripts/build.py --no-conda --no-nix`.
55
+ - No parameters via `make`. Advanced: call `python -m scripts.build` directly for custom flows.
56
56
  - `release`
57
57
  - `REMOTE=<name>` (default: `origin`) — git remote to push to
58
- - Advanced (via script): `python scripts/release.py --retries 5 --retry-wait 3.0`
58
+ - Advanced (via script): `python -m scripts.release --retries 5 --retry-wait 3.0`
59
59
 
60
60
  ## Interactive Menu (Textual)
61
61
 
@@ -78,7 +78,7 @@ make menu
78
78
 
79
79
  - `test`: single entry point for local CI — runs ruff lint + format check, pyright, pytest (including doctests) with coverage (enabled by default), and uploads coverage to Codecov if configured (reads `.env`).
80
80
  - Auto‑bootstrap: `make test` will try to install dev tools (`pip install -e .[dev]`) if `ruff`/`pyright`/`pytest` are missing. Set `SKIP_BOOTSTRAP=1` to skip this behavior.
81
- - `build`: convenient builder — creates Python wheel/sdist, then attempts Conda, Homebrew, and Nix builds. It auto‑installs missing tools (Miniforge, Homebrew, Nix) when needed.
81
+ - `build`: creates Python wheel/sdist artifacts that match what CI uploads to PyPI.
82
82
  - `install`/`dev`/`user-install`: common install flows for editable or per‑user installs.
83
83
  - `version-current`: prints current version from `pyproject.toml`.
84
84
  - `bump`: updates `pyproject.toml` version and inserts a new section in `CHANGELOG.md`. Use `VERSION=X.Y.Z make bump` or `make bump-minor`/`bump-major`/`bump-patch`.
@@ -92,8 +92,20 @@ make test # ruff + pyright + pytest + coverage (default ON)
92
92
  SKIP_BOOTSTRAP=1 make test # skip auto-install of dev deps
93
93
  COVERAGE=off make test # disable coverage locally
94
94
  COVERAGE=on make test # force coverage and generate coverage.xml/codecov.xml
95
+ make coverage # python -m coverage run -m pytest -vv (no coverage CLI needed)
95
96
  ```
96
97
 
98
+ The same helper can be run directly without Make:
99
+
100
+ ```bash
101
+ python -m scripts coverage
102
+ ```
103
+
104
+ The coverage helper sets `COVERAGE_NO_SQL=1` before launching pytest so the
105
+ legacy file-backed coverage data store is used instead of SQLite. This sidesteps
106
+ the "database is locked" failures that occur when multiple runs touch the same
107
+ workspace.
108
+
97
109
  The pytest suite uses OS markers (`skipif` guards) to exercise POSIX-, Windows-,
98
110
  and platform-agnostic behaviours. Run `make test` on every platform you ship to
99
111
  keep the signal-handling guarantees honest.
@@ -104,38 +116,18 @@ keep the signal-handling guarantees honest.
104
116
  - For private repos, set `CODECOV_TOKEN` (see `.env.example`) or export it in your shell.
105
117
  - For public repos, a token is typically not required.
106
118
 
107
- ## Packaging Sync (Conda/Brew/Nix)
108
-
109
- - `make test` and `make push` automatically align the packaging skeletons in `packaging/` with the current `pyproject.toml`:
110
- - Conda: updates `{% set version = "X.Y.Z" %}` and both `python >=X.Y` constraints to match `requires-python`.
111
- - Homebrew: updates the source URL tag to `vX.Y.Z` and sets `depends_on "python@X.Y"` to match `requires-python`.
112
- - Nix: updates the package `version`, example `rev = "vX.Y.Z"`, and switches `pkgs.pythonXYZPackages` / `pkgs.pythonXYZ` to match the minimum Python version from `requires-python`.
113
- - Local `make test` runs skip the sync unless you export `ENFORCE_PACKAGING_SYNC=1` (CI enables it automatically).
114
- - To run just the sync without bumping versions: `python scripts/bump_version.py --sync-packaging`.
115
- - On release tags (`v*.*.*`), CI validates that packaging files are consistent with `pyproject.toml` and will fail if they drift.
116
-
117
119
  ## Versioning & Metadata
118
120
 
119
- - Single source of truth for package metadata is `pyproject.toml` (`[project]`).
120
- - The library reads its own installed metadata at runtime via `importlib.metadata` (see `src/lib_cli_exit_tools/__init__conf__.py`).
121
- - Do not duplicate the version in code; bump only `pyproject.toml` and update `CHANGELOG.md`.
122
- - Console script name is discovered from entry points; defaults to `lib_cli_exit_tools`.
123
-
124
- ## Packaging Skeletons
125
-
126
- Starter files for package managers live under `packaging/`:
127
-
128
- - Conda: `packaging/conda/recipe/meta.yaml` (update version + sha256)
129
- - Homebrew: `packaging/brew/Formula/lib-cli-exit-tools.rb` (fill sha256 and vendored resources)
130
- - Nix: `packaging/nix/flake.nix` (use working tree or pin to GitHub rev with sha256)
131
-
132
- These are templates; fill placeholders (e.g., sha256) before publishing. Version and Python constraints are auto-synced from `pyproject.toml` by `make test`/`make push` and during version bumps.
121
+ - `pyproject.toml` (`[project]`) remains the single source of truth for name, version, homepage, and authors.
122
+ - `scripts._utils.sync_metadata_module()` rewrites `src/lib_cli_exit_tools/__init__conf__.py` so runtime code consumes generated constants instead of `importlib.metadata`.
123
+ - After editing project metadata (name, version, description, URLs, authors), run `make test` or `python -m scripts.test` to regenerate the module before committing.
124
+ - Console script name is still discovered from entry points; automation falls back to `lib_cli_exit_tools` when no explicit alias exists.
133
125
 
134
126
  ## CI & Publishing
135
127
 
136
128
  GitHub Actions workflows are included:
137
129
 
138
- - `.github/workflows/ci.yml` — lint/type/test, build wheel/sdist, verify pipx and uv installs, Nix and Conda builds (CI-only; no local install required).
130
+ - `.github/workflows/ci.yml` — lint/type/test, build wheel/sdist, and verify pipx/uv installs.
139
131
  - `.github/workflows/release.yml` — on tags `v*.*.*`, builds artifacts and publishes to PyPI when `PYPI_API_TOKEN` secret is set.
140
132
 
141
133
  To publish a release:
@@ -144,4 +136,4 @@ To publish a release:
144
136
  3. Ensure `PYPI_API_TOKEN` secret is configured in the repo.
145
137
  4. Release workflow uploads wheel/sdist to PyPI.
146
138
 
147
- Conda/Homebrew/Nix: use files in `packaging/` to submit to their ecosystems. CI also attempts builds to validate recipes, but does not publish automatically.
139
+ Third-party packaging targets (Conda, Homebrew, Nix) were removed; distribution now flows solely through PyPI.