devstuff 2.2.1__py3-none-any.whl → 2.3.0__py3-none-any.whl
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.
- dev_setup/catalog.py +22 -0
- dev_setup/commands/add_cmd.py +14 -0
- dev_setup/functions.yaml +98 -0
- dev_setup/generic.py +59 -4
- dev_setup/tools.yaml +58 -8
- {devstuff-2.2.1.dist-info → devstuff-2.3.0.dist-info}/METADATA +67 -6
- {devstuff-2.2.1.dist-info → devstuff-2.3.0.dist-info}/RECORD +9 -9
- {devstuff-2.2.1.dist-info → devstuff-2.3.0.dist-info}/WHEEL +1 -1
- {devstuff-2.2.1.dist-info → devstuff-2.3.0.dist-info}/entry_points.txt +0 -0
dev_setup/catalog.py
CHANGED
|
@@ -14,6 +14,9 @@ BUNDLED_CATALOG = "tools.yaml"
|
|
|
14
14
|
|
|
15
15
|
VERSION = 1
|
|
16
16
|
VALID_KEY = re.compile(r"^[a-z0-9][a-z0-9_-]*$")
|
|
17
|
+
# Fields that only mean anything to `uv tool install`, and the types that accept them.
|
|
18
|
+
UV_ONLY_FIELDS = ("uv_with", "uv_executables_from", "uv_python")
|
|
19
|
+
UV_TYPES = ("pip", "uvx")
|
|
17
20
|
SUPPORTED_FIELDS = {
|
|
18
21
|
"name",
|
|
19
22
|
"description",
|
|
@@ -26,6 +29,9 @@ SUPPORTED_FIELDS = {
|
|
|
26
29
|
"requires",
|
|
27
30
|
"npm_name",
|
|
28
31
|
"pip_name",
|
|
32
|
+
"uv_with",
|
|
33
|
+
"uv_executables_from",
|
|
34
|
+
"uv_python",
|
|
29
35
|
"apt_packages",
|
|
30
36
|
"git_url",
|
|
31
37
|
"git_install_cmd",
|
|
@@ -89,6 +95,22 @@ def validate_catalog(raw: Any, *, source: Path | str = "<catalog>") -> dict[str,
|
|
|
89
95
|
item.setdefault("category", "custom")
|
|
90
96
|
item.setdefault("type", "unknown")
|
|
91
97
|
|
|
98
|
+
for field in UV_ONLY_FIELDS:
|
|
99
|
+
if field not in item:
|
|
100
|
+
continue
|
|
101
|
+
if item["type"] not in UV_TYPES:
|
|
102
|
+
types = "/".join(UV_TYPES)
|
|
103
|
+
raise CatalogError(
|
|
104
|
+
f"{source}: tool {key!r} sets {field!r}, which is only valid on "
|
|
105
|
+
f"type {types} (got {item['type']!r})"
|
|
106
|
+
)
|
|
107
|
+
value = item[field]
|
|
108
|
+
if field == "uv_python":
|
|
109
|
+
if not isinstance(value, str):
|
|
110
|
+
raise CatalogError(f"{source}: tool {key!r} {field} must be a string")
|
|
111
|
+
elif not isinstance(value, list) or not all(isinstance(v, str) for v in value):
|
|
112
|
+
raise CatalogError(f"{source}: tool {key!r} {field} must be a list of strings")
|
|
113
|
+
|
|
92
114
|
requires = item.get("requires")
|
|
93
115
|
if requires is None:
|
|
94
116
|
if item["type"] == "npm":
|
dev_setup/commands/add_cmd.py
CHANGED
|
@@ -61,6 +61,20 @@ def add_cmd() -> None:
|
|
|
61
61
|
elif install_type == "uvx":
|
|
62
62
|
kwargs["pip_name"] = ui.text_input("PyPI package name:", default=key, required=True)
|
|
63
63
|
kwargs["check_cmd"] = ui.text_input("Command to check if installed:", default=key)
|
|
64
|
+
# All optional. Empty answers must not write empty keys into the user catalog.
|
|
65
|
+
exes = ui.text_input(
|
|
66
|
+
"Also expose executables from (space-separated packages, blank for none):"
|
|
67
|
+
)
|
|
68
|
+
if exes:
|
|
69
|
+
kwargs["uv_executables_from"] = exes.split()
|
|
70
|
+
extra = ui.text_input(
|
|
71
|
+
"Extra packages to install alongside it (space-separated, blank for none):"
|
|
72
|
+
)
|
|
73
|
+
if extra:
|
|
74
|
+
kwargs["uv_with"] = extra.split()
|
|
75
|
+
py = ui.text_input("Pin to a Python version (e.g. 3.12, blank for uv's default):")
|
|
76
|
+
if py:
|
|
77
|
+
kwargs["uv_python"] = py
|
|
64
78
|
|
|
65
79
|
elif install_type == "apt":
|
|
66
80
|
kwargs["apt_packages"] = ui.text_input(
|
dev_setup/functions.yaml
CHANGED
|
@@ -238,3 +238,101 @@ functions:
|
|
|
238
238
|
saml2aws login --force
|
|
239
239
|
fi
|
|
240
240
|
docs_url: https://github.com/Versent/saml2aws
|
|
241
|
+
|
|
242
|
+
which-ansible:
|
|
243
|
+
name: Which Ansible
|
|
244
|
+
description: Show which ansible a directory will actually use — the project's uv venv or the global one
|
|
245
|
+
category: validation
|
|
246
|
+
type: script
|
|
247
|
+
params:
|
|
248
|
+
- name: path
|
|
249
|
+
description: Directory to inspect (default the current one)
|
|
250
|
+
required: false
|
|
251
|
+
default: "."
|
|
252
|
+
docs_url: https://docs.astral.sh/uv/guides/projects/
|
|
253
|
+
script: |
|
|
254
|
+
# No `set -e`: reporting "this project has no ansible" is a successful answer,
|
|
255
|
+
# not a failure, and -e would turn a stray non-zero probe into a red banner.
|
|
256
|
+
# Non-zero is reserved for "could not perform the lookup" (see whats-on-port).
|
|
257
|
+
set -uo pipefail
|
|
258
|
+
|
|
259
|
+
if ! command -v uv >/dev/null 2>&1; then
|
|
260
|
+
echo "uv is not installed, so there is nothing to inspect." >&2
|
|
261
|
+
echo "Install it with: devstuff install uv" >&2
|
|
262
|
+
exit 1
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
start="${path:-.}"
|
|
266
|
+
if [ ! -d "$start" ]; then
|
|
267
|
+
echo "Not a directory: $start" >&2
|
|
268
|
+
exit 1
|
|
269
|
+
fi
|
|
270
|
+
start="$(cd "$start" && pwd)"
|
|
271
|
+
|
|
272
|
+
# Walk up for a uv project root, the same way uv itself does.
|
|
273
|
+
root=""
|
|
274
|
+
dir="$start"
|
|
275
|
+
while [ -n "$dir" ]; do
|
|
276
|
+
if [ -f "$dir/pyproject.toml" ] || [ -f "$dir/uv.lock" ]; then
|
|
277
|
+
root="$dir"
|
|
278
|
+
break
|
|
279
|
+
fi
|
|
280
|
+
[ "$dir" = "/" ] && break
|
|
281
|
+
dir="$(dirname "$dir")"
|
|
282
|
+
done
|
|
283
|
+
|
|
284
|
+
global="$(command -v ansible-playbook 2>/dev/null || true)"
|
|
285
|
+
|
|
286
|
+
if [ -z "$root" ]; then
|
|
287
|
+
echo "No uv project above $start (no pyproject.toml or uv.lock)."
|
|
288
|
+
if [ -n "$global" ]; then
|
|
289
|
+
echo "ansible-playbook here is the global one: $global"
|
|
290
|
+
else
|
|
291
|
+
echo "No ansible-playbook on PATH either — install one with: devstuff install ansible"
|
|
292
|
+
fi
|
|
293
|
+
exit 0
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
echo "uv project root: $root"
|
|
297
|
+
|
|
298
|
+
declared=""
|
|
299
|
+
for f in "$root/uv.lock" "$root/pyproject.toml"; do
|
|
300
|
+
[ -f "$f" ] || continue
|
|
301
|
+
if grep -qiE '(^|[^-a-z])ansible(-core)?([^-a-z]|$)' "$f" 2>/dev/null; then
|
|
302
|
+
declared="$f"
|
|
303
|
+
break
|
|
304
|
+
fi
|
|
305
|
+
done
|
|
306
|
+
|
|
307
|
+
venv_ansible="$root/.venv/bin/ansible-playbook"
|
|
308
|
+
if [ -x "$venv_ansible" ]; then
|
|
309
|
+
echo "Project ansible: $venv_ansible"
|
|
310
|
+
echo " $("$venv_ansible" --version 2>/dev/null | head -1)"
|
|
311
|
+
echo
|
|
312
|
+
echo "Inside this project, run ansible through uv so it uses that one:"
|
|
313
|
+
echo " uv run ansible-playbook <playbook.yml>"
|
|
314
|
+
# `[ -n "$x" ] && echo ...` as the last statement of a branch would make the
|
|
315
|
+
# whole script exit 1 whenever the test is false — a correct answer reported
|
|
316
|
+
# as a failure. Use a full `if`.
|
|
317
|
+
if [ -n "$global" ]; then
|
|
318
|
+
echo "(A bare 'ansible-playbook' would use $global instead.)"
|
|
319
|
+
fi
|
|
320
|
+
elif [ -n "$declared" ]; then
|
|
321
|
+
echo "Declared in: $declared"
|
|
322
|
+
echo "But .venv has no ansible-playbook yet — create it with:"
|
|
323
|
+
echo " uv sync"
|
|
324
|
+
if [ -n "$global" ]; then
|
|
325
|
+
echo "Until then a bare 'ansible-playbook' uses the global $global."
|
|
326
|
+
fi
|
|
327
|
+
else
|
|
328
|
+
echo "This project does not declare ansible."
|
|
329
|
+
if [ -n "$global" ]; then
|
|
330
|
+
echo "ansible-playbook here is the global one: $global"
|
|
331
|
+
else
|
|
332
|
+
echo "No ansible-playbook on PATH — install one with: devstuff install ansible"
|
|
333
|
+
fi
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
# Belt and braces for the rule above: every path that got far enough to print
|
|
337
|
+
# an answer is a success, whatever the last command happened to return.
|
|
338
|
+
exit 0
|
dev_setup/generic.py
CHANGED
|
@@ -95,6 +95,12 @@ class GenericTool(Tool):
|
|
|
95
95
|
version_cmd: str = ""
|
|
96
96
|
npm_name: str = ""
|
|
97
97
|
pip_name: str = ""
|
|
98
|
+
# uvx/pip only. `uv tool install` exposes console scripts of the *requested*
|
|
99
|
+
# package alone, so a package whose entry points live in a dependency (ansible's
|
|
100
|
+
# live in ansible-core) needs uv_executables_from or it installs nothing usable.
|
|
101
|
+
uv_with: list | None = None
|
|
102
|
+
uv_executables_from: list | None = None
|
|
103
|
+
uv_python: str = ""
|
|
98
104
|
git_url: str = ""
|
|
99
105
|
git_install_cmd: str = ""
|
|
100
106
|
git_remove_cmd: str = ""
|
|
@@ -113,6 +119,12 @@ class GenericTool(Tool):
|
|
|
113
119
|
self.name = self.key
|
|
114
120
|
if self.requires is None:
|
|
115
121
|
self.requires = list(AUTO_REQUIRES.get(self.install_type, []))
|
|
122
|
+
# Normalise to lists so the installer can iterate without None checks. Both
|
|
123
|
+
# stay falsy when unset, so to_dict() still omits them.
|
|
124
|
+
if self.uv_with is None:
|
|
125
|
+
self.uv_with = []
|
|
126
|
+
if self.uv_executables_from is None:
|
|
127
|
+
self.uv_executables_from = []
|
|
116
128
|
|
|
117
129
|
@classmethod
|
|
118
130
|
def from_dict(cls, data: dict, key: str) -> GenericTool:
|
|
@@ -214,6 +226,22 @@ def _install_npm(tool: GenericTool) -> None:
|
|
|
214
226
|
_run(["bash", "-lc", f"{_npm_init()} && npm install -g {shlex.quote(tool.npm_name)}"])
|
|
215
227
|
|
|
216
228
|
|
|
229
|
+
def _uv_install_flags(tool: GenericTool) -> list[str]:
|
|
230
|
+
"""Catalog uv_* fields as `uv tool install` flags.
|
|
231
|
+
|
|
232
|
+
uv records all of these in the tool's uv-receipt.toml and re-applies them on
|
|
233
|
+
`uv tool upgrade`, so they only need passing at install time.
|
|
234
|
+
"""
|
|
235
|
+
flags: list[str] = []
|
|
236
|
+
if tool.uv_python:
|
|
237
|
+
flags += ["--python", tool.uv_python]
|
|
238
|
+
for pkg in tool.uv_with or []:
|
|
239
|
+
flags += ["--with", pkg]
|
|
240
|
+
for pkg in tool.uv_executables_from or []:
|
|
241
|
+
flags += ["--with-executables-from", pkg]
|
|
242
|
+
return flags
|
|
243
|
+
|
|
244
|
+
|
|
217
245
|
def _install_uvx(tool: GenericTool) -> None:
|
|
218
246
|
if not tool.pip_name:
|
|
219
247
|
raise RuntimeError("pip_name not set")
|
|
@@ -224,7 +252,7 @@ def _install_uvx(tool: GenericTool) -> None:
|
|
|
224
252
|
"Install it first: devstuff install uv"
|
|
225
253
|
)
|
|
226
254
|
with verbose.step(f"Installing {tool.name} via uvx..."):
|
|
227
|
-
_run([uv, "tool", "install"
|
|
255
|
+
_run([uv, "tool", "install"] + _uv_install_flags(tool) + [tool.pip_name])
|
|
228
256
|
|
|
229
257
|
|
|
230
258
|
def _install_git(tool: GenericTool) -> None:
|
|
@@ -284,9 +312,30 @@ def _update_uvx(tool: GenericTool, version: str | None) -> None:
|
|
|
284
312
|
"uv is required to update uvx packages. "
|
|
285
313
|
"Install it first: devstuff install uv"
|
|
286
314
|
)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
315
|
+
# Both paths go through `uv tool install`, not `uv tool upgrade`.
|
|
316
|
+
#
|
|
317
|
+
# `uv tool upgrade` takes a tool *name*, not a requirement, so the pinned form
|
|
318
|
+
# ("pkg==1.2.3") was read as the whole name and always failed. And it is a no-op
|
|
319
|
+
# on an already-pinned tool ("Nothing to upgrade"), which would leave a user who
|
|
320
|
+
# ever pinned with no way back to latest through devstuff. `install pkg@latest`
|
|
321
|
+
# re-resolves *and* clears the pin in one call — measured against uv 0.11.21.
|
|
322
|
+
#
|
|
323
|
+
# Passing the uv_* flags on every update is deliberate: this writes a fresh
|
|
324
|
+
# receipt, and re-deriving from the catalog means a newly added uv_with or
|
|
325
|
+
# uv_executables_from takes effect on update rather than only on reinstall.
|
|
326
|
+
target = f"{tool.pip_name}=={version}" if version else f"{tool.pip_name}@latest"
|
|
327
|
+
cmd = [uv, "tool", "install", "--force"] + _uv_install_flags(tool) + [target]
|
|
328
|
+
|
|
329
|
+
if version:
|
|
330
|
+
from dev_setup import ui
|
|
331
|
+
ui.warn(
|
|
332
|
+
f"Pinning {tool.name} to {version}. It stays there until the next "
|
|
333
|
+
f"'devstuff update {tool.key}' without --version, which moves it back "
|
|
334
|
+
f"to the latest release."
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
with verbose.step(f"Updating {tool.name} via uv tool install..."):
|
|
338
|
+
_run(cmd)
|
|
290
339
|
|
|
291
340
|
|
|
292
341
|
def _update_apt(tool: GenericTool, version: str | None) -> None:
|
|
@@ -500,6 +549,12 @@ def _remove_npm(tool: GenericTool) -> None:
|
|
|
500
549
|
|
|
501
550
|
|
|
502
551
|
def _remove_uvx(tool: GenericTool) -> None:
|
|
552
|
+
# An explicit remove_script wins, mirroring _remove_apt. Without this, a tool that
|
|
553
|
+
# shares a pip_name with another entry (ansible-vault shares ansible's) would
|
|
554
|
+
# uninstall the whole shared tool environment out from under it.
|
|
555
|
+
if tool.remove_script:
|
|
556
|
+
_run_bash_script(tool.remove_script)
|
|
557
|
+
return
|
|
503
558
|
uv = shutil.which("uv")
|
|
504
559
|
if not uv:
|
|
505
560
|
raise RuntimeError(
|
dev_setup/tools.yaml
CHANGED
|
@@ -4,23 +4,29 @@ tools:
|
|
|
4
4
|
name: Ansible
|
|
5
5
|
description: Automation engine for configuration management and app deployment
|
|
6
6
|
category: tools
|
|
7
|
-
type:
|
|
7
|
+
type: uvx
|
|
8
8
|
check_cmd: ansible
|
|
9
|
-
|
|
9
|
+
pip_name: ansible
|
|
10
|
+
# The `ansible` distribution is the collections bundle and declares no console
|
|
11
|
+
# scripts of its own — `ansible`, `ansible-playbook`, `ansible-vault` and the rest
|
|
12
|
+
# are entry points of its ansible-core dependency, and `uv tool install` only
|
|
13
|
+
# exposes the requested package's scripts. Without this, the install "succeeds"
|
|
14
|
+
# and puts a single unusable `ansible-community` on PATH. Do not swap this for
|
|
15
|
+
# pip_name: ansible-core — that drops the bundled collections.
|
|
16
|
+
uv_executables_from:
|
|
17
|
+
- ansible-core
|
|
10
18
|
help_cmd: ansible --help
|
|
11
19
|
docs_url: https://docs.ansible.com/
|
|
12
|
-
remove_script: |
|
|
13
|
-
set -euo pipefail
|
|
14
|
-
sudo apt-get remove -y ansible
|
|
15
|
-
sudo apt-get autoremove -y
|
|
16
20
|
|
|
17
21
|
ansible-vault:
|
|
18
22
|
name: Ansible Vault
|
|
19
23
|
description: Encrypt and manage secrets within Ansible projects (bundled with Ansible)
|
|
20
24
|
category: tools
|
|
21
|
-
type:
|
|
25
|
+
type: uvx
|
|
22
26
|
check_cmd: ansible-vault
|
|
23
|
-
|
|
27
|
+
pip_name: ansible
|
|
28
|
+
uv_executables_from:
|
|
29
|
+
- ansible-core
|
|
24
30
|
help_cmd: ansible-vault --help
|
|
25
31
|
docs_url: https://docs.ansible.com/ansible/latest/vault_guide/index.html
|
|
26
32
|
requires:
|
|
@@ -525,6 +531,50 @@ tools:
|
|
|
525
531
|
sudo apt-get remove -y 'php8.4*'
|
|
526
532
|
sudo apt-get autoremove -y
|
|
527
533
|
|
|
534
|
+
python:
|
|
535
|
+
name: Python (uv-managed)
|
|
536
|
+
description: CPython provisioned by uv, installed as the default python/python3
|
|
537
|
+
category: languages
|
|
538
|
+
type: bash
|
|
539
|
+
# Every Linux host already has a python3, so "is python3 on PATH" would report
|
|
540
|
+
# this installed before it ever ran. Ask uv where its interpreters live and
|
|
541
|
+
# require the default shim to resolve *inside* that directory. `test -x` rather
|
|
542
|
+
# than `test -e` because a leftover shim pointing at an uninstalled version is a
|
|
543
|
+
# dangling symlink — readlink still prints a matching path, but it is not usable.
|
|
544
|
+
# The `test -n "$d"` guard is load-bearing: with uv absent, "$d" is empty and the
|
|
545
|
+
# case pattern would collapse to "/*", which matches every absolute path.
|
|
546
|
+
check_cmd: |
|
|
547
|
+
bin="${UV_PYTHON_BIN_DIR:-$HOME/.local/bin}"; d="$(uv python dir 2>/dev/null)"; test -n "$d" || exit 1; case "$(readlink -f "$bin/python3" 2>/dev/null)" in "$d"/*) test -x "$bin/python3";; *) exit 1;; esac
|
|
548
|
+
help_cmd: uv python list
|
|
549
|
+
docs_url: https://docs.astral.sh/uv/guides/install-python/
|
|
550
|
+
requires:
|
|
551
|
+
- uv
|
|
552
|
+
install_script: |
|
|
553
|
+
set -euo pipefail
|
|
554
|
+
uv python install --default
|
|
555
|
+
bin="${UV_PYTHON_BIN_DIR:-$HOME/.local/bin}"
|
|
556
|
+
echo
|
|
557
|
+
echo "'python' and 'python3' in $bin now resolve to uv's CPython."
|
|
558
|
+
echo "Since uv puts $bin early on PATH, that is what your shell will pick."
|
|
559
|
+
echo "The system interpreter at /usr/bin/python3 is untouched, and anything"
|
|
560
|
+
echo "with an explicit /usr/bin/python3 shebang keeps using it."
|
|
561
|
+
remove_script: |
|
|
562
|
+
set -euo pipefail
|
|
563
|
+
bin="${UV_PYTHON_BIN_DIR:-$HOME/.local/bin}"
|
|
564
|
+
target="$(readlink -f "$bin/python3" 2>/dev/null || true)"
|
|
565
|
+
if [ -z "$target" ] || [ ! -x "$target" ]; then
|
|
566
|
+
echo "No uv-managed default python3 found in $bin — nothing to remove."
|
|
567
|
+
exit 0
|
|
568
|
+
fi
|
|
569
|
+
# Uninstall only the version the default shim points at. `uv python uninstall
|
|
570
|
+
# --all` would also take out managed interpreters that existing `uv tool`
|
|
571
|
+
# environments were built against, breaking tools this command was not asked
|
|
572
|
+
# to touch.
|
|
573
|
+
version="$("$target" -c 'import sys; print("%d.%d.%d" % sys.version_info[:3])')"
|
|
574
|
+
uv python uninstall "$version"
|
|
575
|
+
echo "Removed uv-managed Python $version and its python/python3 shims."
|
|
576
|
+
echo "Other uv-managed versions and /usr/bin/python3 were left alone."
|
|
577
|
+
|
|
528
578
|
pre-commit:
|
|
529
579
|
name: pre-commit
|
|
530
580
|
description: Git hook manager for automated code quality checks
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: devstuff
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
|
@@ -3,17 +3,17 @@ dev_setup/__main__.py,sha256=w-Vnqe3bgAw_UNlPywPkbOqoFKcEk2H2S79wrQjXvU4,102
|
|
|
3
3
|
dev_setup/agent_tools.schema.json,sha256=veXnih00xW4w9yK3ruWz-npUrCnJmcQpQdFvfcu7v_I,4928
|
|
4
4
|
dev_setup/agent_tools.yaml,sha256=56E70B1W7hBqjGhYhhCAGzFQvi1nUZ04cnNiqZlMjyY,4506
|
|
5
5
|
dev_setup/base.py,sha256=j2EQ5gzeGgtveWQN_BgbLcZianMENJ7PQy8EQ9J9MPE,1946
|
|
6
|
-
dev_setup/catalog.py,sha256=
|
|
6
|
+
dev_setup/catalog.py,sha256=PrimTBUYoh8cW9QYhXUxP8qSMeK51kzQjUoPEGD9EBw,6530
|
|
7
7
|
dev_setup/cli.py,sha256=Y_RbHkexTAIQN4neDQ3buo6vmtTMNdXrsYsmY0X_Om8,2976
|
|
8
8
|
dev_setup/doctor.py,sha256=JliUAgmiW2HVCWHhT76PIXTBekNXXrc0VfDMaOeLBWY,15918
|
|
9
9
|
dev_setup/function_runner.py,sha256=08F6jvB9-oOyE5ivYtRjnL16JTGsfRnkpvkjSVrJhsM,6957
|
|
10
10
|
dev_setup/functions.schema.json,sha256=VVdpH3xWsdbtqw47S1QqpBcs4O9wd3XZ_s0Q3sGwv0Q,6220
|
|
11
|
-
dev_setup/functions.yaml,sha256=
|
|
11
|
+
dev_setup/functions.yaml,sha256=MYeBgR6mkpJZDcoSW88lCIfrtayvJViY8BZxjxedM7o,12734
|
|
12
12
|
dev_setup/functions_catalog.py,sha256=YgR2hY9HM1iol1AZcF-_j02EXCP9U4P3e_5eWes9KH0,7116
|
|
13
13
|
dev_setup/functions_registry.py,sha256=YSl-VgNXPf0hj4WgReg-pZUj4fAjFlwvC165dgon_BE,3682
|
|
14
|
-
dev_setup/generic.py,sha256=
|
|
14
|
+
dev_setup/generic.py,sha256=Tr7YAkS_SXjExJpyNw3palDuaiNn1TcAoeZ0wHdXIeM,28424
|
|
15
15
|
dev_setup/registry.py,sha256=8lrk6EXCCWk1WNeNnCGLTJ-v8rpCs7-TLYtVXgiuxnA,2243
|
|
16
|
-
dev_setup/tools.yaml,sha256=
|
|
16
|
+
dev_setup/tools.yaml,sha256=5etktWY2tRAm7l4CG34wspdw9M-zipuhhvmJHg5ufJQ,30431
|
|
17
17
|
dev_setup/ui.py,sha256=Z5nNZV8MN8tAjo1ZWq7-QPZYkPKQKuM7JbmGtiuKTqM,3806
|
|
18
18
|
dev_setup/verbose.py,sha256=N3U9JAYOvFwdd8DT6W7Pqq6RCIUfbcQnPsm0O1TnvQY,5003
|
|
19
19
|
dev_setup/agent/__init__.py,sha256=znrgxUCr_y0Qp-f41S6EaTv-hoTYwoZ9mOMLVge-IJg,226
|
|
@@ -32,7 +32,7 @@ dev_setup/agent/session.py,sha256=KxBBbFFvO5bBbDJ6H94O7tgtrQ2i5kGq6EsXCHaDvPo,11
|
|
|
32
32
|
dev_setup/agent/transcript.py,sha256=hJQuSLriAe5hvnD3SCMqOWduuYtblyhmqzZQ3jDco1Y,1868
|
|
33
33
|
dev_setup/agent/wizard.py,sha256=p0vHiJ-palTMoL6p7zQscL01laBFKw9f5HHY1yemZ4w,4056
|
|
34
34
|
dev_setup/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
-
dev_setup/commands/add_cmd.py,sha256=
|
|
35
|
+
dev_setup/commands/add_cmd.py,sha256=dqgagwBDkeK47RTnIREf0PlXjfLHQpxjdQ0kHJqLqDE,12290
|
|
36
36
|
dev_setup/commands/agent_cmd.py,sha256=TtsCzNVXgwxS2ACFLFl6ibnjoohEQWXgM2rfWkBnKk0,5128
|
|
37
37
|
dev_setup/commands/catalog_cmd.py,sha256=dz_0UfZy3v622sbJ4AWiNftZr6yYtsjaBQw1z7UF4Lk,1002
|
|
38
38
|
dev_setup/commands/configure_cmd.py,sha256=-6UQhrcdEpf6AecDfGq7otq2G4rqdeRPjJUV1lze2Zg,3738
|
|
@@ -90,7 +90,7 @@ dev_setup/configure/starship/model.py,sha256=HNkIj69DqBQFAq0VTHqPLNZT2DuBZSfF-5y
|
|
|
90
90
|
dev_setup/configure/starship/preview.py,sha256=ZaNhNA3AeHHtPnMqrJbB9XVVXDFbH1R36jm8xyYdt2U,6145
|
|
91
91
|
dev_setup/configure/starship/render.py,sha256=gDE0hbGZ9oDCR1VAZQRDm5EFYviMjj0-eSmr1EnZfn8,10262
|
|
92
92
|
dev_setup/configure/starship/wizard.py,sha256=BmWl-U6ttF79gepDhCv8UxT8ZDIMe1SWnhSkA1iIrGQ,15498
|
|
93
|
-
devstuff-2.
|
|
94
|
-
devstuff-2.
|
|
95
|
-
devstuff-2.
|
|
96
|
-
devstuff-2.
|
|
93
|
+
devstuff-2.3.0.dist-info/METADATA,sha256=GN8skkw5mqOAce6_TDbHMk7SFX5EUiPQAL8w0O7Chek,72225
|
|
94
|
+
devstuff-2.3.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
95
|
+
devstuff-2.3.0.dist-info/entry_points.txt,sha256=48ITy9ENdXDFrTHwAyVPUpq1MDmgFqcOisOo5mAR6OQ,53
|
|
96
|
+
devstuff-2.3.0.dist-info/RECORD,,
|
|
File without changes
|