devstuff 1.16.0__tar.gz → 1.18.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. {devstuff-1.16.0 → devstuff-1.18.0}/.github/workflows/test-installs.yml +2 -0
  2. {devstuff-1.16.0 → devstuff-1.18.0}/CHANGELOG.md +13 -0
  3. {devstuff-1.16.0 → devstuff-1.18.0}/CLAUDE.md +127 -2
  4. {devstuff-1.16.0 → devstuff-1.18.0}/PKG-INFO +206 -2
  5. {devstuff-1.16.0 → devstuff-1.18.0}/README.md +205 -1
  6. {devstuff-1.16.0 → devstuff-1.18.0}/docs/specs/README.md +2 -0
  7. devstuff-1.18.0/docs/specs/commitizen-config/development-plan.md +80 -0
  8. devstuff-1.18.0/docs/specs/commitizen-config/specifications.md +119 -0
  9. devstuff-1.18.0/docs/specs/commitizen-config/stack-decisions.md +183 -0
  10. devstuff-1.18.0/docs/specs/starship-config/development-plan.md +74 -0
  11. devstuff-1.18.0/docs/specs/starship-config/specifications.md +164 -0
  12. devstuff-1.18.0/docs/specs/starship-config/stack-decisions.md +187 -0
  13. devstuff-1.18.0/pyproject.toml +174 -0
  14. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/cli.py +3 -0
  15. devstuff-1.18.0/src/dev_setup/commands/configure_cmd.py +123 -0
  16. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/help_cmd.py +2 -0
  17. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/install_cmd.py +19 -1
  18. devstuff-1.18.0/src/dev_setup/configure/__init__.py +62 -0
  19. devstuff-1.18.0/src/dev_setup/configure/commitizen/__init__.py +1 -0
  20. devstuff-1.18.0/src/dev_setup/configure/commitizen/detect.py +213 -0
  21. devstuff-1.18.0/src/dev_setup/configure/commitizen/model.py +510 -0
  22. devstuff-1.18.0/src/dev_setup/configure/commitizen/render.py +552 -0
  23. devstuff-1.18.0/src/dev_setup/configure/commitizen/validate.py +321 -0
  24. devstuff-1.18.0/src/dev_setup/configure/commitizen/wizard.py +613 -0
  25. devstuff-1.18.0/src/dev_setup/configure/starship/__init__.py +1 -0
  26. devstuff-1.18.0/src/dev_setup/configure/starship/fonts.py +47 -0
  27. devstuff-1.18.0/src/dev_setup/configure/starship/model.py +850 -0
  28. devstuff-1.18.0/src/dev_setup/configure/starship/preview.py +157 -0
  29. devstuff-1.18.0/src/dev_setup/configure/starship/render.py +268 -0
  30. devstuff-1.18.0/src/dev_setup/configure/starship/wizard.py +396 -0
  31. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/tools.yaml +60 -0
  32. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/ui.py +4 -2
  33. devstuff-1.18.0/tests/integration/test_commitizen_config.py +135 -0
  34. devstuff-1.18.0/tests/test_configure_commitizen.py +759 -0
  35. devstuff-1.18.0/tests/test_configure_starship.py +682 -0
  36. {devstuff-1.16.0 → devstuff-1.18.0}/uv.lock +1 -1
  37. devstuff-1.16.0/pyproject.toml +0 -75
  38. {devstuff-1.16.0 → devstuff-1.18.0}/.claude/skills/run-stuff/SKILL.md +0 -0
  39. {devstuff-1.16.0 → devstuff-1.18.0}/.claude/skills/run-stuff/smoke.sh +0 -0
  40. {devstuff-1.16.0 → devstuff-1.18.0}/.github/workflows/bump.yml +0 -0
  41. {devstuff-1.16.0 → devstuff-1.18.0}/.github/workflows/ci.yml +0 -0
  42. {devstuff-1.16.0 → devstuff-1.18.0}/.github/workflows/publish.yml +0 -0
  43. {devstuff-1.16.0 → devstuff-1.18.0}/.gitignore +0 -0
  44. {devstuff-1.16.0 → devstuff-1.18.0}/dev/Dockerfile +0 -0
  45. {devstuff-1.16.0 → devstuff-1.18.0}/dev/Dockerfile.ci +0 -0
  46. {devstuff-1.16.0 → devstuff-1.18.0}/dev/Makefile +0 -0
  47. {devstuff-1.16.0 → devstuff-1.18.0}/dev/docker-compose.yml +0 -0
  48. {devstuff-1.16.0 → devstuff-1.18.0}/dev-setup +0 -0
  49. {devstuff-1.16.0 → devstuff-1.18.0}/docs/specs/agent/development-plan.md +0 -0
  50. {devstuff-1.16.0 → devstuff-1.18.0}/docs/specs/agent/specifications.md +0 -0
  51. {devstuff-1.16.0 → devstuff-1.18.0}/docs/specs/agent/stack-decisions.md +0 -0
  52. {devstuff-1.16.0 → devstuff-1.18.0}/install.sh +0 -0
  53. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/__init__.py +0 -0
  54. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/__main__.py +0 -0
  55. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/__init__.py +0 -0
  56. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/approval.py +0 -0
  57. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/bridges.py +0 -0
  58. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/catalog.py +0 -0
  59. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/completion.py +0 -0
  60. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/config.py +0 -0
  61. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/loop.py +0 -0
  62. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/ollama.py +0 -0
  63. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/preflight.py +0 -0
  64. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/primitives.py +0 -0
  65. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/registry.py +0 -0
  66. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/sandbox.py +0 -0
  67. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/session.py +0 -0
  68. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/transcript.py +0 -0
  69. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent/wizard.py +0 -0
  70. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent_tools.schema.json +0 -0
  71. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/agent_tools.yaml +0 -0
  72. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/base.py +0 -0
  73. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/catalog.py +0 -0
  74. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/__init__.py +0 -0
  75. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/add_cmd.py +0 -0
  76. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/agent_cmd.py +0 -0
  77. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/catalog_cmd.py +0 -0
  78. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/delete_cmd.py +0 -0
  79. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/docs_cmd.py +0 -0
  80. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/functions_cmd.py +0 -0
  81. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/list_cmd.py +0 -0
  82. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/remove_cmd.py +0 -0
  83. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/run_cmd.py +0 -0
  84. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/skills_cmd.py +0 -0
  85. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/commands/update_cmd.py +0 -0
  86. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/function_runner.py +0 -0
  87. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/functions.schema.json +0 -0
  88. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/functions.yaml +0 -0
  89. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/functions_catalog.py +0 -0
  90. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/functions_registry.py +0 -0
  91. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/generic.py +0 -0
  92. {devstuff-1.16.0 → devstuff-1.18.0}/src/dev_setup/registry.py +0 -0
  93. {devstuff-1.16.0 → devstuff-1.18.0}/tests/__init__.py +0 -0
  94. {devstuff-1.16.0 → devstuff-1.18.0}/tests/integration/__init__.py +0 -0
  95. {devstuff-1.16.0 → devstuff-1.18.0}/tests/integration/conftest.py +0 -0
  96. {devstuff-1.16.0 → devstuff-1.18.0}/tests/integration/test_agent_smoke.py +0 -0
  97. {devstuff-1.16.0 → devstuff-1.18.0}/tests/integration/test_agent_tui.py +0 -0
  98. {devstuff-1.16.0 → devstuff-1.18.0}/tests/integration/test_tools.py +0 -0
  99. {devstuff-1.16.0 → devstuff-1.18.0}/tests/test_agent.py +0 -0
  100. {devstuff-1.16.0 → devstuff-1.18.0}/tests/test_agent_completion.py +0 -0
  101. {devstuff-1.16.0 → devstuff-1.18.0}/tests/test_agent_loop.py +0 -0
  102. {devstuff-1.16.0 → devstuff-1.18.0}/tests/test_agent_sandbox.py +0 -0
  103. {devstuff-1.16.0 → devstuff-1.18.0}/tests/test_agent_tools.py +0 -0
  104. {devstuff-1.16.0 → devstuff-1.18.0}/tests/test_agent_wizard.py +0 -0
  105. {devstuff-1.16.0 → devstuff-1.18.0}/tests/test_catalog.py +0 -0
  106. {devstuff-1.16.0 → devstuff-1.18.0}/tests/test_functions.py +0 -0
  107. {devstuff-1.16.0 → devstuff-1.18.0}/tests/test_generic.py +0 -0
  108. {devstuff-1.16.0 → devstuff-1.18.0}/tests/test_registry.py +0 -0
