devstuff 2.2.1__tar.gz → 2.4.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.
- devstuff-2.4.0/.github/workflows/bump.yml +180 -0
- devstuff-2.4.0/.github/workflows/publish.yml +96 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/.github/workflows/test-installs.yml +7 -2
- {devstuff-2.2.1 → devstuff-2.4.0}/CHANGELOG.md +12 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/CLAUDE.md +45 -1
- {devstuff-2.2.1 → devstuff-2.4.0}/PKG-INFO +67 -6
- {devstuff-2.2.1 → devstuff-2.4.0}/README.md +65 -4
- devstuff-2.4.0/docs/specs/uv-backbone/development-plan.md +131 -0
- devstuff-2.4.0/docs/specs/uv-backbone/specifications.md +227 -0
- devstuff-2.4.0/docs/specs/uv-backbone/stack-decisions.md +119 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/pyproject.toml +1 -1
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/catalog.py +22 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/add_cmd.py +14 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/functions.yaml +98 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/generic.py +59 -4
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/tools.yaml +58 -8
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/integration/test_tools.py +19 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_catalog.py +108 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_generic.py +164 -1
- {devstuff-2.2.1 → devstuff-2.4.0}/uv.lock +1 -1
- devstuff-2.2.1/.github/workflows/bump.yml +0 -95
- devstuff-2.2.1/.github/workflows/publish.yml +0 -44
- {devstuff-2.2.1 → devstuff-2.4.0}/.claude/skills/run-stuff/SKILL.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/.claude/skills/run-stuff/smoke.sh +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/.github/workflows/ci.yml +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/.gitignore +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/dev/Dockerfile +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/dev/Dockerfile.ci +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/dev/Makefile +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/dev/docker-compose.yml +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/devstuff +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/README.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/agent/development-plan.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/agent/specifications.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/agent/stack-decisions.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/ansible-config/development-plan.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/ansible-config/specifications.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/ansible-config/stack-decisions.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/bat-config/development-plan.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/bat-config/specifications.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/bat-config/stack-decisions.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/commitizen-config/development-plan.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/commitizen-config/specifications.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/commitizen-config/stack-decisions.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/docker-config/development-plan.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/docker-config/specifications.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/docker-config/stack-decisions.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/lazygit-config/development-plan.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/lazygit-config/specifications.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/lazygit-config/stack-decisions.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/precommit-config/development-plan.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/precommit-config/specifications.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/precommit-config/stack-decisions.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/starship-config/development-plan.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/starship-config/specifications.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/starship-config/stack-decisions.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/verbose-mode/development-plan.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/verbose-mode/specifications.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/docs/specs/verbose-mode/stack-decisions.md +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/install.sh +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/__init__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/__main__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/__init__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/approval.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/bridges.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/catalog.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/completion.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/config.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/loop.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/ollama.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/preflight.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/primitives.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/registry.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/sandbox.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/session.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/transcript.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent/wizard.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent_tools.schema.json +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/agent_tools.yaml +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/base.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/cli.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/__init__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/agent_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/catalog_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/configure_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/delete_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/docs_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/doctor_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/functions_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/help_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/install_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/list_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/remove_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/run_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/skills_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/commands/update_cmd.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/__init__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/ansible/__init__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/ansible/detect.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/ansible/model.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/ansible/render.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/ansible/validate.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/ansible/wizard.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/bat/__init__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/bat/detect.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/bat/model.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/bat/preview.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/bat/render.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/bat/wizard.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/commitizen/__init__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/commitizen/detect.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/commitizen/model.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/commitizen/render.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/commitizen/validate.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/commitizen/wizard.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/docker/__init__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/docker/detect.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/docker/model.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/docker/render.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/docker/validate.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/docker/wizard.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/lazygit/__init__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/lazygit/detect.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/lazygit/model.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/lazygit/render.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/lazygit/validate.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/lazygit/wizard.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/precommit/__init__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/precommit/detect.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/precommit/model.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/precommit/render.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/precommit/validate.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/precommit/wizard.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/starship/__init__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/starship/fonts.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/starship/model.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/starship/preview.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/starship/render.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/configure/starship/wizard.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/doctor.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/function_runner.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/functions.schema.json +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/functions_catalog.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/functions_registry.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/registry.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/ui.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/src/dev_setup/verbose.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/__init__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/integration/__init__.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/integration/conftest.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/integration/test_agent_smoke.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/integration/test_agent_tui.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/integration/test_commitizen_config.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_agent.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_agent_completion.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_agent_loop.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_agent_sandbox.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_agent_tools.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_agent_wizard.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_configure_ansible.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_configure_bat.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_configure_commitizen.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_configure_docker.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_configure_lazygit.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_configure_precommit.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_configure_starship.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_doctor.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_functions.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_registry.py +0 -0
- {devstuff-2.2.1 → devstuff-2.4.0}/tests/test_verbose.py +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
name: Bump version
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [closed]
|
|
6
|
+
branches:
|
|
7
|
+
- master
|
|
8
|
+
|
|
9
|
+
# Serialize bump runs against master regardless of which PR triggered them.
|
|
10
|
+
concurrency:
|
|
11
|
+
group: bump-master
|
|
12
|
+
cancel-in-progress: false
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
bump:
|
|
16
|
+
name: cz bump
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
# Only run for PRs that were actually merged, not closed unmerged.
|
|
19
|
+
# A raw `git push` — which is how this job publishes its own commit —
|
|
20
|
+
# never fires pull_request events, so there's no infinite-loop risk
|
|
21
|
+
# from this job triggering itself (unlike the old push-to-master
|
|
22
|
+
# trigger, which needed an explicit "bump:" message guard for that).
|
|
23
|
+
if: github.event.pull_request.merged == true
|
|
24
|
+
permissions:
|
|
25
|
+
contents: write
|
|
26
|
+
env:
|
|
27
|
+
# secrets can't be referenced directly in step `if:` conditions, so
|
|
28
|
+
# surface presence via an env var instead.
|
|
29
|
+
HAS_GH_PAT: ${{ secrets.GH_PAT != '' }}
|
|
30
|
+
|
|
31
|
+
steps:
|
|
32
|
+
- name: Require GH_PAT
|
|
33
|
+
# GH_PAT (a Personal Access Token with repo write scope) is required
|
|
34
|
+
# so that the tag push re-triggers publish.yml — GitHub Actions does
|
|
35
|
+
# not re-process events from pushes made with GITHUB_TOKEN. Fail loudly
|
|
36
|
+
# instead of silently pushing a tag that will never publish.
|
|
37
|
+
if: env.HAS_GH_PAT != 'true'
|
|
38
|
+
run: |
|
|
39
|
+
echo "::error::GH_PAT secret is not set. It is required so the tag push triggers publish.yml (GITHUB_TOKEN pushes are not re-processed by Actions)."
|
|
40
|
+
exit 1
|
|
41
|
+
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
with:
|
|
44
|
+
ref: master # the merge already happened server-side by the time this runs
|
|
45
|
+
fetch-depth: 0 # commitizen needs full history to determine bump type
|
|
46
|
+
token: ${{ secrets.GH_PAT }}
|
|
47
|
+
|
|
48
|
+
- name: Install uv
|
|
49
|
+
uses: astral-sh/setup-uv@v5
|
|
50
|
+
|
|
51
|
+
- name: Configure git
|
|
52
|
+
run: |
|
|
53
|
+
git config user.name "github-actions[bot]"
|
|
54
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
55
|
+
|
|
56
|
+
- name: Run cz bump
|
|
57
|
+
id: bump
|
|
58
|
+
run: |
|
|
59
|
+
if uv run --group dev cz bump --yes; then
|
|
60
|
+
echo "bumped=true" >> "$GITHUB_OUTPUT"
|
|
61
|
+
else
|
|
62
|
+
EXIT=$?
|
|
63
|
+
if [ "$EXIT" -eq 21 ]; then
|
|
64
|
+
echo "No conventional commits found since last tag — nothing to bump."
|
|
65
|
+
echo "bumped=false" >> "$GITHUB_OUTPUT"
|
|
66
|
+
else
|
|
67
|
+
exit "$EXIT"
|
|
68
|
+
fi
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# Build-only PRs: `cz bump` exits 21 because `build` is deliberately not
|
|
72
|
+
# in bump_map (we don't want it consuming a PATCH slot from the main
|
|
73
|
+
# release line). Instead, ship a PEP 440 post-release — vX.Y.Z.postN —
|
|
74
|
+
# which is publishable to PyPI, sorts after vX.Y.Z but before the next
|
|
75
|
+
# vX.(Y+1).0, and is still recognised by `cz bump` as the current
|
|
76
|
+
# version tag (tag_format is `v$version` and `X.Y.Z.postN` is valid
|
|
77
|
+
# PEP 440), so the next regular bump stays coherent. A PR mixing `build`
|
|
78
|
+
# with feat/fix/etc. is handled by the `cz bump` path above — the build
|
|
79
|
+
# commits just fold into that release's changelog — so this step only
|
|
80
|
+
# fires when there was nothing else to release.
|
|
81
|
+
- name: Build-patch post-release
|
|
82
|
+
id: buildpatch
|
|
83
|
+
if: steps.bump.outputs.bumped == 'false'
|
|
84
|
+
run: |
|
|
85
|
+
set -euo pipefail
|
|
86
|
+
|
|
87
|
+
last_tag="$(git describe --tags --abbrev=0)"
|
|
88
|
+
commits="$(git log "${last_tag}..HEAD" --pretty=format:%s)"
|
|
89
|
+
|
|
90
|
+
# No build commits -> genuinely nothing to release.
|
|
91
|
+
if ! printf '%s\n' "$commits" | grep -qE '^build(\(.+\))?!?:'; then
|
|
92
|
+
echo "No build commits since ${last_tag} — nothing to release."
|
|
93
|
+
echo "buildpatch=false" >> "$GITHUB_OUTPUT"
|
|
94
|
+
exit 0
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# Safety net: if a higher-precedence release type slipped through,
|
|
98
|
+
# bail rather than ship a competing post-release. (cz bump exiting 21
|
|
99
|
+
# already implies this, but guard against future bump_map changes.)
|
|
100
|
+
if printf '%s\n' "$commits" | grep -qE '^(feat|fix|refactor|perf|deps)(\(.+\))?!?:'; then
|
|
101
|
+
echo "Higher-precedence release commits present alongside build — deferring to the main release track."
|
|
102
|
+
echo "buildpatch=false" >> "$GITHUB_OUTPUT"
|
|
103
|
+
exit 0
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# Current version -> release segment (strip any existing post/dev).
|
|
107
|
+
current_version="$(grep -E '^version = ' pyproject.toml | head -1 | sed -E 's/^version = "(.*)"$/\1/')"
|
|
108
|
+
base="$(printf '%s' "$current_version" | sed -E 's/(\.post[0-9]+|\.dev[0-9]+).*$//')"
|
|
109
|
+
|
|
110
|
+
# Next post number for this base (vX.Y.Z.post1, .post2, ...).
|
|
111
|
+
prev_n="$(git tag --list "v${base}.post*" | sed -E 's/^v.*\.post([0-9]+)$/\1/' | sort -n | tail -1 || true)"
|
|
112
|
+
next_n=$(( ${prev_n:-0} + 1 ))
|
|
113
|
+
new_version="${base}.post${next_n}"
|
|
114
|
+
new_tag="v${new_version}"
|
|
115
|
+
|
|
116
|
+
echo "Bumping build patch: ${current_version} -> ${new_version} (${new_tag})"
|
|
117
|
+
|
|
118
|
+
# Bump version in pyproject.toml.
|
|
119
|
+
sed -i -E "s/^version = \".*\"/version = \"${new_version}\"/" pyproject.toml
|
|
120
|
+
|
|
121
|
+
# Prepend a Build section to the changelog (matches cz's heading style).
|
|
122
|
+
date="$(date +%Y-%m-%d)"
|
|
123
|
+
build_entries="$(
|
|
124
|
+
printf '%s\n' "$commits" | grep -E '^build(\(.+\))?!?:' | while IFS= read -r subject; do
|
|
125
|
+
msg="$(printf '%s' "$subject" | sed -E 's/^build(\([^)]+\))?(!)?: //')"
|
|
126
|
+
scope="$(printf '%s' "$subject" | sed -nE 's/^build\(([^)]+)\).*/\1/p')"
|
|
127
|
+
if [ -n "$scope" ]; then
|
|
128
|
+
printf -- '- **%s**: %s\n' "$scope" "$msg"
|
|
129
|
+
else
|
|
130
|
+
printf -- '- %s\n' "$msg"
|
|
131
|
+
fi
|
|
132
|
+
done
|
|
133
|
+
)"
|
|
134
|
+
{
|
|
135
|
+
printf '## %s (%s)\n\n### Build\n\n%s\n' "$new_tag" "$date" "$build_entries"
|
|
136
|
+
cat CHANGELOG.md
|
|
137
|
+
} > CHANGELOG.md.new
|
|
138
|
+
mv CHANGELOG.md.new CHANGELOG.md
|
|
139
|
+
|
|
140
|
+
# Sync the lockfile against the new version before committing so the
|
|
141
|
+
# tag points at a single self-contained commit (no amend/retag needed).
|
|
142
|
+
uv lock
|
|
143
|
+
git add pyproject.toml CHANGELOG.md uv.lock
|
|
144
|
+
git commit -m "bump: version ${current_version} → ${new_version} (build patch)"
|
|
145
|
+
git tag -a "$new_tag" -m "$new_tag" HEAD
|
|
146
|
+
|
|
147
|
+
git push --follow-tags
|
|
148
|
+
if ! git ls-remote --exit-code --tags origin "refs/tags/${new_tag}" >/dev/null; then
|
|
149
|
+
echo "::error::${new_tag} was not found on origin after push --follow-tags (publish.yml build_patch job will not trigger)."
|
|
150
|
+
exit 1
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
echo "buildpatch=true" >> "$GITHUB_OUTPUT"
|
|
154
|
+
echo "new_tag=${new_tag}" >> "$GITHUB_OUTPUT"
|
|
155
|
+
|
|
156
|
+
- name: Sync lockfile
|
|
157
|
+
if: steps.bump.outputs.bumped == 'true'
|
|
158
|
+
run: |
|
|
159
|
+
uv lock
|
|
160
|
+
git add uv.lock
|
|
161
|
+
if ! git diff --staged --quiet; then
|
|
162
|
+
# cz bump already tagged the pre-lock commit; amending moves HEAD
|
|
163
|
+
# to a new SHA, so the tag must be recreated to point at it.
|
|
164
|
+
# Must stay annotated (-a) — `git push --follow-tags` only
|
|
165
|
+
# pushes annotated tags, so a lightweight retag here would
|
|
166
|
+
# silently never reach the remote.
|
|
167
|
+
tag="$(git describe --tags --exact-match HEAD)"
|
|
168
|
+
git commit --amend --no-edit
|
|
169
|
+
git tag -f -a "$tag" -m "$tag" HEAD
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
- name: Push commit and tag
|
|
173
|
+
if: steps.bump.outputs.bumped == 'true'
|
|
174
|
+
run: |
|
|
175
|
+
git push --follow-tags
|
|
176
|
+
tag="$(git describe --tags --exact-match HEAD)"
|
|
177
|
+
if ! git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null; then
|
|
178
|
+
echo "::error::${tag} was not found on origin after push --follow-tags (publish.yml will not trigger)."
|
|
179
|
+
exit 1
|
|
180
|
+
fi
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# A `v*` tag push triggers this workflow. Two release tracks share that
|
|
4
|
+
# prefix, so they're routed by job-level `if` conditions on the tag name
|
|
5
|
+
# rather than by disjoint trigger globs (GitHub Actions globs can't negate,
|
|
6
|
+
# so `v*` would match both and a glob-only split would double-publish):
|
|
7
|
+
#
|
|
8
|
+
# - Regular releases vX.Y.Z -> build / publish
|
|
9
|
+
# - Build-patch post vX.Y.Z.postN -> build_patch / publish_build_patch
|
|
10
|
+
#
|
|
11
|
+
# The post-release track exists so a PR that contains only `build`-type
|
|
12
|
+
# commits still ships a publishable, PEP 440-distinct version. bump.yml
|
|
13
|
+
# decides which tag to create; this file only reacts to whichever one lands.
|
|
14
|
+
on:
|
|
15
|
+
push:
|
|
16
|
+
tags:
|
|
17
|
+
- "v*"
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
build:
|
|
21
|
+
name: Build distribution
|
|
22
|
+
if: ${{ !contains(github.ref, '.post') }}
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
|
|
27
|
+
- name: Install uv
|
|
28
|
+
uses: astral-sh/setup-uv@v5
|
|
29
|
+
|
|
30
|
+
- name: Build wheel and source distribution
|
|
31
|
+
run: uv build
|
|
32
|
+
|
|
33
|
+
- name: Upload dist artifacts
|
|
34
|
+
uses: actions/upload-artifact@v4
|
|
35
|
+
with:
|
|
36
|
+
name: dist
|
|
37
|
+
path: dist/
|
|
38
|
+
|
|
39
|
+
publish:
|
|
40
|
+
name: Publish to PyPI
|
|
41
|
+
if: ${{ !contains(github.ref, '.post') }}
|
|
42
|
+
needs: build
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
environment:
|
|
45
|
+
name: pypi
|
|
46
|
+
url: https://pypi.org/p/devstuff
|
|
47
|
+
permissions:
|
|
48
|
+
id-token: write # required for OIDC trusted publishing
|
|
49
|
+
steps:
|
|
50
|
+
- name: Download dist artifacts
|
|
51
|
+
uses: actions/download-artifact@v4
|
|
52
|
+
with:
|
|
53
|
+
name: dist
|
|
54
|
+
path: dist/
|
|
55
|
+
|
|
56
|
+
- name: Publish to PyPI
|
|
57
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
58
|
+
|
|
59
|
+
build_patch:
|
|
60
|
+
name: Build build-patch distribution
|
|
61
|
+
if: ${{ contains(github.ref, '.post') }}
|
|
62
|
+
runs-on: ubuntu-latest
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@v4
|
|
65
|
+
|
|
66
|
+
- name: Install uv
|
|
67
|
+
uses: astral-sh/setup-uv@v5
|
|
68
|
+
|
|
69
|
+
- name: Build wheel and source distribution
|
|
70
|
+
run: uv build
|
|
71
|
+
|
|
72
|
+
- name: Upload dist artifacts
|
|
73
|
+
uses: actions/upload-artifact@v4
|
|
74
|
+
with:
|
|
75
|
+
name: dist-build-patch
|
|
76
|
+
path: dist/
|
|
77
|
+
|
|
78
|
+
publish_build_patch:
|
|
79
|
+
name: Publish build-patch to PyPI
|
|
80
|
+
if: ${{ contains(github.ref, '.post') }}
|
|
81
|
+
needs: build_patch
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
environment:
|
|
84
|
+
name: pypi
|
|
85
|
+
url: https://pypi.org/p/devstuff
|
|
86
|
+
permissions:
|
|
87
|
+
id-token: write # required for OIDC trusted publishing
|
|
88
|
+
steps:
|
|
89
|
+
- name: Download dist artifacts
|
|
90
|
+
uses: actions/download-artifact@v4
|
|
91
|
+
with:
|
|
92
|
+
name: dist-build-patch
|
|
93
|
+
path: dist/
|
|
94
|
+
|
|
95
|
+
- name: Publish to PyPI
|
|
96
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -51,27 +51,32 @@ jobs:
|
|
|
51
51
|
fail-fast: false # let all tools run even when some fail
|
|
52
52
|
matrix:
|
|
53
53
|
tool:
|
|
54
|
+
- ansible # uvx tool, requires uv
|
|
55
|
+
- ansible-vault # shares ansible's tool environment
|
|
54
56
|
- aws
|
|
57
|
+
- bat
|
|
55
58
|
- commitizen # uvx tool, requires uv
|
|
56
|
-
- eza
|
|
57
59
|
- gh
|
|
58
60
|
- git-lfs
|
|
59
61
|
- go
|
|
62
|
+
- homebrew # slow: builds from source
|
|
60
63
|
- htop
|
|
61
64
|
- ipython # uvx tool, requires uv
|
|
62
65
|
- java
|
|
63
66
|
- lazygit
|
|
67
|
+
- llm-checker # npm tool, installs nvm first via `requires`
|
|
68
|
+
- lmstudio
|
|
64
69
|
- mkcert
|
|
65
70
|
- nerd-font
|
|
66
71
|
- nvm
|
|
67
72
|
- php
|
|
68
73
|
- pi # installs nvm first via its declared `requires`
|
|
69
74
|
- pre-commit # uvx tool, requires uv
|
|
75
|
+
- python # uv-managed CPython, requires uv
|
|
70
76
|
- ruby # compiles from source — this job takes ~15 minutes
|
|
71
77
|
- saml2aws
|
|
72
78
|
- starship
|
|
73
79
|
- uv
|
|
74
|
-
- whichllm # uvx tool, requires uv
|
|
75
80
|
- yq
|
|
76
81
|
|
|
77
82
|
steps:
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## v2.4.0 (2026-08-20)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- **ci**: publish build-patch updates as PEP 440 post-releases
|
|
6
|
+
|
|
7
|
+
## v2.3.0 (2026-08-13)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- make uv the install backbone, and fix ansible (#42)
|
|
12
|
+
|
|
1
13
|
## v2.2.1 (2026-08-05)
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
|
@@ -83,6 +83,36 @@ pattern for upgrading an already-installed tool (latest or a pinned version); fo
|
|
|
83
83
|
`bash` types "update" is a full reinstall, since there's no narrower mechanism, so the command
|
|
84
84
|
layer confirms before re-running it.
|
|
85
85
|
|
|
86
|
+
**uv is the backbone for Python-packaged tools** (spec in `docs/specs/uv-backbone/`). The
|
|
87
|
+
`uvx`/`pip` types accept three optional fields beyond `pip_name` — `uv_with`,
|
|
88
|
+
`uv_executables_from`, `uv_python` — assembled into argv by `_uv_install_flags` in `generic.py`
|
|
89
|
+
and rejected by `validate_catalog()` on every other type. Three things about them are
|
|
90
|
+
load-bearing:
|
|
91
|
+
- **`uv tool install` only exposes console scripts of the *requested* package.** The `ansible`
|
|
92
|
+
distribution is a collections bundle declaring none of its own, so `pip_name: ansible` alone
|
|
93
|
+
installs one unusable `ansible-community` binary and reports success. `uv_executables_from:
|
|
94
|
+
[ansible-core]` is what produces the eleven real ones. Don't "simplify" it to
|
|
95
|
+
`pip_name: ansible-core` — that silently drops the bundled collections.
|
|
96
|
+
- **`_update_uvx` never uses `uv tool upgrade`** — both paths are `uv tool install --force`
|
|
97
|
+
(`<pkg>==<ver>` pinned, `<pkg>@latest` otherwise). `uv tool upgrade` can't take a requirement
|
|
98
|
+
*and* is a no-op on an already-pinned tool, so the obvious pairing makes a pin a one-way door.
|
|
99
|
+
`--force` writes a fresh receipt, so the `uv_*` flags are re-passed on every update; that is
|
|
100
|
+
deliberate, since it also means a newly added `uv_with` applies on update, not just reinstall.
|
|
101
|
+
Anything printed telling a user how to escape a pin must be measured — two plausible remedies
|
|
102
|
+
("re-run without `--version`", "run `devstuff install`") are both false, the second because
|
|
103
|
+
`install_cmd` returns early on `is_installed()`.
|
|
104
|
+
- **The `python` entry provisions CPython through uv** (`uv python install --default`). Its
|
|
105
|
+
`check_cmd` is the delicate part: every host has a `python3`, so the check requires the default
|
|
106
|
+
shim to *resolve inside* `uv python dir`, uses `test -x` so a dangling shim from a removed
|
|
107
|
+
interpreter reads as absent, and guards `test -n "$d"` because an empty `uv python dir` would
|
|
108
|
+
collapse the `case` pattern to `/*` and match everything. Removal uninstalls only the version
|
|
109
|
+
the default shim points at — `uv python uninstall --all` would also destroy managed interpreters
|
|
110
|
+
that existing `uv tool` environments were built against.
|
|
111
|
+
- **`_remove_uvx` honours an explicit `remove_script`**, mirroring `_remove_apt`. Two entries can
|
|
112
|
+
share one `pip_name` (`ansible-vault` shares `ansible`'s), and there `uv tool uninstall` would
|
|
113
|
+
tear out the shared environment. Extras need no field: `pip_name` reaches `subprocess` as one
|
|
114
|
+
argv element, never a shell, so `pip_name: "ansible-lint[lock]"` already works.
|
|
115
|
+
|
|
86
116
|
**Verbosity** (`verbose.py`, spec in `docs/specs/verbose-mode/`): one process-wide level —
|
|
87
117
|
`0` / `-v` / `-vv` — set by a Click callback and read by the subprocess helpers, never threaded
|
|
88
118
|
through call signatures. Three things about it are load-bearing:
|
|
@@ -114,7 +144,15 @@ Add an entry to `src/dev_setup/tools.yaml` using an existing `type` (`npm`, `pip
|
|
|
114
144
|
and per-type examples. Then:
|
|
115
145
|
- Add the key to `.github/workflows/test-installs.yml`'s matrix (or to `_SKIP` in
|
|
116
146
|
`tests/integration/test_tools.py` with a reason, if it can't run in CI).
|
|
117
|
-
|
|
147
|
+
`test_ci_matrix_covers_every_builtin_tool` enforces this, in both directions — a stale matrix
|
|
148
|
+
entry names a pytest node id that matches nothing, so the job exits 4 and the workflow files a
|
|
149
|
+
GitHub issue every week. That went unnoticed for `eza`/`whichllm` from July until the test
|
|
150
|
+
existed, which is why it is a test and not a line in this file.
|
|
151
|
+
- Add it to the relevant table in README.md ("Built-in packages"). Not enforced by a test —
|
|
152
|
+
the tables are prose-formatted and grouped loosely, so check by hand.
|
|
153
|
+
- If it needs an executable that belongs to a *dependency* rather than the package itself, add
|
|
154
|
+
it to `_EXTRA_EXECUTABLES` in `tests/integration/test_tools.py`; `is_installed()` alone would
|
|
155
|
+
pass on a half-installed tool (see the `uvx` notes above).
|
|
118
156
|
- No Python code changes needed — `GenericTool` already knows how to run every existing type.
|
|
119
157
|
|
|
120
158
|
## Adding a new tool *type* (e.g. a `composer`/PHP-package type)
|
|
@@ -194,6 +232,12 @@ through its exit code — only whether it ran. A "found nothing" answer should t
|
|
|
194
232
|
say so, or the user gets a red "command failed" banner under a correct result; keep non-zero for
|
|
195
233
|
"could not perform the lookup" (see `whats-on-port`'s comment on this).
|
|
196
234
|
|
|
235
|
+
The way this rule actually gets broken is not a stray `exit 1` — it is **a bare `[ -n "$x" ] &&
|
|
236
|
+
echo …` as the last statement of a branch**, which silently becomes the script's exit status when
|
|
237
|
+
the test is false. `which-ansible` shipped that bug in its first draft: "no global ansible found",
|
|
238
|
+
a correct answer, exited 1. Use a full `if`, and end such scripts with an explicit `exit 0`. It is
|
|
239
|
+
only catchable by *running* every branch, not by reading them.
|
|
240
|
+
|
|
197
241
|
Not yet built: an `add` wizard and `catalog import`/`export` for functions, analogous to the
|
|
198
242
|
ones tools already have.
|
|
199
243
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: devstuff
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Development environment setup CLI for Linux
|
|
5
5
|
Project-URL: Repository, https://github.com/thesawdawg/dev-setup-py
|
|
6
6
|
Author-email: Sawyer <sawyerksu@gmail.com>
|
|
@@ -287,7 +287,7 @@ How "update" is performed depends on the package's install `type`:
|
|
|
287
287
|
| Type | Latest | Specific version |
|
|
288
288
|
|------|--------|-------------------|
|
|
289
289
|
| `npm` | `npm install -g <pkg>@latest` | `npm install -g <pkg>@<version>` |
|
|
290
|
-
| `pip` / `uvx` | `uv tool
|
|
290
|
+
| `pip` / `uvx` | `uv tool install --force <pkg>@latest` | `uv tool install --force <pkg>==<version>` |
|
|
291
291
|
| `apt` | `apt-get install --only-upgrade` | `apt-get install <pkg>=<version>` (single package only) |
|
|
292
292
|
| `git` | `git pull` (+ re-run `git_install_cmd`) | not supported — repos are cloned shallow (`--depth=1`) |
|
|
293
293
|
| `script` / `bash` | Re-runs the install script | not supported — no version parameter to inject |
|
|
@@ -296,6 +296,17 @@ For `script`/`bash` packages, "update" is a full reinstall (the same script that
|
|
|
296
296
|
`sudo` runs again), since there's no narrower update mechanism available. `devstuff update` asks
|
|
297
297
|
for confirmation before doing this.
|
|
298
298
|
|
|
299
|
+
`pip`/`uvx` packages go through `uv tool install` rather than `uv tool upgrade` in **both**
|
|
300
|
+
columns. `uv tool upgrade` takes a tool *name*, so it cannot accept `<pkg>==<version>` at all, and
|
|
301
|
+
it is a no-op on a tool that was previously pinned — using it for the "latest" column would mean
|
|
302
|
+
pinning a package once left no way to move it again. `<pkg>@latest` re-resolves and clears any
|
|
303
|
+
existing pin in one step. Pinning warns that the pin holds until the next `devstuff update`
|
|
304
|
+
without `--version`.
|
|
305
|
+
|
|
306
|
+
Any `uv_with` / `uv_executables_from` / `uv_python` fields on the package are re-applied on every
|
|
307
|
+
update, so adding one to your catalog takes effect on the next update rather than needing a
|
|
308
|
+
reinstall.
|
|
309
|
+
|
|
299
310
|
---
|
|
300
311
|
|
|
301
312
|
### `configure`
|
|
@@ -1132,6 +1143,7 @@ ones tools already have — for now, custom functions are hand-edited YAML at
|
|
|
1132
1143
|
| `whats-on-port` | network | script | Find which process is listening on a port | `port`, `protocol` (optional: `tcp`/`udp`/`all`) |
|
|
1133
1144
|
| `acc-check` | web-dev | script | Run the pi coding agent's `/dogfood` skill against a web URL | `url`, `instruction` (optional) |
|
|
1134
1145
|
| `aws-saml-reauth` | web-dev | script | Reauthorize the AWS CLI via `saml2aws login --force` | `profile` (optional) |
|
|
1146
|
+
| `which-ansible` | validation | script | Show which ansible a directory will actually use — the project's uv venv or the global one | `path` (optional) |
|
|
1135
1147
|
|
|
1136
1148
|
#### `whats-on-port`
|
|
1137
1149
|
|
|
@@ -1163,6 +1175,32 @@ so a missing `ss` names the apt package instead of surfacing "command not found"
|
|
|
1163
1175
|
|
|
1164
1176
|
---
|
|
1165
1177
|
|
|
1178
|
+
#### `which-ansible`
|
|
1179
|
+
|
|
1180
|
+
```bash
|
|
1181
|
+
devstuff run which-ansible # the current directory
|
|
1182
|
+
devstuff run which-ansible ~/infra # somewhere else
|
|
1183
|
+
```
|
|
1184
|
+
|
|
1185
|
+
Answers "if I type `ansible-playbook` here, which one runs?" — the project's own uv venv, or the
|
|
1186
|
+
global tool install. It walks up for a `pyproject.toml`/`uv.lock` the way uv does, then reports
|
|
1187
|
+
whichever applies:
|
|
1188
|
+
|
|
1189
|
+
- a synced project venv, plus the `uv run ansible-playbook` invocation that uses it (and what a
|
|
1190
|
+
bare `ansible-playbook` would pick instead);
|
|
1191
|
+
- ansible declared but not synced yet, pointing at `uv sync`;
|
|
1192
|
+
- no ansible in the project, or no project at all, naming the global one.
|
|
1193
|
+
|
|
1194
|
+
Every one of those is a success and exits 0 — including "this project has no ansible", which is
|
|
1195
|
+
an answer, not a failure. Non-zero is reserved for not being able to look at all (no uv, or a
|
|
1196
|
+
path that isn't a directory).
|
|
1197
|
+
|
|
1198
|
+
It exists because devstuff's ansible and a uv-managed ansible repo are now the *same shape* — both
|
|
1199
|
+
a uv-created venv — so the question stopped being "which tool is broken" and became "which
|
|
1200
|
+
environment am I in". See `docs/specs/uv-backbone/` for why the global one moved off apt.
|
|
1201
|
+
|
|
1202
|
+
---
|
|
1203
|
+
|
|
1166
1204
|
## Built-in packages
|
|
1167
1205
|
|
|
1168
1206
|
### Core
|
|
@@ -1182,13 +1220,18 @@ Optional utilities you may want on some machines.
|
|
|
1182
1220
|
| Key | Name | Description | Help |
|
|
1183
1221
|
|-----|------|-------------|------|
|
|
1184
1222
|
| `ansible` | Ansible | Automation engine for configuration management and app deployment (`devstuff configure ansible`) | `ansible --help` |
|
|
1223
|
+
| `ansible-vault` | Ansible Vault | Encrypt and manage secrets within Ansible projects (bundled with Ansible) | `ansible-vault --help` |
|
|
1185
1224
|
| `aws` | AWS CLI | Amazon Web Services CLI v2 | `aws help` |
|
|
1186
1225
|
| `bat` | bat | cat replacement with syntax highlighting and git integration (`devstuff configure bat`) | `bat --help` |
|
|
1187
1226
|
| `commitizen` | Commitizen | Conventional-commit prompt, semantic version bumping, and changelog generation (`devstuff configure commitizen`) | `cz --help` |
|
|
1188
|
-
| `eza` | eza | Modern ls replacement with git status, icons, and tree view | `eza --help` |
|
|
1189
1227
|
| `gh` | GitHub CLI | GitHub's official CLI | `gh --help` |
|
|
1228
|
+
| `git-lfs` | Git LFS | Git extension for versioning large files | `git lfs --help` |
|
|
1190
1229
|
| `htop` | htop | Interactive process and resource monitor | `man htop` |
|
|
1230
|
+
| `homebrew` | Homebrew | Package manager for Linux (and macOS) | `brew --help` |
|
|
1231
|
+
| `ipython` | IPython | Enhanced interactive Python shell with rich tab completion and magic commands | `ipython --help` |
|
|
1191
1232
|
| `lazygit` | lazygit | TUI git client for fast, keyboard-driven git workflows (`devstuff configure lazygit`) | `lazygit --help` |
|
|
1233
|
+
| `llm-checker` | llm-checker | Check LLM compatibility with your hardware | `llm-checker --help` |
|
|
1234
|
+
| `lmstudio` | LM Studio | Run large language models locally with a GUI | `lms --help` |
|
|
1192
1235
|
| `mkcert` | mkcert | Zero-config local HTTPS certificates | `mkcert --help` |
|
|
1193
1236
|
| `nerd-font` | JetBrainsMono Nerd Font | Patched font supplying the icons Starship and other CLI tools draw | `fc-list \| grep -i "nerd font"` |
|
|
1194
1237
|
| `ollama` | Ollama | Run large language models locally | `ollama --help` |
|
|
@@ -1205,6 +1248,7 @@ Optional utilities you may want on some machines.
|
|
|
1205
1248
|
|-----|------|-------------|------|
|
|
1206
1249
|
| `go` | Go | Go programming language toolchain | `go help` |
|
|
1207
1250
|
| `java` | Java 21 (OpenJDK) | OpenJDK 21 LTS - JDK and JRE | `java --help` |
|
|
1251
|
+
| `python` | Python (uv-managed) | CPython provisioned by uv, installed as the default `python`/`python3` | `uv python list` |
|
|
1208
1252
|
| `ruby` | Ruby (rbenv) | Ruby via rbenv version manager + ruby-build | `ruby --version` |
|
|
1209
1253
|
|
|
1210
1254
|
---
|
|
@@ -1247,7 +1291,10 @@ tools:
|
|
|
1247
1291
|
| `docs_url` | no | URL opened by `devstuff docs <key>` |
|
|
1248
1292
|
| `requires` | no | List of package keys that must already be installed |
|
|
1249
1293
|
| `npm_name` | npm | npm package name |
|
|
1250
|
-
| `pip_name` | pip | PyPI package name |
|
|
1294
|
+
| `pip_name` | pip | PyPI package name. Extras work as-is: `pip_name: "ansible-lint[lock]"` |
|
|
1295
|
+
| `uv_with` | no (pip/uvx) | Extra packages installed into the tool's environment (`uv tool install --with`) |
|
|
1296
|
+
| `uv_executables_from` | no (pip/uvx) | Also expose console scripts from these packages (`--with-executables-from`). Needed when a package's entry points live in a dependency |
|
|
1297
|
+
| `uv_python` | no (pip/uvx) | Pin the tool environment's Python (`--python`), e.g. `"3.12"` |
|
|
1251
1298
|
| `apt_packages` | apt | Space-separated list of apt packages |
|
|
1252
1299
|
| `git_url` | git | Repository URL to clone |
|
|
1253
1300
|
| `git_install_cmd` | git | Bash command run inside the cloned repo after clone |
|
|
@@ -1256,7 +1303,21 @@ tools:
|
|
|
1256
1303
|
| `install_script` | bash | Full bash script to run on install |
|
|
1257
1304
|
| `remove_script` | bash | Full bash script to run on remove |
|
|
1258
1305
|
|
|
1259
|
-
Unknown fields fail validation
|
|
1306
|
+
Unknown fields fail validation — including the three `uv_*` fields on any type other than `pip`/`uvx`. `requires` defaults to `["nvm"]` for `npm` tools and `["uv"]` for `pip`/`uvx` tools unless explicitly set.
|
|
1307
|
+
|
|
1308
|
+
`uv_executables_from` is worth understanding before you skip it: `uv tool install` exposes the console scripts of the *requested* package only. The `ansible` distribution is a collections bundle that declares none of its own — `ansible`, `ansible-playbook` and `ansible-vault` are all entry points of its `ansible-core` dependency — so without this field the install reports success and puts a single unusable `ansible-community` on `PATH`:
|
|
1309
|
+
|
|
1310
|
+
```yaml
|
|
1311
|
+
version: 1
|
|
1312
|
+
tools:
|
|
1313
|
+
ansible:
|
|
1314
|
+
name: Ansible
|
|
1315
|
+
type: uvx
|
|
1316
|
+
check_cmd: ansible
|
|
1317
|
+
pip_name: ansible
|
|
1318
|
+
uv_executables_from:
|
|
1319
|
+
- ansible-core
|
|
1320
|
+
```
|
|
1260
1321
|
|
|
1261
1322
|
### Examples
|
|
1262
1323
|
|
|
@@ -260,7 +260,7 @@ How "update" is performed depends on the package's install `type`:
|
|
|
260
260
|
| Type | Latest | Specific version |
|
|
261
261
|
|------|--------|-------------------|
|
|
262
262
|
| `npm` | `npm install -g <pkg>@latest` | `npm install -g <pkg>@<version>` |
|
|
263
|
-
| `pip` / `uvx` | `uv tool
|
|
263
|
+
| `pip` / `uvx` | `uv tool install --force <pkg>@latest` | `uv tool install --force <pkg>==<version>` |
|
|
264
264
|
| `apt` | `apt-get install --only-upgrade` | `apt-get install <pkg>=<version>` (single package only) |
|
|
265
265
|
| `git` | `git pull` (+ re-run `git_install_cmd`) | not supported — repos are cloned shallow (`--depth=1`) |
|
|
266
266
|
| `script` / `bash` | Re-runs the install script | not supported — no version parameter to inject |
|
|
@@ -269,6 +269,17 @@ For `script`/`bash` packages, "update" is a full reinstall (the same script that
|
|
|
269
269
|
`sudo` runs again), since there's no narrower update mechanism available. `devstuff update` asks
|
|
270
270
|
for confirmation before doing this.
|
|
271
271
|
|
|
272
|
+
`pip`/`uvx` packages go through `uv tool install` rather than `uv tool upgrade` in **both**
|
|
273
|
+
columns. `uv tool upgrade` takes a tool *name*, so it cannot accept `<pkg>==<version>` at all, and
|
|
274
|
+
it is a no-op on a tool that was previously pinned — using it for the "latest" column would mean
|
|
275
|
+
pinning a package once left no way to move it again. `<pkg>@latest` re-resolves and clears any
|
|
276
|
+
existing pin in one step. Pinning warns that the pin holds until the next `devstuff update`
|
|
277
|
+
without `--version`.
|
|
278
|
+
|
|
279
|
+
Any `uv_with` / `uv_executables_from` / `uv_python` fields on the package are re-applied on every
|
|
280
|
+
update, so adding one to your catalog takes effect on the next update rather than needing a
|
|
281
|
+
reinstall.
|
|
282
|
+
|
|
272
283
|
---
|
|
273
284
|
|
|
274
285
|
### `configure`
|
|
@@ -1105,6 +1116,7 @@ ones tools already have — for now, custom functions are hand-edited YAML at
|
|
|
1105
1116
|
| `whats-on-port` | network | script | Find which process is listening on a port | `port`, `protocol` (optional: `tcp`/`udp`/`all`) |
|
|
1106
1117
|
| `acc-check` | web-dev | script | Run the pi coding agent's `/dogfood` skill against a web URL | `url`, `instruction` (optional) |
|
|
1107
1118
|
| `aws-saml-reauth` | web-dev | script | Reauthorize the AWS CLI via `saml2aws login --force` | `profile` (optional) |
|
|
1119
|
+
| `which-ansible` | validation | script | Show which ansible a directory will actually use — the project's uv venv or the global one | `path` (optional) |
|
|
1108
1120
|
|
|
1109
1121
|
#### `whats-on-port`
|
|
1110
1122
|
|
|
@@ -1136,6 +1148,32 @@ so a missing `ss` names the apt package instead of surfacing "command not found"
|
|
|
1136
1148
|
|
|
1137
1149
|
---
|
|
1138
1150
|
|
|
1151
|
+
#### `which-ansible`
|
|
1152
|
+
|
|
1153
|
+
```bash
|
|
1154
|
+
devstuff run which-ansible # the current directory
|
|
1155
|
+
devstuff run which-ansible ~/infra # somewhere else
|
|
1156
|
+
```
|
|
1157
|
+
|
|
1158
|
+
Answers "if I type `ansible-playbook` here, which one runs?" — the project's own uv venv, or the
|
|
1159
|
+
global tool install. It walks up for a `pyproject.toml`/`uv.lock` the way uv does, then reports
|
|
1160
|
+
whichever applies:
|
|
1161
|
+
|
|
1162
|
+
- a synced project venv, plus the `uv run ansible-playbook` invocation that uses it (and what a
|
|
1163
|
+
bare `ansible-playbook` would pick instead);
|
|
1164
|
+
- ansible declared but not synced yet, pointing at `uv sync`;
|
|
1165
|
+
- no ansible in the project, or no project at all, naming the global one.
|
|
1166
|
+
|
|
1167
|
+
Every one of those is a success and exits 0 — including "this project has no ansible", which is
|
|
1168
|
+
an answer, not a failure. Non-zero is reserved for not being able to look at all (no uv, or a
|
|
1169
|
+
path that isn't a directory).
|
|
1170
|
+
|
|
1171
|
+
It exists because devstuff's ansible and a uv-managed ansible repo are now the *same shape* — both
|
|
1172
|
+
a uv-created venv — so the question stopped being "which tool is broken" and became "which
|
|
1173
|
+
environment am I in". See `docs/specs/uv-backbone/` for why the global one moved off apt.
|
|
1174
|
+
|
|
1175
|
+
---
|
|
1176
|
+
|
|
1139
1177
|
## Built-in packages
|
|
1140
1178
|
|
|
1141
1179
|
### Core
|
|
@@ -1155,13 +1193,18 @@ Optional utilities you may want on some machines.
|
|
|
1155
1193
|
| Key | Name | Description | Help |
|
|
1156
1194
|
|-----|------|-------------|------|
|
|
1157
1195
|
| `ansible` | Ansible | Automation engine for configuration management and app deployment (`devstuff configure ansible`) | `ansible --help` |
|
|
1196
|
+
| `ansible-vault` | Ansible Vault | Encrypt and manage secrets within Ansible projects (bundled with Ansible) | `ansible-vault --help` |
|
|
1158
1197
|
| `aws` | AWS CLI | Amazon Web Services CLI v2 | `aws help` |
|
|
1159
1198
|
| `bat` | bat | cat replacement with syntax highlighting and git integration (`devstuff configure bat`) | `bat --help` |
|
|
1160
1199
|
| `commitizen` | Commitizen | Conventional-commit prompt, semantic version bumping, and changelog generation (`devstuff configure commitizen`) | `cz --help` |
|
|
1161
|
-
| `eza` | eza | Modern ls replacement with git status, icons, and tree view | `eza --help` |
|
|
1162
1200
|
| `gh` | GitHub CLI | GitHub's official CLI | `gh --help` |
|
|
1201
|
+
| `git-lfs` | Git LFS | Git extension for versioning large files | `git lfs --help` |
|
|
1163
1202
|
| `htop` | htop | Interactive process and resource monitor | `man htop` |
|
|
1203
|
+
| `homebrew` | Homebrew | Package manager for Linux (and macOS) | `brew --help` |
|
|
1204
|
+
| `ipython` | IPython | Enhanced interactive Python shell with rich tab completion and magic commands | `ipython --help` |
|
|
1164
1205
|
| `lazygit` | lazygit | TUI git client for fast, keyboard-driven git workflows (`devstuff configure lazygit`) | `lazygit --help` |
|
|
1206
|
+
| `llm-checker` | llm-checker | Check LLM compatibility with your hardware | `llm-checker --help` |
|
|
1207
|
+
| `lmstudio` | LM Studio | Run large language models locally with a GUI | `lms --help` |
|
|
1165
1208
|
| `mkcert` | mkcert | Zero-config local HTTPS certificates | `mkcert --help` |
|
|
1166
1209
|
| `nerd-font` | JetBrainsMono Nerd Font | Patched font supplying the icons Starship and other CLI tools draw | `fc-list \| grep -i "nerd font"` |
|
|
1167
1210
|
| `ollama` | Ollama | Run large language models locally | `ollama --help` |
|
|
@@ -1178,6 +1221,7 @@ Optional utilities you may want on some machines.
|
|
|
1178
1221
|
|-----|------|-------------|------|
|
|
1179
1222
|
| `go` | Go | Go programming language toolchain | `go help` |
|
|
1180
1223
|
| `java` | Java 21 (OpenJDK) | OpenJDK 21 LTS - JDK and JRE | `java --help` |
|
|
1224
|
+
| `python` | Python (uv-managed) | CPython provisioned by uv, installed as the default `python`/`python3` | `uv python list` |
|
|
1181
1225
|
| `ruby` | Ruby (rbenv) | Ruby via rbenv version manager + ruby-build | `ruby --version` |
|
|
1182
1226
|
|
|
1183
1227
|
---
|
|
@@ -1220,7 +1264,10 @@ tools:
|
|
|
1220
1264
|
| `docs_url` | no | URL opened by `devstuff docs <key>` |
|
|
1221
1265
|
| `requires` | no | List of package keys that must already be installed |
|
|
1222
1266
|
| `npm_name` | npm | npm package name |
|
|
1223
|
-
| `pip_name` | pip | PyPI package name |
|
|
1267
|
+
| `pip_name` | pip | PyPI package name. Extras work as-is: `pip_name: "ansible-lint[lock]"` |
|
|
1268
|
+
| `uv_with` | no (pip/uvx) | Extra packages installed into the tool's environment (`uv tool install --with`) |
|
|
1269
|
+
| `uv_executables_from` | no (pip/uvx) | Also expose console scripts from these packages (`--with-executables-from`). Needed when a package's entry points live in a dependency |
|
|
1270
|
+
| `uv_python` | no (pip/uvx) | Pin the tool environment's Python (`--python`), e.g. `"3.12"` |
|
|
1224
1271
|
| `apt_packages` | apt | Space-separated list of apt packages |
|
|
1225
1272
|
| `git_url` | git | Repository URL to clone |
|
|
1226
1273
|
| `git_install_cmd` | git | Bash command run inside the cloned repo after clone |
|
|
@@ -1229,7 +1276,21 @@ tools:
|
|
|
1229
1276
|
| `install_script` | bash | Full bash script to run on install |
|
|
1230
1277
|
| `remove_script` | bash | Full bash script to run on remove |
|
|
1231
1278
|
|
|
1232
|
-
Unknown fields fail validation
|
|
1279
|
+
Unknown fields fail validation — including the three `uv_*` fields on any type other than `pip`/`uvx`. `requires` defaults to `["nvm"]` for `npm` tools and `["uv"]` for `pip`/`uvx` tools unless explicitly set.
|
|
1280
|
+
|
|
1281
|
+
`uv_executables_from` is worth understanding before you skip it: `uv tool install` exposes the console scripts of the *requested* package only. The `ansible` distribution is a collections bundle that declares none of its own — `ansible`, `ansible-playbook` and `ansible-vault` are all entry points of its `ansible-core` dependency — so without this field the install reports success and puts a single unusable `ansible-community` on `PATH`:
|
|
1282
|
+
|
|
1283
|
+
```yaml
|
|
1284
|
+
version: 1
|
|
1285
|
+
tools:
|
|
1286
|
+
ansible:
|
|
1287
|
+
name: Ansible
|
|
1288
|
+
type: uvx
|
|
1289
|
+
check_cmd: ansible
|
|
1290
|
+
pip_name: ansible
|
|
1291
|
+
uv_executables_from:
|
|
1292
|
+
- ansible-core
|
|
1293
|
+
```
|
|
1233
1294
|
|
|
1234
1295
|
### Examples
|
|
1235
1296
|
|