devstuff 2.1.0__py3-none-any.whl → 2.2.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/cli.py +28 -1
- dev_setup/commands/install_cmd.py +1 -5
- dev_setup/commands/remove_cmd.py +1 -5
- dev_setup/commands/run_cmd.py +10 -1
- dev_setup/commands/update_cmd.py +1 -5
- dev_setup/function_runner.py +27 -6
- dev_setup/generic.py +86 -69
- dev_setup/verbose.py +148 -0
- {devstuff-2.1.0.dist-info → devstuff-2.2.0.dist-info}/METADATA +25 -1
- {devstuff-2.1.0.dist-info → devstuff-2.2.0.dist-info}/RECORD +12 -11
- {devstuff-2.1.0.dist-info → devstuff-2.2.0.dist-info}/WHEEL +0 -0
- {devstuff-2.1.0.dist-info → devstuff-2.2.0.dist-info}/entry_points.txt +0 -0
dev_setup/cli.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import click
|
|
2
2
|
|
|
3
|
-
from dev_setup import __version__
|
|
3
|
+
from dev_setup import __version__, verbose
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
@click.group(
|
|
7
7
|
invoke_without_command=True,
|
|
8
8
|
context_settings={"help_option_names": ["-h", "--help"]},
|
|
9
9
|
)
|
|
10
|
+
@verbose.option
|
|
10
11
|
@click.pass_context
|
|
11
12
|
def cli(ctx: click.Context) -> None:
|
|
12
13
|
if ctx.invoked_subcommand is None:
|
|
@@ -54,5 +55,31 @@ def _register_commands() -> None:
|
|
|
54
55
|
cli.add_command(skills_cmd, "skills")
|
|
55
56
|
cli.add_command(agent_cmd, "agent")
|
|
56
57
|
|
|
58
|
+
_add_verbose_option(cli)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _add_verbose_option(group: click.Group) -> None:
|
|
62
|
+
"""Give every command and subcommand `-v`/`-vv`, recursively.
|
|
63
|
+
|
|
64
|
+
Done centrally rather than by decorating each command so that the flag is accepted
|
|
65
|
+
everywhere it could plausibly be typed — `devstuff -v install x`, `devstuff install
|
|
66
|
+
-v x`, `devstuff functions -v enable k` all work, and no command can be forgotten.
|
|
67
|
+
Aliases (`remove`/`uninstall`) share one Command object, hence the seen-set.
|
|
68
|
+
"""
|
|
69
|
+
seen: set[int] = set()
|
|
70
|
+
|
|
71
|
+
def walk(cmd: click.Command) -> None:
|
|
72
|
+
if id(cmd) in seen:
|
|
73
|
+
return
|
|
74
|
+
seen.add(id(cmd))
|
|
75
|
+
if not any("--verbose" in p.opts for p in cmd.params):
|
|
76
|
+
verbose.option(cmd)
|
|
77
|
+
if isinstance(cmd, click.Group):
|
|
78
|
+
for sub in cmd.commands.values():
|
|
79
|
+
walk(sub)
|
|
80
|
+
|
|
81
|
+
for command in group.commands.values():
|
|
82
|
+
walk(command)
|
|
83
|
+
|
|
57
84
|
|
|
58
85
|
_register_commands()
|
|
@@ -11,13 +11,9 @@ from dev_setup.base import Tool
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
@click.command("install")
|
|
14
|
-
@click.option("--verbose", "-v", is_flag=True, help="Stream install output to the terminal.")
|
|
15
14
|
@click.argument("packages", nargs=-1)
|
|
16
|
-
def install_cmd(packages: tuple[str, ...]
|
|
15
|
+
def install_cmd(packages: tuple[str, ...]) -> None:
|
|
17
16
|
"""Install packages. Interactive picker when called with no arguments."""
|
|
18
|
-
from dev_setup import generic
|
|
19
|
-
generic._verbose = verbose
|
|
20
|
-
|
|
21
17
|
if not packages:
|
|
22
18
|
_install_interactive()
|
|
23
19
|
else:
|
dev_setup/commands/remove_cmd.py
CHANGED
|
@@ -9,13 +9,9 @@ from dev_setup.base import Tool
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@click.command("remove")
|
|
12
|
-
@click.option("--verbose", "-v", is_flag=True, help="Stream removal output to the terminal.")
|
|
13
12
|
@click.argument("packages", nargs=-1)
|
|
14
|
-
def remove_cmd(packages: tuple[str, ...]
|
|
13
|
+
def remove_cmd(packages: tuple[str, ...]) -> None:
|
|
15
14
|
"""Uninstall installed packages."""
|
|
16
|
-
from dev_setup import generic
|
|
17
|
-
generic._verbose = verbose
|
|
18
|
-
|
|
19
15
|
if not packages:
|
|
20
16
|
ui.error("Specify at least one package key. See: devstuff list --installed")
|
|
21
17
|
sys.exit(1)
|
dev_setup/commands/run_cmd.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import subprocess
|
|
3
4
|
import sys
|
|
4
5
|
|
|
5
6
|
import click
|
|
6
7
|
|
|
7
8
|
from dev_setup import function_runner as runner
|
|
8
|
-
from dev_setup import functions_registry, ui
|
|
9
|
+
from dev_setup import functions_registry, ui, verbose
|
|
9
10
|
from dev_setup.function_runner import ParamResolutionError
|
|
10
11
|
from dev_setup.functions_registry import FunctionDef, FunctionParam
|
|
11
12
|
|
|
@@ -20,6 +21,8 @@ def run_cmd(key: str, args: tuple[str, ...]) -> None:
|
|
|
20
21
|
ui.error(f"Unknown function: '{key}'")
|
|
21
22
|
sys.exit(1)
|
|
22
23
|
|
|
24
|
+
verbose.trace(f"function '{fn.key}': type={fn.type} register={fn.register} args={list(args)}")
|
|
25
|
+
|
|
23
26
|
if fn.type == "script":
|
|
24
27
|
_run_script(fn, args)
|
|
25
28
|
elif fn.type == "shell-eval" and fn.register == "eval":
|
|
@@ -49,6 +52,12 @@ def _run_script(fn: FunctionDef, args: tuple[str, ...]) -> None:
|
|
|
49
52
|
except ParamResolutionError as exc:
|
|
50
53
|
ui.error(str(exc))
|
|
51
54
|
sys.exit(1)
|
|
55
|
+
except subprocess.CalledProcessError as exc:
|
|
56
|
+
# devstuff's own exit code is always 1 here (a function can't signal a result
|
|
57
|
+
# through it), so the script's real exit code is only visible under -v.
|
|
58
|
+
ui.error(f"'{fn.name}' failed: {exc}")
|
|
59
|
+
verbose.log(f"'{fn.key}' exited {exc.returncode}")
|
|
60
|
+
sys.exit(1)
|
|
52
61
|
except Exception as exc:
|
|
53
62
|
ui.error(f"'{fn.name}' failed: {exc}")
|
|
54
63
|
sys.exit(1)
|
dev_setup/commands/update_cmd.py
CHANGED
|
@@ -12,17 +12,13 @@ from dev_setup.generic import UpdateStatus
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@click.command("update")
|
|
15
|
-
@click.option("--verbose", "-v", is_flag=True, help="Stream update output to the terminal.")
|
|
16
15
|
@click.option(
|
|
17
16
|
"--version", "target_version", default=None,
|
|
18
17
|
help="Update to a specific version instead of latest. Only valid with a single package.",
|
|
19
18
|
)
|
|
20
19
|
@click.argument("packages", nargs=-1)
|
|
21
|
-
def update_cmd(packages: tuple[str, ...],
|
|
20
|
+
def update_cmd(packages: tuple[str, ...], target_version: str | None) -> None:
|
|
22
21
|
"""Update packages. Interactive picker with recommended updates when called with no arguments."""
|
|
23
|
-
from dev_setup import generic
|
|
24
|
-
generic._verbose = verbose
|
|
25
|
-
|
|
26
22
|
if not packages:
|
|
27
23
|
if target_version:
|
|
28
24
|
ui.error("--version requires a package key.")
|
dev_setup/function_runner.py
CHANGED
|
@@ -6,6 +6,7 @@ import subprocess
|
|
|
6
6
|
import tempfile
|
|
7
7
|
from collections.abc import Callable
|
|
8
8
|
|
|
9
|
+
from dev_setup import verbose
|
|
9
10
|
from dev_setup.base import patch_bashrc, remove_bashrc_block
|
|
10
11
|
from dev_setup.functions_registry import FunctionDef, FunctionParam
|
|
11
12
|
|
|
@@ -70,7 +71,12 @@ def render_eval_script(fn: FunctionDef, args: tuple[str, ...]) -> str:
|
|
|
70
71
|
clean for `eval "$(...)"` capture, so missing required params raise instead."""
|
|
71
72
|
values = resolve_params(fn.params, args)
|
|
72
73
|
prelude = _literal_prelude(fn.params, values)
|
|
73
|
-
|
|
74
|
+
script = f"{prelude}\n{fn.script}" if prelude else fn.script
|
|
75
|
+
# Logged, never traced with `set -x`: this text is evaluated by the *caller's*
|
|
76
|
+
# interactive shell, where enabling xtrace would persist after the function returns.
|
|
77
|
+
verbose.log(f"eval script for '{fn.key}':")
|
|
78
|
+
verbose.block(script, minimum=verbose.VERBOSE)
|
|
79
|
+
return script
|
|
74
80
|
|
|
75
81
|
|
|
76
82
|
def _bashrc_prelude(fn: FunctionDef) -> list[str]:
|
|
@@ -106,7 +112,10 @@ def render_bashrc_function(fn: FunctionDef) -> str:
|
|
|
106
112
|
|
|
107
113
|
|
|
108
114
|
def enable_bashrc_function(fn: FunctionDef) -> bool:
|
|
109
|
-
|
|
115
|
+
block = render_bashrc_function(fn)
|
|
116
|
+
verbose.log(f"patching ~/.bashrc with devstuff-fn:{fn.key}")
|
|
117
|
+
verbose.block(block)
|
|
118
|
+
return patch_bashrc(f"devstuff-fn:{fn.key}", block)
|
|
110
119
|
|
|
111
120
|
|
|
112
121
|
def disable_bashrc_function(fn: FunctionDef) -> bool:
|
|
@@ -137,13 +146,25 @@ def run_script_function(
|
|
|
137
146
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".sh", delete=False) as f:
|
|
138
147
|
f.write(content)
|
|
139
148
|
tmp = f.name
|
|
149
|
+
|
|
150
|
+
# `bash -x` at -vv traces every expanded command inside the function to stderr.
|
|
151
|
+
# A function is an opaque blob of someone else's shell until you can see that;
|
|
152
|
+
# `capture` mode gets it too, since the trace lands in the returned text.
|
|
153
|
+
cmd = ["bash", *(["-x"] if verbose.enabled(verbose.TRACE) else []), tmp, *values]
|
|
154
|
+
verbose.command(cmd)
|
|
155
|
+
verbose.trace(f"params: {_describe_params(fn.params, values)}")
|
|
156
|
+
verbose.block(content)
|
|
140
157
|
try:
|
|
141
158
|
if capture:
|
|
142
|
-
proc = subprocess.run(
|
|
143
|
-
["bash", tmp, *values], check=True, capture_output=True, text=True
|
|
144
|
-
)
|
|
159
|
+
proc = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
|
145
160
|
return ((proc.stdout or "") + (proc.stderr or "")).strip()
|
|
146
|
-
subprocess.run(
|
|
161
|
+
subprocess.run(cmd, check=True)
|
|
147
162
|
return None
|
|
148
163
|
finally:
|
|
149
164
|
os.unlink(tmp)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _describe_params(params: list[FunctionParam], values: list[str]) -> str:
|
|
168
|
+
if not params:
|
|
169
|
+
return "(none)"
|
|
170
|
+
return " ".join(f"{p.name}={v!r}" for p, v in zip(params, values, strict=True))
|
dev_setup/generic.py
CHANGED
|
@@ -10,10 +10,9 @@ from collections.abc import Callable
|
|
|
10
10
|
from dataclasses import dataclass, fields
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
|
|
13
|
+
from dev_setup import verbose
|
|
13
14
|
from dev_setup.base import Tool
|
|
14
15
|
|
|
15
|
-
_verbose: bool = False
|
|
16
|
-
|
|
17
16
|
# Auto-inferred requires per install type (re-derived on load, not persisted)
|
|
18
17
|
AUTO_REQUIRES = {
|
|
19
18
|
"npm": ["nvm"],
|
|
@@ -43,9 +42,20 @@ class UpdateStatus:
|
|
|
43
42
|
|
|
44
43
|
|
|
45
44
|
def _run(cmd: list, *, cwd: Path | None = None) -> None:
|
|
46
|
-
"""Run a command.
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
"""Run a state-changing command (install/remove/update). Raises RuntimeError on failure.
|
|
46
|
+
|
|
47
|
+
Streams output when verbose, captures when not — captured output is discarded on
|
|
48
|
+
success and surfaced as the exception message on failure, which is the only reason
|
|
49
|
+
quiet mode can say anything useful about what went wrong.
|
|
50
|
+
"""
|
|
51
|
+
verbose.command(cmd, cwd=cwd)
|
|
52
|
+
if verbose.enabled():
|
|
53
|
+
try:
|
|
54
|
+
subprocess.run(cmd, check=True, cwd=cwd)
|
|
55
|
+
except subprocess.CalledProcessError as e:
|
|
56
|
+
# The output already streamed past; repeating the whole argv in the error
|
|
57
|
+
# adds nothing the user can't see directly above it.
|
|
58
|
+
raise RuntimeError(f"exit code {e.returncode}") from e
|
|
49
59
|
else:
|
|
50
60
|
try:
|
|
51
61
|
subprocess.run(cmd, check=True, capture_output=True, text=True, cwd=cwd)
|
|
@@ -54,6 +64,26 @@ def _run(cmd: list, *, cwd: Path | None = None) -> None:
|
|
|
54
64
|
raise RuntimeError(msg) from e
|
|
55
65
|
|
|
56
66
|
|
|
67
|
+
def _probe(cmd: list, *, log_at: int = verbose.TRACE, **kwargs) -> subprocess.CompletedProcess:
|
|
68
|
+
"""Run a command whose output we capture rather than stream, and don't raise on.
|
|
69
|
+
|
|
70
|
+
Used for read-only probes (version checks, dpkg queries, …) and for the few
|
|
71
|
+
best-effort actions whose failure is deliberately ignored. Verbosity only adds
|
|
72
|
+
logging here, and by default only at -vv: probes fire constantly — one or more per
|
|
73
|
+
tool on every `list` — and would bury the actual work at -v. `log_at=verbose.VERBOSE`
|
|
74
|
+
marks the ones that are real actions the user should see at -v.
|
|
75
|
+
"""
|
|
76
|
+
kwargs.setdefault("capture_output", True)
|
|
77
|
+
verbose.command(cmd, cwd=kwargs.get("cwd"), minimum=log_at)
|
|
78
|
+
proc = subprocess.run(cmd, **kwargs)
|
|
79
|
+
if verbose.enabled(verbose.TRACE):
|
|
80
|
+
out = "".join(
|
|
81
|
+
part for part in (proc.stdout, proc.stderr) if isinstance(part, str)
|
|
82
|
+
)
|
|
83
|
+
verbose.result(proc.returncode, out)
|
|
84
|
+
return proc
|
|
85
|
+
|
|
86
|
+
|
|
57
87
|
@dataclass
|
|
58
88
|
class GenericTool(Tool):
|
|
59
89
|
key: str = ""
|
|
@@ -160,7 +190,7 @@ class GenericTool(Tool):
|
|
|
160
190
|
if cmd and shutil.which(cmd):
|
|
161
191
|
for flag in ["--version", "-v", "version"]:
|
|
162
192
|
try:
|
|
163
|
-
r =
|
|
193
|
+
r = _probe([cmd, flag], capture_output=True, text=True, timeout=5)
|
|
164
194
|
if r.returncode == 0 and r.stdout.strip():
|
|
165
195
|
return r.stdout.strip().splitlines()[0]
|
|
166
196
|
except Exception:
|
|
@@ -178,18 +208,13 @@ class GenericTool(Tool):
|
|
|
178
208
|
|
|
179
209
|
|
|
180
210
|
def _install_npm(tool: GenericTool) -> None:
|
|
181
|
-
from dev_setup import ui
|
|
182
211
|
if not tool.npm_name:
|
|
183
212
|
raise RuntimeError("npm_name not set")
|
|
184
|
-
with
|
|
185
|
-
|
|
186
|
-
["bash", "-lc", f"{_npm_init()} && npm install -g {shlex.quote(tool.npm_name)}"],
|
|
187
|
-
check=True, capture_output=True,
|
|
188
|
-
)
|
|
213
|
+
with verbose.step(f"Installing {tool.name} via npm..."):
|
|
214
|
+
_run(["bash", "-lc", f"{_npm_init()} && npm install -g {shlex.quote(tool.npm_name)}"])
|
|
189
215
|
|
|
190
216
|
|
|
191
217
|
def _install_uvx(tool: GenericTool) -> None:
|
|
192
|
-
from dev_setup import ui
|
|
193
218
|
if not tool.pip_name:
|
|
194
219
|
raise RuntimeError("pip_name not set")
|
|
195
220
|
uv = shutil.which("uv")
|
|
@@ -198,26 +223,19 @@ def _install_uvx(tool: GenericTool) -> None:
|
|
|
198
223
|
"uv is required to install uvx packages. "
|
|
199
224
|
"Install it first: devstuff install uv"
|
|
200
225
|
)
|
|
201
|
-
with
|
|
202
|
-
|
|
226
|
+
with verbose.step(f"Installing {tool.name} via uvx..."):
|
|
227
|
+
_run([uv, "tool", "install", tool.pip_name])
|
|
203
228
|
|
|
204
229
|
|
|
205
230
|
def _install_git(tool: GenericTool) -> None:
|
|
206
|
-
from dev_setup import ui
|
|
207
231
|
if not tool.git_url:
|
|
208
232
|
raise RuntimeError("git_url not set")
|
|
209
233
|
dest = _git_clone_dest(tool.git_url)
|
|
210
|
-
with
|
|
211
|
-
|
|
212
|
-
["git", "clone", "--depth=1", tool.git_url, str(dest)],
|
|
213
|
-
check=True, capture_output=True,
|
|
214
|
-
)
|
|
234
|
+
with verbose.step(f"Cloning {tool.name}..."):
|
|
235
|
+
_run(["git", "clone", "--depth=1", tool.git_url, str(dest)])
|
|
215
236
|
if tool.git_install_cmd:
|
|
216
|
-
with
|
|
217
|
-
|
|
218
|
-
["bash", "-c", tool.git_install_cmd],
|
|
219
|
-
cwd=dest, check=True, capture_output=True,
|
|
220
|
-
)
|
|
237
|
+
with verbose.step("Running install command..."):
|
|
238
|
+
_run(["bash", "-c", tool.git_install_cmd], cwd=dest)
|
|
221
239
|
|
|
222
240
|
|
|
223
241
|
def _install_apt(tool: GenericTool) -> None:
|
|
@@ -225,7 +243,7 @@ def _install_apt(tool: GenericTool) -> None:
|
|
|
225
243
|
if not tool.apt_packages:
|
|
226
244
|
raise RuntimeError("apt_packages not set")
|
|
227
245
|
ui.info(f"Installing {tool.name} via apt...")
|
|
228
|
-
|
|
246
|
+
_run_apt_update()
|
|
229
247
|
_run(["sudo", "apt-get", "install", "-y"] + tool.apt_packages.split())
|
|
230
248
|
|
|
231
249
|
|
|
@@ -250,19 +268,14 @@ def _install_bash(tool: GenericTool) -> None:
|
|
|
250
268
|
|
|
251
269
|
|
|
252
270
|
def _update_npm(tool: GenericTool, version: str | None) -> None:
|
|
253
|
-
from dev_setup import ui
|
|
254
271
|
if not tool.npm_name:
|
|
255
272
|
raise RuntimeError("npm_name not set")
|
|
256
273
|
target = f"{tool.npm_name}@{version or 'latest'}"
|
|
257
|
-
with
|
|
258
|
-
|
|
259
|
-
["bash", "-lc", f"{_npm_init()} && npm install -g {shlex.quote(target)}"],
|
|
260
|
-
check=True, capture_output=True,
|
|
261
|
-
)
|
|
274
|
+
with verbose.step(f"Updating {tool.name} via npm..."):
|
|
275
|
+
_run(["bash", "-lc", f"{_npm_init()} && npm install -g {shlex.quote(target)}"])
|
|
262
276
|
|
|
263
277
|
|
|
264
278
|
def _update_uvx(tool: GenericTool, version: str | None) -> None:
|
|
265
|
-
from dev_setup import ui
|
|
266
279
|
if not tool.pip_name:
|
|
267
280
|
raise RuntimeError("pip_name not set")
|
|
268
281
|
uv = shutil.which("uv")
|
|
@@ -272,8 +285,8 @@ def _update_uvx(tool: GenericTool, version: str | None) -> None:
|
|
|
272
285
|
"Install it first: devstuff install uv"
|
|
273
286
|
)
|
|
274
287
|
target = f"{tool.pip_name}=={version}" if version else tool.pip_name
|
|
275
|
-
with
|
|
276
|
-
|
|
288
|
+
with verbose.step(f"Updating {tool.name} via uv tool upgrade..."):
|
|
289
|
+
_run([uv, "tool", "upgrade", target])
|
|
277
290
|
|
|
278
291
|
|
|
279
292
|
def _update_apt(tool: GenericTool, version: str | None) -> None:
|
|
@@ -281,7 +294,7 @@ def _update_apt(tool: GenericTool, version: str | None) -> None:
|
|
|
281
294
|
if not tool.apt_packages:
|
|
282
295
|
raise RuntimeError("apt_packages not set")
|
|
283
296
|
packages = tool.apt_packages.split()
|
|
284
|
-
|
|
297
|
+
_run_apt_update()
|
|
285
298
|
if version:
|
|
286
299
|
if len(packages) != 1:
|
|
287
300
|
raise RuntimeError(
|
|
@@ -295,7 +308,6 @@ def _update_apt(tool: GenericTool, version: str | None) -> None:
|
|
|
295
308
|
|
|
296
309
|
|
|
297
310
|
def _update_git(tool: GenericTool, version: str | None) -> None:
|
|
298
|
-
from dev_setup import ui
|
|
299
311
|
if version:
|
|
300
312
|
raise RuntimeError(
|
|
301
313
|
"Version pinning is not supported for git tools (shallow clone). "
|
|
@@ -306,13 +318,10 @@ def _update_git(tool: GenericTool, version: str | None) -> None:
|
|
|
306
318
|
dest = _git_clone_dest(tool.git_url)
|
|
307
319
|
if not dest.exists():
|
|
308
320
|
raise RuntimeError(f"{tool.name} clone not found at {dest}")
|
|
309
|
-
with
|
|
321
|
+
with verbose.step(f"Pulling latest {tool.name}..."):
|
|
310
322
|
_run(["git", "-C", str(dest), "pull"])
|
|
311
323
|
if tool.git_install_cmd:
|
|
312
|
-
|
|
313
|
-
["bash", "-c", tool.git_install_cmd],
|
|
314
|
-
cwd=dest, check=True, capture_output=True,
|
|
315
|
-
)
|
|
324
|
+
_run(["bash", "-c", tool.git_install_cmd], cwd=dest)
|
|
316
325
|
|
|
317
326
|
|
|
318
327
|
def _update_script_url(tool: GenericTool, version: str | None) -> None:
|
|
@@ -335,7 +344,7 @@ def _update_bash(tool: GenericTool, version: str | None) -> None:
|
|
|
335
344
|
def _npm_installed_version(pkg: str) -> str:
|
|
336
345
|
import json
|
|
337
346
|
try:
|
|
338
|
-
r =
|
|
347
|
+
r = _probe(
|
|
339
348
|
["bash", "-lc", f"{_npm_init()} && npm list -g --depth=0 --json {shlex.quote(pkg)}"],
|
|
340
349
|
capture_output=True, text=True, timeout=10,
|
|
341
350
|
)
|
|
@@ -347,7 +356,7 @@ def _npm_installed_version(pkg: str) -> str:
|
|
|
347
356
|
|
|
348
357
|
def _npm_latest_version(pkg: str) -> str:
|
|
349
358
|
try:
|
|
350
|
-
r =
|
|
359
|
+
r = _probe(
|
|
351
360
|
["bash", "-lc", f"{_npm_init()} && npm view {shlex.quote(pkg)} version"],
|
|
352
361
|
capture_output=True, text=True, timeout=10,
|
|
353
362
|
)
|
|
@@ -371,7 +380,7 @@ def _uv_tool_current_version(pkg: str) -> str:
|
|
|
371
380
|
if not uv:
|
|
372
381
|
return ""
|
|
373
382
|
try:
|
|
374
|
-
r =
|
|
383
|
+
r = _probe(
|
|
375
384
|
[uv, "tool", "list", "--color", "never"], capture_output=True, text=True, timeout=15,
|
|
376
385
|
)
|
|
377
386
|
except Exception:
|
|
@@ -392,7 +401,7 @@ def _uv_outdated_map() -> dict[str, str]:
|
|
|
392
401
|
if not uv:
|
|
393
402
|
return {}
|
|
394
403
|
try:
|
|
395
|
-
r =
|
|
404
|
+
r = _probe(
|
|
396
405
|
[uv, "tool", "list", "--outdated", "--color", "never"],
|
|
397
406
|
capture_output=True, text=True, timeout=20,
|
|
398
407
|
)
|
|
@@ -423,14 +432,14 @@ def _check_update_apt(tool: GenericTool) -> UpdateStatus:
|
|
|
423
432
|
return UpdateStatus()
|
|
424
433
|
pkg = tool.apt_packages.split()[0]
|
|
425
434
|
try:
|
|
426
|
-
r =
|
|
435
|
+
r = _probe(
|
|
427
436
|
["dpkg-query", "-W", "-f=${Version}", pkg], capture_output=True, text=True, timeout=10,
|
|
428
437
|
)
|
|
429
438
|
current = r.stdout.strip() if r.returncode == 0 else ""
|
|
430
439
|
except Exception:
|
|
431
440
|
current = ""
|
|
432
441
|
try:
|
|
433
|
-
r =
|
|
442
|
+
r = _probe(["apt-cache", "policy", pkg], capture_output=True, text=True, timeout=10)
|
|
434
443
|
except Exception:
|
|
435
444
|
return UpdateStatus(current=current)
|
|
436
445
|
candidate = ""
|
|
@@ -451,7 +460,7 @@ def _check_update_git(tool: GenericTool) -> UpdateStatus:
|
|
|
451
460
|
if not dest.exists():
|
|
452
461
|
return UpdateStatus()
|
|
453
462
|
try:
|
|
454
|
-
r =
|
|
463
|
+
r = _probe(
|
|
455
464
|
["git", "-C", str(dest), "rev-parse", "--short", "HEAD"],
|
|
456
465
|
capture_output=True, text=True, timeout=10,
|
|
457
466
|
)
|
|
@@ -459,7 +468,7 @@ def _check_update_git(tool: GenericTool) -> UpdateStatus:
|
|
|
459
468
|
except Exception:
|
|
460
469
|
current = ""
|
|
461
470
|
try:
|
|
462
|
-
r =
|
|
471
|
+
r = _probe(
|
|
463
472
|
["git", "ls-remote", tool.git_url, "HEAD"], capture_output=True, text=True, timeout=10,
|
|
464
473
|
)
|
|
465
474
|
remote_full = r.stdout.split()[0] if r.returncode == 0 and r.stdout.strip() else ""
|
|
@@ -486,32 +495,28 @@ _UPDATE_CHECKERS: dict[str, Callable[[GenericTool], UpdateStatus]] = {
|
|
|
486
495
|
|
|
487
496
|
|
|
488
497
|
def _remove_npm(tool: GenericTool) -> None:
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
subprocess.run(
|
|
492
|
-
["bash", "-lc", f"{_npm_init()} && npm uninstall -g {shlex.quote(tool.npm_name)}"],
|
|
493
|
-
check=True, capture_output=True,
|
|
494
|
-
)
|
|
498
|
+
with verbose.step(f"Removing {tool.name}..."):
|
|
499
|
+
_run(["bash", "-lc", f"{_npm_init()} && npm uninstall -g {shlex.quote(tool.npm_name)}"])
|
|
495
500
|
|
|
496
501
|
|
|
497
502
|
def _remove_uvx(tool: GenericTool) -> None:
|
|
498
|
-
from dev_setup import ui
|
|
499
503
|
uv = shutil.which("uv")
|
|
500
504
|
if not uv:
|
|
501
505
|
raise RuntimeError(
|
|
502
506
|
"uv is required to remove uvx packages. "
|
|
503
507
|
"Install it first: devstuff install uv"
|
|
504
508
|
)
|
|
505
|
-
with
|
|
506
|
-
|
|
509
|
+
with verbose.step(f"Removing {tool.name}..."):
|
|
510
|
+
_run([uv, "tool", "uninstall", tool.pip_name])
|
|
507
511
|
|
|
508
512
|
|
|
509
513
|
def _remove_git(tool: GenericTool) -> None:
|
|
510
|
-
from dev_setup import ui
|
|
511
514
|
dest = _git_clone_dest(tool.git_url)
|
|
512
515
|
if tool.git_remove_cmd:
|
|
513
|
-
|
|
514
|
-
|
|
516
|
+
# Best-effort: the clone directory is removed below either way, so a remove
|
|
517
|
+
# command that fails must not abort the removal.
|
|
518
|
+
with verbose.step("Running remove command..."):
|
|
519
|
+
_probe(["bash", "-c", tool.git_remove_cmd], cwd=dest, log_at=verbose.VERBOSE)
|
|
515
520
|
if dest.exists():
|
|
516
521
|
shutil.rmtree(dest)
|
|
517
522
|
|
|
@@ -610,7 +615,7 @@ _UPDATERS: dict[str, Callable[[GenericTool, str | None], None]] = {
|
|
|
610
615
|
|
|
611
616
|
def _npm_global_installed(pkg: str) -> bool:
|
|
612
617
|
try:
|
|
613
|
-
r =
|
|
618
|
+
r = _probe(
|
|
614
619
|
["bash", "-lc", f"{_npm_init()} && npm list -g --depth=0 {shlex.quote(pkg)}"],
|
|
615
620
|
capture_output=True,
|
|
616
621
|
text=True,
|
|
@@ -620,13 +625,19 @@ def _npm_global_installed(pkg: str) -> bool:
|
|
|
620
625
|
return False
|
|
621
626
|
|
|
622
627
|
|
|
628
|
+
def _run_apt_update() -> None:
|
|
629
|
+
"""Refresh apt's package lists. Deliberately non-fatal: a failing mirror shouldn't
|
|
630
|
+
stop an install of a package that may already be cached."""
|
|
631
|
+
_probe(["sudo", "apt-get", "update", "-q"], log_at=verbose.VERBOSE)
|
|
632
|
+
|
|
633
|
+
|
|
623
634
|
def _npm_init() -> str:
|
|
624
635
|
return '. "$HOME/.nvm/nvm.sh" 2>/dev/null || true'
|
|
625
636
|
|
|
626
637
|
|
|
627
638
|
def _apt_installed(pkg: str) -> bool:
|
|
628
639
|
try:
|
|
629
|
-
r =
|
|
640
|
+
r = _probe(["dpkg", "-s", pkg], capture_output=True, text=True)
|
|
630
641
|
return "Status: install ok installed" in r.stdout
|
|
631
642
|
except Exception:
|
|
632
643
|
return False
|
|
@@ -641,8 +652,10 @@ def _download_script(url: str, *, expected_sha256: str = "") -> str:
|
|
|
641
652
|
"""Download a script over HTTPS and optionally verify its sha256."""
|
|
642
653
|
import urllib.request
|
|
643
654
|
|
|
655
|
+
verbose.log(f"downloading {url}")
|
|
644
656
|
with urllib.request.urlopen(url, timeout=30) as resp:
|
|
645
657
|
data = resp.read()
|
|
658
|
+
verbose.trace(f"downloaded {len(data)} bytes, sha256 {hashlib.sha256(data).hexdigest()}")
|
|
646
659
|
|
|
647
660
|
if expected_sha256:
|
|
648
661
|
actual = hashlib.sha256(data).hexdigest()
|
|
@@ -664,8 +677,12 @@ def _run_bash_script(script: str) -> None:
|
|
|
664
677
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".sh", delete=False) as f:
|
|
665
678
|
f.write(script)
|
|
666
679
|
tmp = f.name
|
|
680
|
+
verbose.trace(f"script → {tmp}")
|
|
681
|
+
verbose.block(script)
|
|
667
682
|
try:
|
|
668
|
-
|
|
683
|
+
# -x at -vv traces each expanded command to stderr as the script runs, which is
|
|
684
|
+
# the only way to see where a downloaded installer actually failed.
|
|
685
|
+
_run(["bash", *(["-x"] if verbose.enabled(verbose.TRACE) else []), tmp])
|
|
669
686
|
finally:
|
|
670
687
|
os.unlink(tmp)
|
|
671
688
|
|
|
@@ -686,7 +703,7 @@ def _is_simple_command(cmd: str) -> bool:
|
|
|
686
703
|
def _bash_version(key: str) -> str:
|
|
687
704
|
for flag in ["--version", "-v", "version"]:
|
|
688
705
|
try:
|
|
689
|
-
r =
|
|
706
|
+
r = _probe(
|
|
690
707
|
[
|
|
691
708
|
"bash", "-lc",
|
|
692
709
|
f'. "$HOME/.nvm/nvm.sh" 2>/dev/null; {shlex.quote(key)} {flag} 2>/dev/null',
|
|
@@ -707,13 +724,13 @@ def _check_cmd_installed(cmd: str, *, install_type: str = "") -> bool:
|
|
|
707
724
|
# Only source nvm for npm-type tools — avoids unnecessary shell overhead
|
|
708
725
|
prefix = f"{_npm_init()} && " if install_type == "npm" else ""
|
|
709
726
|
try:
|
|
710
|
-
return
|
|
727
|
+
return _probe(
|
|
711
728
|
["bash", "-lc", f"{prefix}command -v {cmd} >/dev/null 2>&1"],
|
|
712
729
|
capture_output=True,
|
|
713
730
|
).returncode == 0
|
|
714
731
|
except Exception:
|
|
715
732
|
return False
|
|
716
733
|
try:
|
|
717
|
-
return
|
|
734
|
+
return _probe(["bash", "-lc", cmd], capture_output=True).returncode == 0
|
|
718
735
|
except Exception:
|
|
719
736
|
return False
|
dev_setup/verbose.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Process-wide verbosity level and the logging helpers built on it.
|
|
2
|
+
|
|
3
|
+
Three levels, set once per process by the `-v` CLI option (`verbose.option`) or the
|
|
4
|
+
`DEVSTUFF_VERBOSE` environment variable:
|
|
5
|
+
|
|
6
|
+
0 quiet — normal output only; subprocess output is captured and shown only on failure
|
|
7
|
+
1 -v — every command devstuff runs is logged, and its output streams live
|
|
8
|
+
2 -vv — plus read-only probes, exit codes, captured output, and `bash -x` tracing
|
|
9
|
+
of script bodies (functions, install/remove scripts)
|
|
10
|
+
|
|
11
|
+
**Everything here writes to stderr, never stdout.** `devstuff run <key>` in
|
|
12
|
+
`register: eval` mode prints shell code to stdout for `eval "$(...)"` to consume, so a
|
|
13
|
+
verbose line on stdout would be executed in the user's shell. Routing the whole logger
|
|
14
|
+
to stderr makes that impossible by construction rather than by remembering to check the
|
|
15
|
+
level at each call site.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import shlex
|
|
22
|
+
from collections.abc import Generator
|
|
23
|
+
from contextlib import contextmanager
|
|
24
|
+
|
|
25
|
+
from rich.console import Console
|
|
26
|
+
|
|
27
|
+
# stderr, and no highlighting/markup: these lines carry raw command lines and script
|
|
28
|
+
# bodies, which are full of characters rich would otherwise style or eat.
|
|
29
|
+
_console = Console(stderr=True, highlight=False)
|
|
30
|
+
|
|
31
|
+
# soft_wrap keeps a logged command line or script line on one terminal line. Rich's
|
|
32
|
+
# default word-wrap breaks a long command mid-word, and what the user most wants to do
|
|
33
|
+
# with a verbose line — copy it back into a shell — stops working when it does.
|
|
34
|
+
_PRINT = {"style": "dim", "markup": False, "soft_wrap": True}
|
|
35
|
+
|
|
36
|
+
QUIET = 0
|
|
37
|
+
VERBOSE = 1
|
|
38
|
+
TRACE = 2
|
|
39
|
+
|
|
40
|
+
_level: int = 0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _from_env() -> int:
|
|
44
|
+
"""DEVSTUFF_VERBOSE=1|2 (or a bare word like "true") as a starting level.
|
|
45
|
+
|
|
46
|
+
Lets a function invoked from a script or another tool be verbose without the caller
|
|
47
|
+
threading a flag through; an explicit -v still wins because it can only raise it.
|
|
48
|
+
"""
|
|
49
|
+
raw = os.environ.get("DEVSTUFF_VERBOSE", "").strip().lower()
|
|
50
|
+
if not raw:
|
|
51
|
+
return QUIET
|
|
52
|
+
if raw.isdigit():
|
|
53
|
+
return min(int(raw), TRACE)
|
|
54
|
+
return VERBOSE if raw in ("true", "yes", "on", "y") else QUIET
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_level = _from_env()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def set_level(value: int) -> None:
|
|
61
|
+
global _level
|
|
62
|
+
_level = max(QUIET, min(TRACE, value))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def level() -> int:
|
|
66
|
+
return _level
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def enabled(minimum: int = VERBOSE) -> bool:
|
|
70
|
+
return _level >= minimum
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def log(msg: str, *, minimum: int = VERBOSE) -> None:
|
|
74
|
+
"""A verbose note. Dim, stderr, one line."""
|
|
75
|
+
if _level >= minimum:
|
|
76
|
+
_console.print(f" {msg}", **_PRINT)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def trace(msg: str) -> None:
|
|
80
|
+
"""A note only worth showing at -vv."""
|
|
81
|
+
log(msg, minimum=TRACE)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def command(cmd: list[str] | str, *, cwd: object = None, minimum: int = VERBOSE) -> None:
|
|
85
|
+
"""Log a command about to run, in a form the user could paste back into a shell."""
|
|
86
|
+
if _level < minimum:
|
|
87
|
+
return
|
|
88
|
+
text = cmd if isinstance(cmd, str) else shlex.join(str(c) for c in cmd)
|
|
89
|
+
log(f"$ {text}" + (f" (in {cwd})" if cwd else ""), minimum=minimum)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def result(returncode: int, output: str = "", *, minimum: int = TRACE) -> None:
|
|
93
|
+
"""Log a captured command's exit code and output."""
|
|
94
|
+
if _level < minimum:
|
|
95
|
+
return
|
|
96
|
+
log(f"→ exit {returncode}", minimum=minimum)
|
|
97
|
+
block(output, minimum=minimum)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def block(text: str, *, minimum: int = TRACE) -> None:
|
|
101
|
+
"""Log a multi-line body (captured output, a script) indented under the last line."""
|
|
102
|
+
if _level < minimum or not text.strip():
|
|
103
|
+
return
|
|
104
|
+
for line in text.rstrip("\n").splitlines():
|
|
105
|
+
_console.print(f" │ {line}", **_PRINT)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@contextmanager
|
|
109
|
+
def step(label: str) -> Generator[None, None, None]:
|
|
110
|
+
"""A unit of work: a spinner normally, a logged line when verbose.
|
|
111
|
+
|
|
112
|
+
The spinner has to go when verbose — it repaints its own line, so it would fight
|
|
113
|
+
with the subprocess output now streaming to the same terminal.
|
|
114
|
+
"""
|
|
115
|
+
if enabled():
|
|
116
|
+
log(label)
|
|
117
|
+
yield
|
|
118
|
+
else:
|
|
119
|
+
from dev_setup import ui
|
|
120
|
+
with ui.spinner(label):
|
|
121
|
+
yield
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def option(f):
|
|
125
|
+
"""`-v`/`-vv` for a Click command or group, setting the process-wide level.
|
|
126
|
+
|
|
127
|
+
`expose_value=False` — the level is process state read by the subprocess helpers
|
|
128
|
+
deep in `generic.py`/`function_runner.py`, not something each command body threads
|
|
129
|
+
through. Attached to both the `cli` group and individual commands so that
|
|
130
|
+
`devstuff -v install x` and `devstuff install -v x` both work; the callback takes
|
|
131
|
+
the max so the two can't cancel each other out.
|
|
132
|
+
"""
|
|
133
|
+
import click
|
|
134
|
+
|
|
135
|
+
def _callback(ctx: object, param: object, value: int) -> int:
|
|
136
|
+
if value:
|
|
137
|
+
set_level(max(level(), value))
|
|
138
|
+
return value
|
|
139
|
+
|
|
140
|
+
return click.option(
|
|
141
|
+
"-v",
|
|
142
|
+
"--verbose",
|
|
143
|
+
count=True,
|
|
144
|
+
expose_value=False,
|
|
145
|
+
is_eager=True,
|
|
146
|
+
callback=_callback,
|
|
147
|
+
help="Verbose output: -v logs and streams every command, -vv adds probes and tracing.",
|
|
148
|
+
)(f)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devstuff
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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>
|
|
@@ -193,6 +193,30 @@ The bash `./devstuff` script in the repo is a convenience runner for the git-clo
|
|
|
193
193
|
|
|
194
194
|
---
|
|
195
195
|
|
|
196
|
+
## Verbose output (`-v` / `-vv`)
|
|
197
|
+
|
|
198
|
+
Every command accepts `-v`, before or after the subcommand. devstuff is a thin layer over other
|
|
199
|
+
people's commands; this is how you see them.
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
devstuff install lazygit -v # log each command and stream its output live
|
|
203
|
+
devstuff -vv run whats-on-port 8080 # + probes, exit codes, and bash -x line tracing
|
|
204
|
+
DEVSTUFF_VERBOSE=1 devstuff update # same as -v, for scripts and CI
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
| Level | What you get |
|
|
208
|
+
|-------|--------------|
|
|
209
|
+
| *(default)* | Normal output. Command output is captured and shown only if the command fails. |
|
|
210
|
+
| `-v` | Every command devstuff runs, printed as a line you can paste back into a shell, with its output streaming live. Spinners are replaced by plain lines so they don't fight with that output. |
|
|
211
|
+
| `-vv` | Plus read-only probes (version checks, install-state checks) with their exit codes and output, the body of any script before it runs, resolved function parameters, and `bash -x` tracing of every line inside function and installer scripts. |
|
|
212
|
+
|
|
213
|
+
`-vvv` and above are the same as `-vv`.
|
|
214
|
+
|
|
215
|
+
All of it goes to **stderr**, so it never contaminates anything you pipe or capture — including
|
|
216
|
+
`eval "$(devstuff run <shell-eval function>)"`, whose stdout has to be shell code and nothing else.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
196
220
|
## Commands
|
|
197
221
|
|
|
198
222
|
### `list`
|
|
@@ -4,17 +4,18 @@ dev_setup/agent_tools.schema.json,sha256=veXnih00xW4w9yK3ruWz-npUrCnJmcQpQdFvfcu
|
|
|
4
4
|
dev_setup/agent_tools.yaml,sha256=56E70B1W7hBqjGhYhhCAGzFQvi1nUZ04cnNiqZlMjyY,4506
|
|
5
5
|
dev_setup/base.py,sha256=j2EQ5gzeGgtveWQN_BgbLcZianMENJ7PQy8EQ9J9MPE,1946
|
|
6
6
|
dev_setup/catalog.py,sha256=YST_dpTpIh08aLz1FBziywtKX_zFfhlU5fr56hgf0mA,5514
|
|
7
|
-
dev_setup/cli.py,sha256=
|
|
7
|
+
dev_setup/cli.py,sha256=Y_RbHkexTAIQN4neDQ3buo6vmtTMNdXrsYsmY0X_Om8,2976
|
|
8
8
|
dev_setup/doctor.py,sha256=JliUAgmiW2HVCWHhT76PIXTBekNXXrc0VfDMaOeLBWY,15918
|
|
9
|
-
dev_setup/function_runner.py,sha256=
|
|
9
|
+
dev_setup/function_runner.py,sha256=08F6jvB9-oOyE5ivYtRjnL16JTGsfRnkpvkjSVrJhsM,6957
|
|
10
10
|
dev_setup/functions.schema.json,sha256=VVdpH3xWsdbtqw47S1QqpBcs4O9wd3XZ_s0Q3sGwv0Q,6220
|
|
11
11
|
dev_setup/functions.yaml,sha256=IZbdIsfRkQEvgYOjEtBMYxD-TRdqKCsoF1siWTHEYtE,9173
|
|
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=6-sUq7NtPlabs9T-2qyNqAMa4E6IKfL93IRTzzMsLp8,25313
|
|
15
15
|
dev_setup/registry.py,sha256=8lrk6EXCCWk1WNeNnCGLTJ-v8rpCs7-TLYtVXgiuxnA,2243
|
|
16
16
|
dev_setup/tools.yaml,sha256=4NQIsMV_x_sUjgafLbuXzHoPpuSd8-QS9XOoQiz9DEk,26730
|
|
17
17
|
dev_setup/ui.py,sha256=Z5nNZV8MN8tAjo1ZWq7-QPZYkPKQKuM7JbmGtiuKTqM,3806
|
|
18
|
+
dev_setup/verbose.py,sha256=N3U9JAYOvFwdd8DT6W7Pqq6RCIUfbcQnPsm0O1TnvQY,5003
|
|
18
19
|
dev_setup/agent/__init__.py,sha256=znrgxUCr_y0Qp-f41S6EaTv-hoTYwoZ9mOMLVge-IJg,226
|
|
19
20
|
dev_setup/agent/approval.py,sha256=R2YnbR1fauGtV7-NW5dH1QvWvNHjR3l23DKRTCiqpEE,3625
|
|
20
21
|
dev_setup/agent/bridges.py,sha256=RX5coPXJUSkyZApxgUZghz_57ibcPrWHVz3D44S6z2Q,4814
|
|
@@ -40,12 +41,12 @@ dev_setup/commands/docs_cmd.py,sha256=1PtpwdFO3LrAxG3HyCLFn5SXVnnwqcJ8K_c9cVd8Ph
|
|
|
40
41
|
dev_setup/commands/doctor_cmd.py,sha256=8HqQ9sB3aVDKer6jbqN_Eq0XKZDXM2o48L6_vL8JAcE,3161
|
|
41
42
|
dev_setup/commands/functions_cmd.py,sha256=D9JRYMRavXbROz0XRGjHRHeFvZY-GKyvRCctJODQDBE,2856
|
|
42
43
|
dev_setup/commands/help_cmd.py,sha256=kHbzMggYVj3hS7JKUiCrBY4VtZ5p71YPoPKQ4Y-OYNU,3301
|
|
43
|
-
dev_setup/commands/install_cmd.py,sha256=
|
|
44
|
+
dev_setup/commands/install_cmd.py,sha256=AGrB0CZPT6hZ0Cnn9AgmlyFkW8F1wZufuXVvwYft_Xo,4610
|
|
44
45
|
dev_setup/commands/list_cmd.py,sha256=-BAywXxpSm4-VAlysLzlDwIqvmk_zBKhmzEObM8jigo,2847
|
|
45
|
-
dev_setup/commands/remove_cmd.py,sha256=
|
|
46
|
-
dev_setup/commands/run_cmd.py,sha256=
|
|
46
|
+
dev_setup/commands/remove_cmd.py,sha256=_KqCa2S9fQFngLUs_OpBzjkIkFKgDtKup4gQ5TqLrVI,1239
|
|
47
|
+
dev_setup/commands/run_cmd.py,sha256=e1xfWt09UALiAe03AxzdvZyYpj0V7WKk2-E_nbk9hIw,2933
|
|
47
48
|
dev_setup/commands/skills_cmd.py,sha256=j4rleYEW4lt-O2ssDwGyhwmk2Wiip64C4Z7iPT6c3y0,7482
|
|
48
|
-
dev_setup/commands/update_cmd.py,sha256=
|
|
49
|
+
dev_setup/commands/update_cmd.py,sha256=9Bdgu9wMdliawonDOsfDsjP2am-okuONStICKn9pkkg,4831
|
|
49
50
|
dev_setup/configure/__init__.py,sha256=YXkDx21lDzjEQvypoAyoEAy8srskEzc5t6PR69evPHA,3266
|
|
50
51
|
dev_setup/configure/ansible/__init__.py,sha256=huQL_tfiU1l0WI6nzjTnGZlxCQX2aH7E9n9TrfwmIpU,407
|
|
51
52
|
dev_setup/configure/ansible/detect.py,sha256=5LkdbJg5BU2lrIU4CZm_Lhr6Sm4TJMCh1sMrsRK6rgc,9531
|
|
@@ -89,7 +90,7 @@ dev_setup/configure/starship/model.py,sha256=HNkIj69DqBQFAq0VTHqPLNZT2DuBZSfF-5y
|
|
|
89
90
|
dev_setup/configure/starship/preview.py,sha256=ZaNhNA3AeHHtPnMqrJbB9XVVXDFbH1R36jm8xyYdt2U,6145
|
|
90
91
|
dev_setup/configure/starship/render.py,sha256=gDE0hbGZ9oDCR1VAZQRDm5EFYviMjj0-eSmr1EnZfn8,10262
|
|
91
92
|
dev_setup/configure/starship/wizard.py,sha256=BmWl-U6ttF79gepDhCv8UxT8ZDIMe1SWnhSkA1iIrGQ,15498
|
|
92
|
-
devstuff-2.
|
|
93
|
-
devstuff-2.
|
|
94
|
-
devstuff-2.
|
|
95
|
-
devstuff-2.
|
|
93
|
+
devstuff-2.2.0.dist-info/METADATA,sha256=Jq_Nx1lYKtqm-nRmPEdjg7R3HOte4oGhj9nL4ARUTMk,68408
|
|
94
|
+
devstuff-2.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
95
|
+
devstuff-2.2.0.dist-info/entry_points.txt,sha256=48ITy9ENdXDFrTHwAyVPUpq1MDmgFqcOisOo5mAR6OQ,53
|
|
96
|
+
devstuff-2.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|