@@ -52,6 +52,7 @@ jobs:
52
52
  matrix:
53
53
  tool:
54
54
  - aws
55
+ - commitizen # uvx tool, requires uv
55
56
  - eza
56
57
  - gh
57
58
  - git-lfs
@@ -61,6 +62,7 @@ jobs:
61
62
  - java
62
63
  - lazygit
63
64
  - mkcert
65
+ - nerd-font
64
66
  - nvm
65
67
  - php
66
68
  - pi # installs nvm first via its declared `requires`
@@ -1,3 +1,16 @@
1
+ ## v1.18.0 (2026-08-01)
2
+
3
+ ### Features
4
+
5
+ - **configure**: add commitizen tool and configuration wizard
6
+
7
+ ## v1.17.0 (2026-07-31)
8
+
9
+ ### Feat
10
+
11
+ - starship config font validation and install
12
+ - **configure**: add a starship prompt setup wizard with live preview
13
+
1
14
  ## v1.16.0 (2026-07-24)
2
15
 
3
16
  ### Feat
@@ -21,8 +21,10 @@ uv run pytest tests/test_catalog.py::test_user_catalog_overrides_bundled_tool_in
21
21
  ```
22
22
 
23
23
  `pip` is not available in this dev environment — `uv pip install -e .` / `uv run` are the way in.
24
- Interactive commands (`install`, `add`, `remove`, `delete`) open questionary/click prompts and
25
- hang in non-TTY shells; don't try to drive them from a piped/non-interactive shell.
24
+ Interactive commands (`install`, `add`, `remove`, `delete`, `configure`) open questionary/click
25
+ prompts and hang in non-TTY shells; don't try to drive them from a piped/non-interactive shell.
26
+ To exercise one end-to-end anyway, fork a pty (`pty.fork()`) and write keystrokes to the fd —
27
+ that is how the starship wizard's full flow was verified.
26
28
 
27
29
  ### Integration tests (real installs, Docker-isolated)
28
30
 
@@ -60,6 +62,7 @@ src/dev_setup/
60
62
  ├── generic.py # GenericTool — the ONE engine that implements every install type
61
63
  ├── tools.yaml # Bundled built-in catalog (core/tools/languages categories)
62
64
  ├── ui.py # Rich console + questionary wrappers (spinners, prompts, styled output)
65
+ ├── configure/ # Per-tool setup wizards (see "Configurators" below)
63
66
  └── commands/ # One Click command per file: list, install, remove, add, delete, docs, catalog
64
67
  ```
