launchline 0.2.1.dev0__tar.gz → 0.3.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 (76) hide show
  1. launchline-0.3.0/.github/dependabot.yml +6 -0
  2. launchline-0.3.0/.github/workflows/ci.yml +64 -0
  3. launchline-0.3.0/.github/workflows/release.yml +231 -0
  4. {launchline-0.2.1.dev0 → launchline-0.3.0}/.gitignore +7 -0
  5. {launchline-0.2.1.dev0 → launchline-0.3.0}/.markdownlint.json +1 -0
  6. launchline-0.3.0/.semrelrc +21 -0
  7. {launchline-0.2.1.dev0 → launchline-0.3.0}/CHANGELOG.md +15 -3
  8. {launchline-0.2.1.dev0 → launchline-0.3.0}/PKG-INFO +28 -20
  9. {launchline-0.2.1.dev0 → launchline-0.3.0}/README.md +26 -18
  10. launchline-0.3.0/docs/compatibility.md +31 -0
  11. launchline-0.3.0/docs/releasing.md +40 -0
  12. launchline-0.3.0/docs/validation/baseline.json +31 -0
  13. launchline-0.3.0/docs/validation/benchmark.py +45 -0
  14. launchline-0.3.0/docs/validation/current.json +31 -0
  15. launchline-0.3.0/docs/validation/hosted-ci.md +22 -0
  16. launchline-0.3.0/docs/validation/mutation.json +74 -0
  17. launchline-0.3.0/docs/validation/package-smoke.json +37 -0
  18. launchline-0.3.0/docs/validation/parity.json +77 -0
  19. launchline-0.3.0/docs/validation/report.md +70 -0
  20. launchline-0.3.0/docs/validation/search-comparison.json +152 -0
  21. launchline-0.3.0/docs/validation/semantic-release-dry-run.json +16 -0
  22. launchline-0.3.0/docs/validation/summary.json +69 -0
  23. {launchline-0.2.1.dev0 → launchline-0.3.0}/pyproject.toml +38 -5
  24. launchline-0.3.0/scripts/benchmark_compare.py +64 -0
  25. launchline-0.3.0/scripts/mutation_check.py +188 -0
  26. launchline-0.3.0/scripts/package_smoke.py +63 -0
  27. launchline-0.3.0/scripts/parity_check.py +70 -0
  28. launchline-0.3.0/scripts/prepare_semrel.py +49 -0
  29. launchline-0.3.0/scripts/release_dryrun.py +123 -0
  30. launchline-0.3.0/scripts/release_version.py +28 -0
  31. launchline-0.3.0/scripts/semantic-release-plugins.json +98 -0
  32. launchline-0.3.0/src/launchline/__main__.py +130 -0
  33. {launchline-0.2.1.dev0 → launchline-0.3.0}/src/launchline/cli.py +25 -3
  34. {launchline-0.2.1.dev0 → launchline-0.3.0}/src/launchline/config.py +104 -89
  35. {launchline-0.2.1.dev0 → launchline-0.3.0}/src/launchline/exceptions.py +5 -1
  36. {launchline-0.2.1.dev0 → launchline-0.3.0}/src/launchline/fuzzy.py +22 -18
  37. {launchline-0.2.1.dev0 → launchline-0.3.0}/src/launchline/keys.py +66 -18
  38. {launchline-0.2.1.dev0 → launchline-0.3.0}/src/launchline/runner.py +4 -1
  39. {launchline-0.2.1.dev0 → launchline-0.3.0}/src/launchline/ui.py +37 -10
  40. {launchline-0.2.1.dev0 → launchline-0.3.0}/tests/conftest.py +31 -0
  41. launchline-0.3.0/tests/fixtures/fuzzy.tsv +405 -0
  42. launchline-0.3.0/tests/test_application.py +136 -0
  43. {launchline-0.2.1.dev0 → launchline-0.3.0}/tests/test_config.py +31 -76
  44. launchline-0.3.0/tests/test_contract.py +240 -0
  45. {launchline-0.2.1.dev0 → launchline-0.3.0}/tests/test_cross_platform.py +109 -61
  46. launchline-0.3.0/tests/test_keys_unix.py +153 -0
  47. launchline-0.3.0/tests/test_release_policy.py +109 -0
  48. launchline-0.3.0/tests/test_release_version.py +40 -0
  49. {launchline-0.2.1.dev0 → launchline-0.3.0}/tests/test_runner.py +24 -10
  50. launchline-0.3.0/tests/test_terminal_e2e.py +213 -0
  51. {launchline-0.2.1.dev0 → launchline-0.3.0}/tests/test_ui.py +228 -112
  52. {launchline-0.2.1.dev0 → launchline-0.3.0}/uv.lock +124 -112
  53. launchline-0.2.1.dev0/.generated-go-semantic-release-changelog.md +0 -23
  54. launchline-0.2.1.dev0/.github/workflows/ci.yml +0 -40
  55. launchline-0.2.1.dev0/.github/workflows/release.yml +0 -73
  56. launchline-0.2.1.dev0/.semrel/linux_amd64/changelog-generator-default/1.15.0/changelog-generator-default_v1.15.0_linux_amd64 +0 -0
  57. launchline-0.2.1.dev0/.semrel/linux_amd64/commit-analyzer-default/1.15.0/commit-analyzer-cz_v1.15.0_linux_amd64 +0 -0
  58. launchline-0.2.1.dev0/.semrel/linux_amd64/condition-github/1.11.0/condition-github_v1.11.0_linux_amd64 +0 -0
  59. launchline-0.2.1.dev0/.semrel/linux_amd64/files-updater-npm/1.11.0/files-updater-npm_v1.11.0_linux_amd64 +0 -0
  60. launchline-0.2.1.dev0/.semrel/linux_amd64/provider-github/1.19.0/provider-github_v1.19.0_linux_amd64 +0 -0
  61. launchline-0.2.1.dev0/src/launchline/__main__.py +0 -109
  62. {launchline-0.2.1.dev0 → launchline-0.3.0}/.python-version +0 -0
  63. {launchline-0.2.1.dev0 → launchline-0.3.0}/LICENSE +0 -0
  64. {launchline-0.2.1.dev0 → launchline-0.3.0}/assets/launchline.ico +0 -0
  65. {launchline-0.2.1.dev0 → launchline-0.3.0}/assets/launchline.png +0 -0
  66. {launchline-0.2.1.dev0 → launchline-0.3.0}/assets/launchline.svg +0 -0
  67. {launchline-0.2.1.dev0 → launchline-0.3.0}/docs/images/demo.gif +0 -0
  68. {launchline-0.2.1.dev0 → launchline-0.3.0}/docs/images/hero.png +0 -0
  69. {launchline-0.2.1.dev0 → launchline-0.3.0}/docs/images/navigate.png +0 -0
  70. {launchline-0.2.1.dev0 → launchline-0.3.0}/docs/images/search.png +0 -0
  71. {launchline-0.2.1.dev0 → launchline-0.3.0}/src/launchline/__init__.py +0 -0
  72. {launchline-0.2.1.dev0 → launchline-0.3.0}/src/launchline/assets/launchline.ico +0 -0
  73. {launchline-0.2.1.dev0 → launchline-0.3.0}/tests/__init__.py +0 -0
  74. {launchline-0.2.1.dev0 → launchline-0.3.0}/tests/test_cli.py +0 -0
  75. {launchline-0.2.1.dev0 → launchline-0.3.0}/tools/generate_icon.py +0 -0
  76. {launchline-0.2.1.dev0 → launchline-0.3.0}/tools/generate_screenshots.py +0 -0