65
68
 
@@ -167,6 +170,122 @@ non-interactive `bash <tmpfile>`, so `~/.bashrc`/nvm's shell init never runs on
167
170
  Not yet built: an `add` wizard and `catalog import`/`export` for functions, analogous to the
168
171
  ones tools already have.
169
172
 
173
+ ## Configurators (`configure/`) — tool-specific wizards, deliberately *not* catalog-driven
174
+
175
+ `src/dev_setup/configure/` holds per-tool setup wizards (`devstuff configure <tool>`), registered
176
+ in a `CONFIGURATORS` dict keyed by catalog tool key. There are two:
177
+ `configure/starship/{model,render,preview,wizard}.py` and
178
+ `configure/commitizen/{model,render,detect,validate,wizard}.py`.
179
+
180
+ **Why this one breaks the YAML-catalog rule.** Installation generalises into ~7 mechanisms, which
181
+ is what makes `GenericTool` possible. Configuration does not: starship's config is a TOML file of
182
+ format strings, palette tables and powerline transition glyphs; another tool's would be shell
183
+ exports or `git config` calls. Expressing this wizard in YAML would need conditional questions, an
184
+ ordered section model with colour roles, and a template language — a programming language spelled
185
+ in YAML. So configurators are Python, dispatched from a dict, the same shape as `_INSTALLERS` in
186
+ `generic.py`. Full reasoning in `docs/specs/starship-config/stack-decisions.md` (SD-1).
187
+
188
+ A configurator that finds a missing *prerequisite* should offer to install it through
189
+ `install_cmd.install_by_key(key)` rather than telling the user to run a second command — that is
190
+ what the starship wizard does for `nerd-font`.
191
+
192
+ **Adding a configurator** — a new module plus one dict entry. The module must expose
193
+ `run(*, target: Path | None = None)` (returns `None` if the user cancelled; writes nothing until
194
+ they confirm) and `config_path() -> Path`. Everything generic — the picker, install-state check,
195
+ `--list`/`--path`/`--output`, and the post-install offer in `install_cmd.py` — reads the registry,
196
+ so none of it needs touching. There is deliberately **no `configure:` field in `tools.yaml`**: user
197
+ catalogs are strictly validated, so a catalog naming a nonexistent configurator would add a
198
+ load-time failure mode for nothing.
199
+
200
+ **Within the starship configurator, `model.py` is the data and everything else reads it.**
201
+ `SECTIONS` (ordered — declaration order *is* prompt order), `PALETTES` and `PRESETS` drive both the
202
+ TOML emitter and the offline preview, which is what stops those two from drifting. Adding a section
203
+ or palette is one entry; no other file changes.
204
+
205
+ **Things learned from the real binary — don't "simplify" these away:**
206
+ - `symbol` is emitted for every section whose body contains `$symbol`, *including as an empty
207
+ string*. Omit it and starship's built-in Nerd Font glyph leaks into the `plain` preset. The
208
+ converse is unit-tested: a section carrying an icon its body can't render is a bug (that's why
209
+ `directory`, which has no `symbol` key at all, has no icon).
210
+ - Not every module spells the colour key `style` — hence `Section.style_key` (`username` wants
211
+ `style_user`). `battery` is deliberately absent: it accepts neither `style` nor `symbol` (both
212
+ live in its `[[battery.display]]` array), so it would cost three quirk fields for one section.
213
+ - `kubernetes`, `time`, `azure`, `status` and `shlvl` ship *disabled*; listing them in `format`
214
+ isn't enough, they need `disabled = false`. `os` and `git_metrics` are absent for the same
215
+ reason `battery` is: `os` takes its symbol from an `[os.symbols]` distro table, `git_metrics`
216
+ has `added_style`/`deleted_style` instead of `style`.
217
+ - Format strings are emitted as TOML **literal** strings (`'…'`) so starship's `$`, `[`, and `\[`
218
+ grammar needs no escaping. The multi-line top-level `format` is the one basic string, and it
219
+ contains no backslashes of its own beyond the line continuations.
220
+ - Powerline transitions are emitted per **run of consecutive sections sharing a palette role**, not
221
+ per section — otherwise two adjacent language segments draw an arrow between two identical
222
+ backgrounds. The three powerline presets differ *only* in the four glyphs in their `Powerline`
223
+ record (`POWERLINES` in `model.py`); nothing in the emitter knows which variant it is drawing.
224
+ - Any literal text the emitter writes into a format string is escaped with `_escape_format()`.
225
+ This is not theoretical: `success_symbol = '[$](bold fg:ok)'` made starship read `$` as the start
226
+ of a variable name, so the `plain` preset shipped with *no prompt symbol at all* until it was
227
+ caught by sweeping every combination and asserting empty stderr.
228
+ - A custom module (`custom.compose`) is a normal `Section`, but its dotted key has to be written
229
+ `${custom.compose}` in `format` — hence `Section.ref`. It also runs a shell command on **every**
230
+ prompt, so it carries a `when` guard, and anything needing the network is disqualified.
231
+ - Symbols for new sections come from `starship print-config --default` — the module's own shipped
232
+ glyph — rather than being picked from a Nerd Font chart that can't be verified without the font.
233
+ - The sample project must never gain a `bun.lock`: starship's `nodejs` module lists it as a
234
+ *negative* detector, so it silently switches the Node section off in previews.
235
+ - The live preview runs `starship prompt` with `STARSHIP_SHELL=nu`, not `bash`: for bash, starship
236
+ wraps escapes in readline's `\[`/`\]` markers, which are invisible inside a `PS1` and print
237
+ literally anywhere else. It also overrides `PWD` (starship prefers it over the real cwd) and
238
+ makes a second `--right` call, since `right_format` is not part of the left prompt.
239
+ - Every preview failure path returns `None` and degrades to the offline renderer. A preview must
240
+ never be able to end the wizard.
241
+ - The Nerd Font gate (`configure/starship/fonts.py`) is allowed to answer "don't know": without
242
+ fontconfig there is nothing to enumerate, and `None` means *stay silent* rather than warn. It
243
+ also refuses to install over SSH — the glyphs are drawn by the client's terminal — and always
244
+ says that installing a font does not repoint the terminal at it. The install itself goes through
245
+ the ordinary catalog path (`nerd-font` in `tools.yaml` → `install_cmd.install_by_key`), never a
246
+ private download (SD-10).
247
+
248
+ **Within the commitizen configurator, the object being configured is a list of commit types, not
249
+ a settings sheet.** `TYPES` in `model.py` is ordered (declaration order *is* prompt order, changelog
250
+ order and regex-alternation order), and `render.py` derives all nine `cz_customize` settings from
251
+ it — so adding a type is one `ChangeType` record and it reaches `bump_pattern`, `bump_map`,
252
+ `schema_pattern`, `change_type_map`, `change_type_order`, `commit_parser` and `questions` with no
253
+ other edit. Full reasoning in `docs/specs/commitizen-config/`.
254
+
255
+ **Things learned from the real binary — don't "simplify" these away:**
256
+ - `bump_map` is an **ordered** map and commitizen `break`s at the first key that `re.match`es, so
257
+ the two breaking-change rules (`^.+!$`, `^BREAKING[\-\ ]CHANGE`) must be emitted first. Reorder
258
+ them and `feat(api)!:` silently ships as a MINOR.
259
+ - What `bump_map`'s keys are matched against is **group 1 of `bump_pattern`** (`feat(api)!`), not
260
+ the commit message. That is why `^.+!$` works at all, and why every selected type belongs in the
261
+ pattern even when it has no map entry.
262
+ - `schema_pattern` must always accept the `bump:` prefix (`ALWAYS_ACCEPTED` in `model.py`):
263
+ `cz bump` writes its own commit with that prefix, and `cz check --rev-range` over a release
264
+ otherwise rejects commitizen's own commit. `cz_conventional_commits` accepts `bump` for exactly
265
+ this reason without ever offering it in the picker.
266
+ - `commit_parser`'s trailing `|\w+!` alternative is load-bearing: it is what keeps `docs!: …` (a
267
+ breaking change on a type with no changelog section) in the release notes at all. Verified — it
268
+ lands in an unlabelled group rather than vanishing.
269
+ - The `BREAKING CHANGE` changelog heading only collects commits with a **footer**. A `feat!:` still
270
+ bumps the major but is written up under Features, because that is the type it declared.
271
+ - Regexes are emitted as TOML **literal** strings (`'…'`) so no backslash needs doubling;
272
+ `message_template` and `schema` are the exceptions (they carry real newlines, so they are basic
273
+ strings). A user-supplied value containing a quote falls back to a basic string automatically.
274
+ - `config_path()` mirrors `commitizen.config.read_cfg`: search order *and* the rule that a file
275
+ without a `commitizen` section doesn't count — otherwise every Python project on disk looks
276
+ already-configured because it has a `pyproject.toml`.
277
+ - The `pyproject.toml` splice is line-based, so it verifies itself by parsing the result back and
278
+ comparing the settings; a mismatch returns `None` and the caller writes `.cz.toml` instead. Don't
279
+ replace that check with reasoning about which files it can handle.
280
+ - `validate.py` is the "measured, not assumed" half: it replays commits through the real
281
+ `cz bump --dry-run` in a throwaway repo. It runs on an explicit menu action plus once at save
282
+ time — not on every redraw (~3s, unlike starship's millisecond preview) — and a disagreement
283
+ *warns*, it never vetoes a save. Keep that distinction in the comments.
284
+
285
+ Not yet built: configurators for anything other than starship and commitizen, and round-tripping an
286
+ existing hand-edited config back into wizard state (the timestamped backup is the safety net
287
+ instead).
288
+
170
289
  ## Specs (`docs/specs/`)
171
290
 
172
291
  Design documents live in `docs/specs/<feature>/` — `specifications.md` (numbered, testable
@@ -258,6 +377,12 @@ Not yet built: an `add` wizard for agent tools, and `catalog import`/`export` fo
258
377
  - **No new runtime dependencies for the agent.** The Ollama transport is stdlib `urllib` against
259
378
  `/api/chat`; the REPL uses `prompt_toolkit`, already vendored via questionary. devstuff is a
260
379
  globally installed CLI, so every dependency is a cost paid by users who never run `agent`.
380
+ - **Configuration is Python, installation is YAML.** The no-per-tool-code rule covers *install
381
+ mechanisms*, which generalise; per-tool config formats don't. `configure/` is a registry of
382
+ Python wizards on purpose — see the section above before trying to fold it into `tools.yaml`.
383
+ - **A config wizard previews with the real binary, not an approximation.** `starship prompt`
384
+ against a temp config in a throwaway project is the source of truth; the offline renderer is a
385
+ labelled fallback for when the tool isn't installed (and is what unit tests exercise).
261
386
  - **Functions get a parallel catalog/registry instead of extending `GenericTool`.** The
262
387
  schemas diverge enough (no `requires` inference, a `params` list, no install/remove
263
388
  lifecycle) that folding them into the tool catalog would be lossy; some duplication with
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devstuff
3
- Version: 1.16.0
3
+ Version: 1.18.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>
@@ -188,6 +188,201 @@ for confirmation before doing this.
188
188
 
189
189
  ---
190
190
 
191
+ ### `configure`
192
+
193
+ Set up an installed tool through a guided wizard, previewing the result before anything is
194
+ written. Installing a tool that has a wizard also offers to run it there and then.
195
+
196
+ ```bash
197
+ devstuff configure # pick from the tools that have a wizard
198
+ devstuff configure starship # run starship's prompt wizard
199
+ devstuff configure --list # which tools have a wizard
200
+ devstuff configure starship --path # print the config file path
201
+ devstuff configure starship --output /tmp/try.toml # write elsewhere, leave the live config alone
202
+ ```
203
+
204
+ | Tool | What it configures |
205
+ |------|-------------------|
206
+ | `commitizen` | Commit types, what each one bumps, git tags, and changelog sections |
207
+ | `starship` | Prompt style, colour palette, which sections appear, and layout |
208
+
209
+ #### Commitizen wizard
210
+
211
+ Commitizen needs a config file before it does anything, and everything past its built-in
212
+ Conventional Commits rules means `cz_customize` — nine coupled regexes where `bump_map`'s keys
213
+ are matched against **group 1** of `bump_pattern`, the two breaking-change rules have to come
214
+ *first* because commitizen stops at the first key that matches, and `schema_pattern` has to
215
+ accept whatever `message_template` produces. Get the order wrong and `feat(api)!:` quietly ships
216
+ as a minor release.
217
+
218
+ The wizard asks about commit types and version rules, and derives all nine settings from the
219
+ answers:
220
+
221
+ | Step | Options |
222
+ |------|---------|
223
+ | **Convention** | `cz_conventional_commits` (commitizen's fixed rules — nothing to configure) · `cz_customize` (your types, your bump rules) |
224
+ | **Commit types** | Checkbox over `feat`, `fix`, `refactor`, `perf`, `docs`, `style`, `test`, `build`, `ci` (all on, matching Conventional Commits) plus `chore`, `revert`, `deps`, `security` — and you can add your own |
225
+ | **Per type** | What it bumps (`MAJOR` / `MINOR` / `PATCH` / no release), whether it appears in the changelog, under which heading, and its hotkey in `cz commit` |
226
+ | **Versioning** | Where the version lives (`commitizen`, `scm`, `pep621`, `uv`, `poetry`, `cargo`, `npm`, `composer`) · scheme (`semver`, `semver2`, `pep440`) · tag format · other files carrying the version |
227
+ | **Release** | Changelog on bump · `0.x` mode · annotated tags · GPG signing · incremental changelog · prerelease merging · `allow_abort` · hotkeys · changelog file · bump commit message |
228
+ | **Prompt** | Which questions `cz commit` asks — scope, body, footer |
229
+ | **Destination** | `pyproject.toml` (spliced in place) or a standalone `.cz.toml` |
230
+
231
+ The custom branch **starts as an exact copy of Conventional Commits** — the built-in type table
232
+ reproduces `commitizen.defaults.BUMP_MAP` and its picker, read out of the installed package and
233
+ pinned by a test. Switching to custom to add one type does not cost you the convention.
234
+
235
+ Before it asks anything, the wizard reads the project: git root, any existing commitizen config
236
+ (in commitizen's own search order, ignoring files without a `commitizen` section), the version
237
+ provider and current version from `pyproject.toml`/`package.json`/`Cargo.toml`/`composer.json`,
238
+ the latest git tag, and an existing changelog.
239
+
240
+ The review screen shows a sample `cz commit` message, the bump table, and the changelog sections:
241
+
242
+ ```
243
+ what each type does to 1.4.2
244
+
245
+ commit increment new version
246
+ feat: … MINOR 1.5.0
247
+ fix: … PATCH 1.4.3
248
+ docs: … — 1.4.2
249
+ feat!: … MAJOR 2.0.0
250
+ BREAKING CHANGE: … (footer) MAJOR 2.0.0
251
+ ```
252
+
253
+ **That table is checked against the real thing.** "Check these rules against the real cz" builds
254
+ a throwaway git repo tagged at 1.4.2, replays one commit per bump level plus both breaking forms
255
+ through `cz bump --dry-run`, and reports whether commitizen agrees rule by rule. The same check
256
+ runs automatically before saving — a disagreement is reported and asks for confirmation, but
257
+ never blocks the save. "Preview a generated changelog" runs the real `cz changelog --dry-run`
258
+ over one sample commit per type. Without `cz` installed the wizard still works; the checks say so
259
+ rather than failing.
260
+
261
+ On save:
262
+
263
+ - Into `pyproject.toml`, every `[tool.commitizen…]` table is replaced and **the rest of the file
264
+ is left byte for byte**. The result is parsed back and compared to what was meant; if it does
265
+ not match, the write falls back to a standalone `.cz.toml` and the original is untouched.
266
+ - Any existing file is copied to `<name>.bak.<timestamp>` first, and a config that wasn't written
267
+ by this wizard asks before being replaced.
268
+ - If more than one commitizen config now exists, you're told which one commitizen will actually
269
+ read — `.cz.toml` beats `pyproject.toml`, so a new dotfile can silently shadow settings you
270
+ forgot about.
271
+ - In a git repo with no `commit-msg` hook, you're offered one running `cz check`. An existing
272
+ hook is never replaced.
273
+
274
+ The output is meant to be hand-edited afterwards: commented, ordered, and with regexes written as
275
+ TOML literal strings so `^((BREAKING[\-\ ]CHANGE|feat)(\(.+\))?!?):` reads as itself instead of
276
+ with every backslash doubled.
277
+
278
+ ```toml
279
+ # Commitizen configuration
280
+ # Generated by `devstuff configure commitizen` — edit freely, or re-run the wizard.
281
+ # Convention: Custom types and bump rules · Types: feat, fix, docs, deps
282
+ # Option reference: https://commitizen-tools.github.io/commitizen/config/
283
+ [tool.commitizen]
284
+ name = 'cz_customize'
285
+ version_provider = 'pep621'
286
+ version_scheme = 'pep440'
287
+ tag_format = 'v$version'
288
+ update_changelog_on_bump = true
289
+ # …
290
+
291
+ # Prefix -> version increment. Order matters: commitizen stops at the first
292
+ # key that matches, so the two breaking-change rules have to lead.
293
+ [tool.commitizen.customize.bump_map]
294
+ '^.+!$' = 'MAJOR'
295
+ '^BREAKING[\-\ ]CHANGE' = 'MAJOR'
296
+ '^feat' = 'MINOR'
297
+ '^fix' = 'PATCH'
298
+ '^deps' = 'PATCH'
299
+ ```
300
+
301
+ Design notes, including why this one isn't catalog-driven and why the checks warn rather than
302
+ veto: [`docs/specs/commitizen-config/`](docs/specs/commitizen-config/).
303
+
304
+ #### Starship prompt wizard
305
+
306
+ Four questions, with the prompt re-rendered after each one:
307
+
308
+ | Step | Options |
309
+ |------|---------|
310
+ | **Style** | `Plain text` · `Bracketed segments` (both ASCII, any font) · `Icons` · `Icons, bracketed` (Nerd Font glyphs) · `Powerline`, `Powerline, rounded`, `Powerline, slanted` (solid colour bars) |
311
+ | **Palette** | `Terminal colours` (inherits your terminal theme) · Catppuccin Mocha · Nord · Gruvbox Dark · Tokyo Night · Dracula · Rosé Pine · Catppuccin Latte (light) |
312
+ | **Sections** | Grouped checkbox over ~35 modules — see the table below |
313
+ | **Layout** | Single line · Two lines · Two lines with shell info right-aligned (needs zsh/fish/nushell — bash has no right prompt) |
314
+
315
+ | Group | Sections |
316
+ |-------|----------|
317
+ | **Context** | username, hostname (over SSH), container (Docker/toolbox/distrobox) |
318
+ | **Location** | current directory |
319
+ | **Git** | branch, commit hash (when detached), status (dirty/ahead/behind), operation in progress |
320
+ | **Languages** | Node.js, Deno, Bun, Python, Rust, Go, Java, PHP, Ruby, Elixir, .NET, package version |
321
+ | **Infrastructure** | Docker context, **Docker Compose project**, Kubernetes context, AWS profile, Google Cloud project, Azure subscription, Terraform workspace, Nix shell |
322
+ | **Shell** | command duration, exit code of the last command, background jobs, nested shell depth, clock |
323
+
324
+ The review menu adds two content toggles: the blank line between prompts, and whether language
325
+ sections show **version numbers** or just the runtime's symbol.
326
+
327
+ If you pick a style that needs a Nerd Font and there isn't one on the machine, the wizard says
328
+ so in the style list itself (`Needs a Nerd Font — none installed here.`) and offers to install
329
+ `nerd-font` right there — once per run, whether you say yes or no. Two cases it handles rather
330
+ than pretending otherwise: over SSH it points you at [nerdfonts.com](https://www.nerdfonts.com/)
331
+ instead, because the glyphs are drawn by the terminal on *your* machine, not the one you're
332
+ configuring; and after installing it reminds you to actually select the font in your terminal's
333
+ settings, which is a preference no shell can change for you. Without `fontconfig` there is no way
334
+ to enumerate fonts, so the wizard says nothing rather than guessing.
335
+
336
+ The Compose section is a starship [custom module](https://starship.rs/config/#custom-commands)
337
+ rather than a built-in one — it reports the project name `docker compose` in that directory would
338
+ actually use (`$COMPOSE_PROJECT_NAME`, else a top-level `name:` in the compose file, else the
339
+ lowercased directory name), and it only runs at all when there is a compose file next to you.
340
+
341
+ The preview is the real thing: the candidate config is written to a temp file and rendered by
342
+ your installed `starship` binary inside a throwaway sample project (a git repo with language
343
+ marker files), so what you see is the bytes your prompt will produce. Without starship installed
344
+ the wizard still works and falls back to a labelled approximation.
345
+
346
+ A review menu then lets you revisit any answer, dump the generated TOML, or save. On save:
347
+
348
+ - Written to `$STARSHIP_CONFIG`, or `~/.config/starship.toml`.
349
+ - Any existing file is copied to `starship.toml.bak.<timestamp>` first — and if it wasn't written
350
+ by this wizard, you're asked before it's replaced.
351
+ - If `~/.bashrc` has no starship hook, you're offered one (using the same marker
352
+ `devstuff install starship` uses, so `devstuff remove starship` still cleans up).
353
+
354
+ The output is meant to be hand-edited afterwards: it's commented, ordered, and colours are
355
+ referenced through nine semantic palette roles (`dir`, `git`, `lang`, `infra`, `shell`, `ok`,
356
+ `err`, `muted`, `bar_text`), so retheming everything is nine edits in one table.
357
+
358
+ ```toml
359
+ # Starship prompt configuration
360
+ # Generated by `devstuff configure starship` — edit freely, or re-run the wizard.
361
+ # Style: Powerline · Palette: Nord · Layout: Two lines
362
+ # Module reference: https://starship.rs/config/
363
+ "$schema" = 'https://starship.rs/config-schema.json'
364
+
365
+ add_newline = true
366
+ palette = 'nord'
367
+
368
+ format = """
369
+ [](fg:dir)\
370
+ $directory\
371
+ [](fg:dir bg:git)\
372
+ $git_branch\
373
+ [](fg:git)\
374
+ $line_break\
375
+ $character"""
376
+
377
+ # Semantic colour roles — swap these to retheme every section at once.
378
+ [palettes.nord]
379
+ dir = '#81a1c1'
380
+ git = '#a3be8c'
381
+ # …
382
+ ```
383
+
384
+ ---
385
+
191
386
  ### `add`
192
387
 
193
388
  Guided wizard to register a new custom package. Supports six install types:
@@ -586,16 +781,18 @@ Optional utilities you may want on some machines.
586
781
  | Key | Name | Description | Help |
587
782
  |-----|------|-------------|------|
588
783
  | `aws` | AWS CLI | Amazon Web Services CLI v2 | `aws help` |
784
+ | `commitizen` | Commitizen | Conventional-commit prompt, semantic version bumping, and changelog generation (`devstuff configure commitizen`) | `cz --help` |
589
785
  | `eza` | eza | Modern ls replacement with git status, icons, and tree view | `eza --help` |
590
786
  | `gh` | GitHub CLI | GitHub's official CLI | `gh --help` |
591
787
  | `htop` | htop | Interactive process and resource monitor | `man htop` |
592
788
  | `mkcert` | mkcert | Zero-config local HTTPS certificates | `mkcert --help` |
789
+ | `nerd-font` | JetBrainsMono Nerd Font | Patched font supplying the icons Starship and other CLI tools draw | `fc-list \| grep -i "nerd font"` |
593
790
  | `ollama` | Ollama | Run large language models locally | `ollama --help` |
594
791
  | `php` | PHP 8.4 | PHP 8.4 + common extensions via ondrej/php PPA | `php --help` |
595
792
  | `pi` | Pi Coding Agent | AI coding agent npm package | `pi --help` |
596
793
  | `pre-commit` | pre-commit | Git hook manager for automated code quality checks | `pre-commit --help` |
597
794
  | `saml2aws` | saml2aws | SAML → AWS STS credentials CLI (Versent) | `saml2aws --help` |
598
- | `starship` | Starship | Fast, cross-shell customizable prompt | `starship --help` |
795
+ | `starship` | Starship | Fast, cross-shell customizable prompt (`devstuff configure starship`) | `starship --help` |
599
796
  | `yq` | yq | Portable command-line YAML/JSON/XML processor | `yq --help` |
600
797
 
601
798
  ### Languages
@@ -740,6 +937,13 @@ dev-setup-py/
740
937
  ├── functions_registry.py # Loads bundled + user YAML into the live function registry
741
938
  ├── function_runner.py # Param resolution + script/eval/bashrc rendering & execution
742
939
  ├── functions.yaml # Bundled built-in function catalog
940
+ ├── configure/ # Per-tool setup wizards (see "configure" above)
941
+ │ ├── __init__.py # CONFIGURATORS registry: tool key -> wizard module
942
+ │ └── starship/
943
+ │ ├── model.py # presets, palettes, sections, StarshipConfig
944
+ │ ├── render.py # starship.toml emitter + offline preview renderer
945
+ │ ├── preview.py # live preview via `starship prompt` in a sample project
946
+ │ └── wizard.py # the interactive flow, backup + save
743
947
  ├── agent_tools.yaml # Bundled agent tool catalog
744
948
  ├── agent/ # Local-model agent (see "Agent" above)
745
949
  │ ├── config.py # agent.yaml load/validate/save