@@ -0,0 +1,6 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: github-actions
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
@@ -0,0 +1,64 @@
1
+ name: CI
2
+ 'on':
3
+ workflow_call: null
4
+ workflow_dispatch: null
5
+ push:
6
+ branches:
7
+ - main
8
+ pull_request:
9
+ branches:
10
+ - main
11
+ permissions:
12
+ contents: read
13
+ concurrency:
14
+ group: ci-${{ github.workflow }}-${{ github.ref }}
15
+ cancel-in-progress: true
16
+ jobs:
17
+ check:
18
+ name: Python (${{ matrix.os }}, ${{ matrix.python }})
19
+ runs-on: ${{ matrix.os }}
20
+ timeout-minutes: 15
21
+ strategy:
22
+ fail-fast: false
23
+ matrix:
24
+ os:
25
+ - ubuntu-latest
26
+ - windows-latest
27
+ - macos-latest
28
+ python:
29
+ - '3.12'
30
+ - '3.13'
31
+ - '3.14'
32
+ steps:
33
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
34
+ with:
35
+ persist-credentials: false
36
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
37
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e
38
+ with:
39
+ python-version: ${{ matrix.python }}
40
+ - run: uv sync --locked --group dev
41
+ - run: uv run poe check
42
+ - run: uv build
43
+ - run: uv run python scripts/package_smoke.py
44
+ - run: uv run poe mutation
45
+ if: matrix.os == 'windows-latest'
46
+ - name: Test results
47
+ if: always()
48
+ uses: mikepenz/action-junit-report@6ef3fdcfcd5f0b5d14df3e659ef19b8c6820f93d
49
+ with:
50
+ report_paths: junit.xml
51
+ annotate_only: true
52
+ check_annotations: false
53
+ check_retries: false
54
+ require_tests: true
55
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
56
+ if: always()
57
+ with:
58
+ name: evidence-${{ matrix.os }}-${{ matrix.python }}
59
+ path: 'junit.xml
60
+
61
+ docs/validation/*json
62
+
63
+ '
64
+ if-no-files-found: error
@@ -0,0 +1,231 @@
1
+ name: Release
2
+ 'on':
3
+ push:
4
+ branches:
5
+ - main
6
+ workflow_dispatch:
7
+ inputs:
8
+ force-patch:
9
+ description: Force patch bump when no releasable commits exist
10
+ type: boolean
11
+ default: false
12
+ permissions:
13
+ contents: read
14
+ concurrency:
15
+ group: release
16
+ cancel-in-progress: false
17
+ jobs:
18
+ target:
19
+ if: github.ref == 'refs/heads/main' && github.actor == 'mikejhill' && github.triggering_actor == 'mikejhill'
20
+ runs-on: ubuntu-latest
21
+ timeout-minutes: 20
22
+ outputs:
23
+ version: ${{ steps.semrel.outputs.version }}
24
+ steps:
25
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
26
+ with:
27
+ persist-credentials: false
28
+ fetch-depth: 0
29
+ ref: ${{ github.sha }}
30
+ - name: Download checksum-pinned semantic-release CLI
31
+ shell: bash
32
+ run: "set -euo pipefail\ndest=\"$RUNNER_TEMP/semantic-release-v2.31.0\"\ncurl --fail --silent --show-error --location\
33
+ \ --proto '=https' --tlsv1.2 \\\n https://github.com/go-semantic-release/semantic-release/releases/download/v2.31.0/semantic-release_v2.31.0_linux_amd64\
34
+ \ \\\n --output \"$dest\"\nprintf '%s %s\\n' 'b8f518b1aeb1d1742f4e6e91a179b707cea4309a64fe3854f465bf724f04f6d3'\
35
+ \ \"$dest\" | sha256sum --check --strict\nchmod 0755 \"$dest\"\n"
36
+ - name: Preload verified runtime plugins
37
+ run: python3 scripts/prepare_semrel.py
38
+ - name: Determine version without publishing
39
+ id: semrel
40
+ shell: bash
41
+ env:
42
+ GITHUB_TOKEN: ${{ github.token }}
43
+ FORCE_PATCH: ${{ inputs.force-patch || false }}
44
+ run: 'set -euo pipefail
45
+
46
+ args=(--dry --allow-initial-development-versions --changelog release-changelog.md --allow-no-changes --plugin-resolver-disable-batch-prefetch)
47
+
48
+ if [ "$FORCE_PATCH" = true ]; then args+=(--force-bump-patch-version); fi
49
+
50
+ "$RUNNER_TEMP/semantic-release-v2.31.0" "${args[@]}"
51
+
52
+ python3 scripts/release_version.py
53
+
54
+ '
55
+ name: Target
56
+ ci:
57
+ name: Python (${{ matrix.os }}, ${{ matrix.python }})
58
+ runs-on: ${{ matrix.os }}
59
+ timeout-minutes: 15
60
+ strategy:
61
+ fail-fast: false
62
+ matrix:
63
+ os:
64
+ - ubuntu-latest
65
+ - windows-latest
66
+ - macos-latest
67
+ python:
68
+ - '3.12'
69
+ - '3.13'
70
+ - '3.14'
71
+ steps:
72
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
73
+ with:
74
+ persist-credentials: false
75
+ ref: ${{ github.sha }}
76
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e
77
+ with:
78
+ python-version: ${{ matrix.python }}
79
+ - run: uv sync --locked --group dev
80
+ - run: uv run poe check
81
+ - run: uv build
82
+ - run: uv run python scripts/package_smoke.py
83
+ - run: uv run poe mutation
84
+ if: matrix.os == 'windows-latest'
85
+ - name: Test results
86
+ if: always()
87
+ uses: mikepenz/action-junit-report@6ef3fdcfcd5f0b5d14df3e659ef19b8c6820f93d
88
+ with:
89
+ report_paths: junit.xml
90
+ annotate_only: true
91
+ check_annotations: false
92
+ check_retries: false
93
+ require_tests: true
94
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
95
+ if: always()
96
+ with:
97
+ name: evidence-${{ matrix.os }}-${{ matrix.python }}
98
+ path: 'junit.xml
99
+
100
+ docs/validation/*json
101
+
102
+ '
103
+ if-no-files-found: error
104
+ needs: target
105
+ if: needs.target.outputs.version != ''
106
+ build:
107
+ needs:
108
+ - target
109
+ - ci
110
+ runs-on: ubuntu-latest
111
+ timeout-minutes: 20
112
+ outputs:
113
+ artifact-id: ${{ steps.dist.outputs.artifact-id }}
114
+ steps:
115
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
116
+ with:
117
+ persist-credentials: false
118
+ fetch-depth: 0
119
+ ref: ${{ github.sha }}
120
+ - uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e
121
+ - name: Build tested version without publishing credentials
122
+ run: '[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || exit 1
123
+
124
+ uv sync --locked --group dev
125
+
126
+ uv run poe check
127
+
128
+ python3 - <<''PY''
129
+
130
+ import os,pathlib
131
+
132
+ p=pathlib.Path(''README.md'')
133
+
134
+ p.write_text(p.read_text().replace(''(docs/'', ''(https://raw.githubusercontent.com/''+os.environ[''GITHUB_REPOSITORY'']+''/v''+os.environ[''VERSION'']+''/docs/''))
135
+
136
+ PY
137
+
138
+ uv build
139
+
140
+ uv run python scripts/package_smoke.py
141
+
142
+ printf ''Built from reviewed main commit %s\n'' "$GITHUB_SHA" > release-notes.md
143
+
144
+ '
145
+ env:
146
+ VERSION: ${{ needs.target.outputs.version }}
147
+ SETUPTOOLS_SCM_PRETEND_VERSION: ${{ needs.target.outputs.version }}
148
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9
149
+ id: dist
150
+ with:
151
+ name: trusted-release
152
+ path: 'dist/*
153
+
154
+ release-notes.md'
155
+ if-no-files-found: error
156
+ retention-days: 7
157
+ name: Build
158
+ github-release:
159
+ if: github.ref == 'refs/heads/main' && github.actor == 'mikejhill' && github.triggering_actor == 'mikejhill' && needs.build.result
160
+ == 'success'
161
+ needs:
162
+ - target
163
+ - build
164
+ - attest
165
+ runs-on: ubuntu-latest
166
+ timeout-minutes: 20
167
+ permissions:
168
+ contents: write # Create immutable refs and upload tested release artifacts.
169
+ steps:
170
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
171
+ with:
172
+ artifact-ids: ${{ needs.build.outputs.artifact-id }}
173
+ path: release
174
+ - name: Publish same-run artifacts from trusted merge
175
+ run: "# The approved immutable-v* ruleset must already prohibit tag updates/deletion.\nancestor=$(gh api \"repos/$GITHUB_REPOSITORY/compare/$GITHUB_SHA...main\"\
176
+ \ --jq .merge_base_commit.sha)\ntest \"$ancestor\" = \"$GITHUB_SHA\" || { echo 'Release commit is no longer on main.'\
177
+ \ >&2; exit 1; }\npython3 - <<'PY'\nimport json,os,pathlib,re\nassert re.fullmatch(r'v[0-9]+\\.[0-9]+\\.[0-9]+',os.environ['TAG'])\n\
178
+ assert re.fullmatch(r'[0-9a-f]{40}',os.environ['GITHUB_SHA'])\npathlib.Path('release-ref.json').write_text(json.dumps({'ref':'refs/tags/'+os.environ['TAG'],'sha':os.environ['GITHUB_SHA']}))\n\
179
+ PY\n# Ref creation is atomic: if another process created it first, accept only the same commit.\nif ! gh api --method\
180
+ \ POST \"repos/$GITHUB_REPOSITORY/git/refs\" --input release-ref.json >/dev/null 2>tag-create-error.txt; then\n actual=$(gh\
181
+ \ api \"repos/$GITHUB_REPOSITORY/commits/$TAG\" --jq .sha)\n test \"$actual\" = \"$GITHUB_SHA\" || { echo 'Tag creation\
182
+ \ failed or a conflicting tag exists.' >&2; exit 1; }\nfi\nactual=$(gh api \"repos/$GITHUB_REPOSITORY/commits/$TAG\"\
183
+ \ --jq .sha)\ntest \"$actual\" = \"$GITHUB_SHA\" || { echo 'Tag does not match the trusted merge.' >&2; exit 1; }\n\
184
+ if gh release view \"$TAG\" --repo \"$GITHUB_REPOSITORY\" >/dev/null 2>&1; then\n :\nelse\n gh release create \"\
185
+ $TAG\" --repo \"$GITHUB_REPOSITORY\" --verify-tag --title \"$TAG\" --generate-notes --notes-file release/release-notes.md\n\
186
+ fi\ngh release upload \"$TAG\" release/dist/* --repo \"$GITHUB_REPOSITORY\" --clobber\n"
187
+ env:
188
+ GH_TOKEN: ${{ github.token }}
189
+ TAG: v${{ needs.target.outputs.version }}
190
+ name: Github Release
191
+ pypi:
192
+ if: github.ref == 'refs/heads/main' && github.actor == 'mikejhill' && github.triggering_actor == 'mikejhill' && needs.github-release.result
193
+ == 'success'
194
+ needs:
195
+ - target
196
+ - build
197
+ - github-release
198
+ runs-on: ubuntu-latest
199
+ timeout-minutes: 20
200
+ environment: pypi
201
+ permissions:
202
+ id-token: write # OIDC identity for attestation or trusted PyPI publishing.
203
+ steps:
204
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
205
+ with:
206
+ artifact-ids: ${{ needs.build.outputs.artifact-id }}
207
+ path: release
208
+ - uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086
209
+ with:
210
+ packages-dir: release/dist/
211
+ skip-existing: true
212
+ name: Pypi
213
+ attest:
214
+ name: Attest tested artifacts
215
+ needs: build
216
+ if: github.ref == 'refs/heads/main' && github.actor == 'mikejhill' && github.triggering_actor == 'mikejhill' && needs.build.result
217
+ == 'success'
218
+ runs-on: ubuntu-latest
219
+ timeout-minutes: 10
220
+ permissions:
221
+ contents: read
222
+ id-token: write # OIDC identity for attestation or trusted PyPI publishing.
223
+ attestations: write # Store provenance for these same-run build artifacts.
224
+ steps:
225
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333
226
+ with:
227
+ path: release
228
+ artifact-ids: ${{ needs.build.outputs.artifact-id }}
229
+ - uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6
230
+ with:
231
+ subject-path: release/dist/*
@@ -257,3 +257,10 @@ __marimo__/
257
257
 
258
258
  # hatch-vcs generated version file
259
259
  src/launchline/_version.py
260
+
261
+ junit.xml
262
+ release/
263
+
264
+ .semrel/
265
+ .version
266
+ release-changelog.md
@@ -1,4 +1,5 @@
1
1
  {
2
2
  "MD013": false,
3
+ "MD024": { "siblings_only": true },
3
4
  "MD033": false
4
5
  }
@@ -0,0 +1,21 @@
1
+ {
2
+ "plugins": {
3
+ "provider": {
4
+ "name": "github@1.19.0"
5
+ },
6
+ "commit-analyzer": {
7
+ "name": "default@1.15.0"
8
+ },
9
+ "changelog-generator": {
10
+ "name": "default@1.15.0"
11
+ },
12
+ "ci-condition": {
13
+ "name": "default@1.11.0"
14
+ },
15
+ "files-updater": {
16
+ "names": [
17
+ "npm@1.11.0"
18
+ ]
19
+ }
20
+ }
21
+ }
@@ -7,11 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Removed
11
+
12
+ - Reverted array syntax for `command` config key; `command` is string-only,
13
+ use `args` for arguments
14
+
15
+ ### Changed
16
+
17
+ - Improved test naming to follow `[unit]_[scenario]_[expectedResult]` pattern
18
+ - Added diagnostic failure messages to all test assertions
19
+ - Release workflow now supports `force-patch` input for non-bumping commits
20
+
21
+ ## [0.2.0] - 2026-03-30
22
+
10
23
  ### Added
11
24
 
12
- - `command` config key now accepts a list of strings (e.g.,
13
- `command = ["ssh", "-t", "my server"]`) for easier argument handling,
14
- especially with values containing spaces
15
25
  - `--config-path` CLI flag to print the active configuration file path
16
26
 
17
27
  ### Fixed
@@ -19,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
29
  - PyPI package page now renders README images correctly (paths are rewritten
20
30
  to absolute URLs during the release build)
21
31
  - Release artifacts no longer accidentally include non-distribution files
32
+ - Release workflow now produces correct version numbers (fixed dirty-tree
33
+ version detection via `SETUPTOOLS_SCM_PRETEND_VERSION`)
22
34
 
23
35
  ## [0.1.0] - 2026-03-30
24
36
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: launchline
3
- Version: 0.2.1.dev0
3
+ Version: 0.3.0
4
4
  Summary: A lightweight terminal launcher for interactive CLI tools.
5
5
  Project-URL: Homepage, https://github.com/mikejhill/launchline
6
6
  Project-URL: Repository, https://github.com/mikejhill/launchline
@@ -24,12 +24,14 @@ Description-Content-Type: text/markdown
24
24
 
25
25
  [![CI](https://github.com/mikejhill/launchline/actions/workflows/ci.yml/badge.svg)](https://github.com/mikejhill/launchline/actions/workflows/ci.yml)
26
26
  [![Release](https://github.com/mikejhill/launchline/actions/workflows/release.yml/badge.svg)](https://github.com/mikejhill/launchline/actions/workflows/release.yml)
27
+ [![PyPI](https://img.shields.io/pypi/v/launchline)](https://pypi.org/project/launchline/)
28
+ [![Python](https://img.shields.io/pypi/pyversions/launchline)](https://pypi.org/project/launchline/)
27
29
 
28
30
  A lightweight terminal launcher for interactive CLI tools. Define your frequently
29
31
  used commands in a TOML config file, then pick them from a fuzzy-searchable menu
30
32
  instead of remembering flags and paths.
31
33
 
32
- ![LaunchLine demo](https://raw.githubusercontent.com/mikejhill/launchline/v0.2.0/docs/images/demo.gif)
34
+ ![LaunchLine demo](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/images/demo.gif)
33
35
 
34
36
  ## Why
35
37
 
@@ -50,7 +52,7 @@ instead.
50
52
 
51
53
  | Fuzzy search | Arrow-key navigation |
52
54
  | :-------------------------------: | :-----------------------------------: |
53
- | ![Search](https://raw.githubusercontent.com/mikejhill/launchline/v0.2.0/docs/images/search.png) | ![Navigate](https://raw.githubusercontent.com/mikejhill/launchline/v0.2.0/docs/images/navigate.png) |
55
+ | ![Search](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/images/search.png) | ![Navigate](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/images/navigate.png) |
54
56
 
55
57
  ## Installation
56
58
 
@@ -123,24 +125,21 @@ Config file format is [TOML](https://toml.io). The file has an optional
123
125
 
124
126
  Each `[[entries]]` table defines one launchable tool:
125
127
 
126
- | Key | Type | Required | Default | Description |
127
- | ------------------- | ------------------------- | -------- | ------- | -------------------------------------------------------------- |
128
- | `name` | string | yes | — | Display name shown in the menu |
129
- | `command` | string or list of strings | yes | — | Executable to run (with optional inline arguments) |
130
- | `args` | list of strings | no | `[]` | Additional arguments appended after any inline command args |
131
- | `description` | string | no | `""` | Short description shown next to the name |
132
- | `working_directory` | string | no | — | Working directory for the subprocess |
133
- | `env` | table | no | `{}` | Extra environment variables (`KEY = "value"`) |
128
+ | Key | Type | Required | Default | Description |
129
+ | ------------------- | --------------- | -------- | ------- | -------------------------------------------------------------- |
130
+ | `name` | string | yes | — | Display name shown in the menu |
131
+ | `command` | string | yes | — | Executable to run |
132
+ | `args` | list of strings | no | `[]` | Arguments passed to the command |
133
+ | `description` | string | no | `""` | Short description shown next to the name |
134
+ | `working_directory` | string | no | — | Working directory for the subprocess |
135
+ | `env` | table | no | `{}` | Extra environment variables (`KEY = "value"`) |
134
136
 
135
137
  ### Validation Rules
136
138
 
137
139
  - At least one `[[entries]]` table is required.
138
140
  - Every entry must have both `name` and `command`.
139
- - `command` may be a string (`"pwsh"`) or a list (`["ssh", "-t", "host"]`).
140
- When a list, the first element is the executable and the rest are arguments.
141
141
  - `on_exit` must be `"restart"` or `"exit"`.
142
- - `args` must be a list (not a bare string). These are appended after any
143
- arguments provided inline in a `command` list.
142
+ - `args` must be a list (not a bare string).
144
143
  - `env` must be a TOML table (not a string or list).
145
144
  - If `working_directory` does not exist at load time, it is silently reset to
146
145
  `None` (a warning is logged).
@@ -171,10 +170,6 @@ name = "PowerShell"
171
170
  command = "pwsh"
172
171
  args = ["-NoProfile"]
173
172
  env = { TERM = "xterm-256color" }
174
-
175
- [[entries]]
176
- name = "Remote Server"
177
- command = ["ssh", "-t", "my server", "bash"]
178
173
  ```
179
174
 
180
175
  ## Usage
@@ -220,3 +215,16 @@ uv run mypy src
220
215
  ## License
221
216
 
222
217
  MIT
218
+
219
+ ## See Also
220
+
221
+ - **[LaunchLine (Rust)](https://github.com/mikejhill/launchline-rust)** — a Rust
222
+ implementation with the shared configuration and keyboard contract,
223
+ ships as a single native binary.
224
+
225
+ ## Compatibility and validation
226
+
227
+ Use `launchline --check-config` to validate configuration without opening the menu.
228
+ Set `NO_COLOR` for a selection marker without ANSI color. See the
229
+ [compatibility matrix](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/compatibility.md), [reproducible test and performance
230
+ results](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/validation/report.md), and [release readiness](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/releasing.md).
@@ -2,12 +2,14 @@
2
2
 
3
3
  [![CI](https://github.com/mikejhill/launchline/actions/workflows/ci.yml/badge.svg)](https://github.com/mikejhill/launchline/actions/workflows/ci.yml)
4
4
  [![Release](https://github.com/mikejhill/launchline/actions/workflows/release.yml/badge.svg)](https://github.com/mikejhill/launchline/actions/workflows/release.yml)
5
+ [![PyPI](https://img.shields.io/pypi/v/launchline)](https://pypi.org/project/launchline/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/launchline)](https://pypi.org/project/launchline/)
5
7
 
6
8
  A lightweight terminal launcher for interactive CLI tools. Define your frequently
7
9
  used commands in a TOML config file, then pick them from a fuzzy-searchable menu
8
10
  instead of remembering flags and paths.
9
11
 
10
- ![LaunchLine demo](https://raw.githubusercontent.com/mikejhill/launchline/v0.2.0/docs/images/demo.gif)
12
+ ![LaunchLine demo](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/images/demo.gif)
11
13
 
12
14
  ## Why
13
15
 
@@ -28,7 +30,7 @@ instead.
28
30
 
29
31
  | Fuzzy search | Arrow-key navigation |
30
32
  | :-------------------------------: | :-----------------------------------: |
31
- | ![Search](https://raw.githubusercontent.com/mikejhill/launchline/v0.2.0/docs/images/search.png) | ![Navigate](https://raw.githubusercontent.com/mikejhill/launchline/v0.2.0/docs/images/navigate.png) |
33
+ | ![Search](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/images/search.png) | ![Navigate](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/images/navigate.png) |
32
34
 
33
35
  ## Installation
34
36
 
@@ -101,24 +103,21 @@ Config file format is [TOML](https://toml.io). The file has an optional
101
103
 
102
104
  Each `[[entries]]` table defines one launchable tool:
103
105
 
104
- | Key | Type | Required | Default | Description |
105
- | ------------------- | ------------------------- | -------- | ------- | -------------------------------------------------------------- |
106
- | `name` | string | yes | — | Display name shown in the menu |
107
- | `command` | string or list of strings | yes | — | Executable to run (with optional inline arguments) |
108
- | `args` | list of strings | no | `[]` | Additional arguments appended after any inline command args |
109
- | `description` | string | no | `""` | Short description shown next to the name |
110
- | `working_directory` | string | no | — | Working directory for the subprocess |
111
- | `env` | table | no | `{}` | Extra environment variables (`KEY = "value"`) |
106
+ | Key | Type | Required | Default | Description |
107
+ | ------------------- | --------------- | -------- | ------- | -------------------------------------------------------------- |
108
+ | `name` | string | yes | — | Display name shown in the menu |
109
+ | `command` | string | yes | — | Executable to run |
110
+ | `args` | list of strings | no | `[]` | Arguments passed to the command |
111
+ | `description` | string | no | `""` | Short description shown next to the name |
112
+ | `working_directory` | string | no | — | Working directory for the subprocess |
113
+ | `env` | table | no | `{}` | Extra environment variables (`KEY = "value"`) |
112
114
 
113
115
  ### Validation Rules
114
116
 
115
117
  - At least one `[[entries]]` table is required.
116
118
  - Every entry must have both `name` and `command`.
117
- - `command` may be a string (`"pwsh"`) or a list (`["ssh", "-t", "host"]`).
118
- When a list, the first element is the executable and the rest are arguments.
119
119
  - `on_exit` must be `"restart"` or `"exit"`.
120
- - `args` must be a list (not a bare string). These are appended after any
121
- arguments provided inline in a `command` list.
120
+ - `args` must be a list (not a bare string).
122
121
  - `env` must be a TOML table (not a string or list).
123
122
  - If `working_directory` does not exist at load time, it is silently reset to
124
123
  `None` (a warning is logged).
@@ -149,10 +148,6 @@ name = "PowerShell"
149
148
  command = "pwsh"
150
149
  args = ["-NoProfile"]
151
150
  env = { TERM = "xterm-256color" }
152
-
153
- [[entries]]
154
- name = "Remote Server"
155
- command = ["ssh", "-t", "my server", "bash"]
156
151
  ```
157
152
 
158
153
  ## Usage
@@ -198,3 +193,16 @@ uv run mypy src
198
193
  ## License
199
194
 
200
195
  MIT
196
+
197
+ ## See Also
198
+
199
+ - **[LaunchLine (Rust)](https://github.com/mikejhill/launchline-rust)** — a Rust
200
+ implementation with the shared configuration and keyboard contract,
201
+ ships as a single native binary.
202
+
203
+ ## Compatibility and validation
204
+
205
+ Use `launchline --check-config` to validate configuration without opening the menu.
206
+ Set `NO_COLOR` for a selection marker without ANSI color. See the
207
+ [compatibility matrix](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/compatibility.md), [reproducible test and performance
208
+ results](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/validation/report.md), and [release readiness](https://raw.githubusercontent.com/mikejhill/launchline/v0.3.0/docs/releasing.md).
@@ -0,0 +1,31 @@
1
+ # Shared compatibility contract
2
+
3
+ Baseline: Python `904ace1`, Rust `109a68b`. Modernized together on MHILL-LAPTOP.
4
+ This matrix records the supported contract and evidence, not universal parity.
5
+
6
+ | Surface | Baseline gap | Implemented contract | Evidence |
7
+ | --- | --- | --- | --- |
8
+ | Configuration | Python coerced types; Rust discarded invalid args/env | Strict strings, booleans, string arrays/maps; reject controls/NUL and illegal environment keys | Both unit suites; 15 differential real CLI cases in validation/parity.json |
9
+ | Resolution | Same CLI > LAUNCHLINE_CONFIG > default precedence | Explicit missing paths fail; exclusive default creation; missing/file cwd warns and inherits | Config tests; actual CLI and child processes |
10
+ | Search | Equivalent greedy subsequence scoring | Identical score bonuses and stable ties; monotonic search avoids tail scans | Shared 405-case ASCII/Unicode score corpus; paired benchmarks |
11
+ | Numeric selection | Python non-ASCII digit crash | ASCII digits only, existing prefix and shortcut semantics | Keyboard and mutation tests |
12
+ | Editing | Rust UTF-8 byte cursor could panic | Character boundary editing; Emacs keys, arrows, Escape clear/quit | Rust unit UX; Python decoder/UI tests; real PTY flows |
13
+ | CLI | Rust accepted arbitrary log levels; Python config-path tracebacks | Same five uppercase levels; friendly failures; --check-config validates without opening TUI; non-TTY guard | Actual subprocess and wheel smoke tests |
14
+ | Launch | Rust cloned entire environment; POSIX status representation differed | Literal argv, inherited env plus overlay, cwd, child exit code; interrupted child 130; POSIX signal 128+signal | Actual child probe; Python Linux SIGTERM test; Rust Unix test added but not run locally |
15
+ | Terminal | Windows inherited ignore flag; Unix queued input loss | Enable own Ctrl+C; restore terminal; preserve queued UTF-8 input; stop on EOF/read error | Windows PTY both; Linux PTY Python; faithful OS-boundary simulations |
16
+ | Accessibility | Color-only emphasis | NO_COLOR suppresses SGR with visible selection marker; keyboard navigation and resize | PTY output/assertions both; no screen-reader certification |
17
+ | Distribution | Rust archives lacked icons; wrapper used wrong version metadata | Bundle icons; wrapper uses launchline-rust version, verifies SHA256SUMS before atomic cache install | Built native archive and isolated installed wheel; tamper/interruption tests |
18
+ | Release | Python #1 pending; Rust broad permissions | Preserve #1 pin/generated notes; owner + rerun owner gates, exact SHA, read-only build, isolated publishers, provenance | actionlint, zizmor auditor, mocked actual publisher body, no-remote semantic-release fixture |
19
+
20
+ Unknown TOML keys remain ignored for compatibility. Shell expansion is not supported.
21
+ Portable home expansion is `~`/`~/...`; Python also recognizes OS-supported named-user
22
+ forms. Diagnostic wording, argparse/clap help formatting, platform process lookup,
23
+ batch execution, Unicode database versions, terminal escape timing and font widths
24
+ can differ. Cursor accounting uses code points rather than grapheme/display columns;
25
+ wide/combining glyph layout is not claimed equivalent. Python needs CPython >=3.12;
26
+ Rust is native, with a separate Python >=3.9 download wrapper. Binary and interpreter
27
+ startup/memory costs therefore differ. No maximum-performance claim is made.
28
+
29
+ Executed: Windows x64 both; Ubuntu/WSL Python. macOS, ARM targets, Rust Linux/macOS,
30
+ physical terminal/screen-reader combinations and hosted CI have not been executed
31
+ here. See [validation report](validation/report.md) and [release readiness](releasing.md).
@@ -0,0 +1,40 @@
1
+ # Release readiness
2
+
3
+ Trusted owner merges to main trigger conventional-commit version calculation.
4
+ Read-only jobs test/build the exact merge SHA before separate publishers consume
5
+ same-run artifacts. Both actor and rerun initiator must be mikejhill. Publisher
6
+ jobs do not check out repository code; write/OIDC permissions are job-scoped.
7
+ Artifacts receive provenance. Conflicting tags fail; generated GitHub notes are
8
+ retained. Release publication remains a production action and was not invoked.
9
+
10
+ The go-semantic-release CLI is pinned to 2.31.0 with a verified checksum; exact
11
+ runtime plugin versions and Linux/Windows checksums are in
12
+ scripts/semantic-release-plugins.json. prepare_semrel.py verifies/preloads them.
13
+ release_version.py validates the generated changelog header (the CLI dry-run does
14
+ not produce its version-file output). The disposable fixture script removes token
15
+ variables, has no remote and verifies no tags; its evidence is in validation/.
16
+ It exercises the Git provider, not the production GitHub provider or PyPI upload.
17
+ Publisher policy tests execute the real workflow shell body with an intercepting
18
+ gh shell function; no requests reach GitHub.
19
+
20
+ Current VerdiClip workflow was read as the reference; Python PR #1 at
21
+ 96d72c1e157e57e1b1aa9c5ad1dfbf51aaa606ab was inspected and its CLI pin,
22
+ immutable tag conflict check, owner/rerun gates and generated notes preserved
23
+ selectively. Neither PR nor production settings were merged or modified.
24
+
25
+ ## Remaining setup (owner action required)
26
+
27
+ Read-only inspection found Python's existing pypi environment still has a required
28
+ reviewer and no deployment branch policy. Rust's pypi environment returned 404
29
+ (absent). PyPI trusted-publisher identities could not be verified. Thus production
30
+ approval-free PyPI delivery is NOT ready. Before rollout, establish/verify main-only
31
+ environment restrictions and the PyPI identity for the corresponding repository,
32
+ release.yml and pypi environment. Any removal of the existing reviewer requires
33
+ separate authorization; this task deliberately leaves the gate unchanged. Verify
34
+ version-tag protection and GitHub attestation availability as well. No credentials
35
+ were created and no repository security setting was changed.
36
+
37
+ To reproduce a safe version fixture, explicitly set SEMREL_BIN to the verified CLI
38
+ and run scripts/release_dryrun.py through the repository's uv environment. Do not
39
+ substitute a production provider or run publication commands as a dry run. A failed
40
+ production upload should be retried on its original run; never move an existing tag.