v0.6.0 to v0.9.0

62 added, 228 removed. Audit B to B.

---
name: dev-machine-setup
- description: "Set up or tune any machine — factory laptop or daily driver — on macOS, Linux, or Windows: report what's missing, install only that, then fix PATH, duplicate runtimes, shell config. Don't use for Dockerfiles, CI images, or single package installs."
+ description: "Set up or tune any dev machine, fresh or drifted, on macOS, Linux, or Windows: report what's missing, install only that, then fix PATH, duplicate runtimes, and shell config. Don't use for Dockerfiles, CI images, or single package installs."
license: MIT
effort: high
compatibility: "macOS, Linux (Debian/Ubuntu/Fedora/Arch), Windows (winget/PowerShell). Needs network and a package manager or permission to install one. Additive by default; anything that changes a working install needs an explicit per-item yes."
metadata:
- version: 0.6.0
+ version: 0.9.0
author: "Luong NGUYEN <luongnv89@gmail.com>"
---
# Dev Machine Setup
Bring **any** machine to a clean, ready-to-develop state — a factory laptop, a half-configured work box, or a
daily driver that has drifted. Never a fresh-install-only script: phase 1 builds a **gap report** of what is
missing and misconfigured, and every later phase acts only on that report.
**Gap-driven** is the whole design. Each phase self-skips when its slice of the report is empty, so a re-run
on an already-good machine installs nothing and still verifies — idempotent by construction.
- This is an orchestrator: per-OS command tables live in `references/`, so only the current platform loads.
+ **Self-contained:** no external scripts repo is cloned. The shell config the skill deploys ships in `assets/`.
+ This is an orchestrator. Per-OS command tables and per-phase detail live in `references/`, so only the current
+ platform loads and the token budget stays on the machine in front of you.
+
## Modes
Mode comes from the **user's intent**, not from machine state — a machine with gaps is not permission to
fill them when the user only asked for a tune-up.
| Mode | Selected by | Runs |
|------|-------------|------|
| `setup` (default) | "set up this machine", "install my dev environment", "get this laptop ready" | Phases 0 → 6 |
| `tune` | "*just* optimize what's there", "don't install anything, fix my setup", "why is `claude` not found" | Phases 0, 1, 5, 6 only |
**Tie-break:** `setup` wins whenever the request mentions missing pieces *at all* — "verify what's missing
and optimize it" is `setup`. Choose `tune` only when the ask is limited to what is already installed; still
unsure once the gap report is on screen, ask once, before phase 3.
`tune` skips phases 3 and 4 entirely — never bulk-install what the user did not ask for. Phase 5 may still
install a package when that *is* the approved fix for a finding (e.g. `uv` for `python-externally-managed`).
- ## Additive vs mutating
+ ## Approvals
- The load-bearing distinction for every approval in this skill:
+ **Read `references/approvals.md` before phase 2** — it holds the rules every phase runs under, in full. The
+ four that must be in your head from the start:
- - **Additive** — installs something that is *absent*. Nothing working can break. Batch-approvable per phase.
+ - **Additive** — installs something *absent*. Nothing working can break. Batch-approvable per phase.
- **Mutating** — changes something that *already works*: upgrades, removals, `chsh`, rc-file edits,
- `curl | sh`, PATH rewrites, replacing a runtime. Needs an explicit **per-item yes**, with the risk named,
- and any rc file backed up first (`optimize.md` § top).
-
- On a daily driver, mutating steps are the real hazard — not missing packages. A prior yes never carries
- forward to another mutating item.
-
- ## Human-in-the-loop (mandatory)
-
- Every phase runs a strict five-step loop. Do not skip a step; do not batch phases into one approval.
-
- 1. **Present** — exactly what will run, as **run-blocks** (§ Presenting commands): each command, what it
- changes, and its **additive/mutating** tag.
- 2. **Approve** — wait for an explicit go-ahead. A blanket "do everything" covers additive steps only.
- 3. **Execute** — only what was approved. On failure, stop and re-present; never silently widen scope.
- 4. **Verify** — confirm the phase actually took (version checks, file presence, exit codes).
- 5. **Record** — write the outcome to the **session file** (§ Pausing and resuming) before moving on, so a
- pause, a crash, or a closed terminal never loses what was already decided and done.
-
- **Read-only probes are exempt from step 2** — `detect_env.py`, version checks, `winget list`, `brew outdated`.
- Say what you are running, then run it. The gate exists to guard *changes*; making the user approve a read that
- changes nothing trains them to rubber-stamp the approvals that matter.
-
- The session file *is* the running log (✓ / ✗ + evidence per item). The final report is built from it, never
- from memory.
-
- ## Presenting commands (run-blocks)
-
- Every command you propose ships as a **run-block**: a fenced block the user can copy whole and run without
- editing it first. Every phase, not just phase 5.
-
- **Never put a command in a table cell** — cells wrap mid-token and produce something nobody can copy. Tables
- carry `finding id · severity · what breaks today · additive/mutating` and *name* the mechanism ("Oh My Zsh
- installer, `curl | sh`"); the command goes in the block underneath.
-
- A run-block is:
-
- - **One item per block**, headed by the finding id or item name and its additive/mutating tag.
- - **Self-contained** — no assumed cwd (`cd` inside the block, or use an absolute path), no `$` prompt
- prefixes, no `<placeholders>`. If a value must come from the user (git identity, an npm prefix), **ask
- first, then present the block with it filled in.**
- - **One purpose per block.** Inspect and fix never share a block — pasting one must not run the other.
- - **Tagged with who runs it.** `you run this` for anything needing a password prompt (`chsh`), a GUI dialog
- (`xcode-select --install`), a browser login, or a shell replacement (`exec zsh -l`); otherwise
- `I can run this`. Says *where it runs*, never a substitute for additive/mutating, which says *what it risks*.
- - Followed by its **verify** block where one is worth running.
-
- Ask for approval as a numbered list, so the user answers `1` or `1,3` instead of retyping ids. Approved
- `you run this` blocks may then be concatenated into one paste-once block — built **only** from items that
- each already got their own yes, in approval order. A combined block is never the thing being approved.
-
- **Example** — two low findings, presented right:
-
- ````markdown
- | Finding | Sev | What breaks today | Tag |
- |---------|-----|-------------------|-----|
- | `login-shell-not-zsh` | low | zsh is installed but bash is still your login shell | mutating |
- | `oh-my-zsh-missing` | low | no completion/plugin baseline for zsh | additive |
-
- **1 · `login-shell-not-zsh`** — mutating · you run this (asks for your password; corporate policy or LDAP
- may refuse it, which is not a failure — we just skip it)
-
- ```bash
- chsh -s "$(command -v zsh)"
- ```
-
- **2 · `oh-my-zsh-missing`** — additive · I can run this (downloads and runs the installer from
- github.com/ohmyzsh, and creates `~/.zshrc`)
-
- ```bash
- sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
- ```
-
- Approve which — `1`, `2`, `1,2`, or `none`?
- ````
-
- ## Pausing and resuming
-
- Machine setup is long, interruptible work, so state lives on disk rather than in the conversation.
-
- **Session file:** `~/.dev-machine-setup/session.json` (PowerShell: `$HOME\.dev-machine-setup\session.json`).
- Create it at the end of phase 1 and rewrite it at every **Record** step — after each phase, and after each
- individual approve/decline/execute inside phases 2–5. Schema and the write command are in
- `references/session.md`.
-
- The user can say "pause" at any point. Answer with three things and nothing else: the session file path, what
- is left, and a **paste-once block of the remaining approved commands** so they can finish by hand if they
- never come back. Never leave a half-applied rc edit behind at a pause.
+ `curl | sh`, PATH rewrites. Needs an explicit **per-item yes**, with the risk named, and any rc file backed
+ up first. A prior yes never carries forward to another mutating item.
+ - **Five-step loop, every phase:** Present → Approve → Execute → Verify → Record. Read-only probes
+ (`detect_env.py`, version checks, `winget list`) skip the approve step; nothing that changes state does.
+ - **Run-blocks:** every proposed command ships as a copy-whole fenced block — never inside a table cell, never
+ with a `<placeholder>` or an assumed cwd, always tagged `you run this` / `I can run this`.
- Resuming needs no arguments — triggering the skill again is enough (phase 0).
+ The session file (`~/.dev-machine-setup/session.json`) *is* the running log. Write it at every Record step;
+ build the final report from it, never from memory. Pause and resume protocol: `references/approvals.md`.
## When to Use
- "set up this new laptop / fresh install"
- "install my dev environment" (Node, Python, agents)
- "optimize / clean up my dev setup", "my machine is a mess"
- "I installed X but the command isn't found"
- Windows OEM bloat, trial antivirus, preinstalled games
Don't use for: Dockerfiles, CI runners, or installing a single named package.
## Prerequisites
- Network.
- Admin/sudo (or winget) for system packages. Without it, only user-level installs are possible — say which
requests that blocks rather than failing them silently.
- - Optional clone of `https://github.com/luongnv89/inbash` — preferred source for Unix/mac scripts this user
- already maintains.
## Reference files (load only what you need)
| File | When |
|------|------|
| `scripts/detect_env.py` | Phase 1 — prints the gap report JSON (inventory + `missing` + `findings`) |
+ | `references/approvals.md` | Before phase 2 — additive/mutating, the five-step loop, run-blocks, pause/resume |
+ | `references/procedure.md` | Every phase — what each phase actually does, step by step |
| `references/detect.md` | How to run the probe without `python3` and how to read every JSON key |
| `references/windows.md` | Windows: inventory, conservative debloat, winget stack |
- | `references/macos.md` | macOS: Homebrew, Node, Python+uv, zsh |
- | `references/linux.md` | Linux: apt/dnf/pacman, NodeSource LTS, python3-pip |
+ | `references/macos.md` | macOS: Homebrew, Node, Python+uv, zsh, starship |
+ | `references/linux.md` | Linux: apt/dnf/pacman, NodeSource LTS, python3-pip, zsh, starship |
| `references/agent-clis.md` | Phase 4 — Claude Code, Codex, Pi, OpenCode install + verify |
| `references/optimize.md` | Phase 5 — one section per finding id, each tagged additive/mutating |
- | `references/session.md` | Phase 0 and every **Record** step — session-file schema, write command, reconcile rules |
+ | `references/session.md` | Phase 0 and every Record step — session-file schema, write command, reconcile rules |
| `references/report-template.md` | Phase 6 — the FINAL REPORT block and what each line must carry |
- | `references/edge-cases.md` | No python3, Windows ARM64, WSL, containers, re-runs, inbash distro limits |
+ | `references/edge-cases.md` | No python3, Windows ARM64, WSL, containers, re-runs, unsupported distros |
+ | `assets/zshrc-config` | Phases 3 and 5 — the `~/.zshrc` deployed by `oh-my-zsh-missing`; owns the theme, plugin list, and starship init |
+ | `assets/starship.toml` | Phases 3 and 5 — the prompt config deployed by `starship-config-*`; `cp` it from the skill dir, never inline its contents |
## Procedure
- Every phase runs the five-step loop above. Do not advance until the current phase verifies green, self-skips
- on an empty gap set, or is explicitly deferred by the user.
-
- ### 0. Resume check
-
- - Read `~/.dev-machine-setup/session.json`. **Absent, or `status: complete`** → say nothing, start at phase 1.
- - **Present and unfinished** → show one screen (mode, phases done, items done / approved-but-not-run /
- declined, next step) and offer **resume** (default) · **start fresh** · **discard**.
- - On resume, **always re-run `detect_env.py` first and reconcile** — the machine moved on while you were away,
- possibly because the user pasted the remaining blocks themselves. Rules in `references/session.md`.
- - Stale — `machine` mismatches this host, or the file is over 30 days old — say so and default to start fresh.
- - **Discard** deletes the file immediately; a discarded session left on disk is what makes a later resume
- replay decisions the user threw away. **Start fresh** keeps it until phase 1 overwrites it.
-
- **Phase 0 done when:** the run is resuming from a reconciled session file, or starting clean with any
- discarded session deleted.
-
- ### 1. Detect and build the gap report
-
- - **Present:** say what it does — read-only, no network, no changes; reports OS, arch, package managers,
- installed versions, what's missing, what's misconfigured. Read-only, so no approval needed.
- - **Execute:** `python3 <skill-dir>/scripts/detect_env.py`, pathed from the skill's own directory, never the
- user's cwd. Needs Python 3.9+; without `python3`, use the fallbacks in `references/detect.md`.
- - **Verify:** the JSON parses and contains `os`, `arch`, `tools`, `missing`, `findings`.
-
- Then **show the gap report** as three short lists before doing anything else:
-
- ```
- Present: git 2.55 · node v26.7 · python 3.14 · uv 0.11 · zsh · claude · codex
- Missing: baseline → uv agents → pi, opencode
- Findings: 1 high · 0 medium · 2 low (npm-global-bin-not-on-path, path-duplicates, oh-my-zsh-missing)
- ```
-
- Read **only** the matching OS reference (`windows.md` / `macos.md` / `linux.md`), and confirm the mode if the
- request was ambiguous.
-
- **Phase 1 done when:** the gap report JSON is captured, the three lists are shown, the mode is fixed, the
- matching OS reference is loaded, and the session file exists with phase 1 recorded `done`.
-
- ### 2. Debloat — fresh Windows only, opt-in
-
- Skip entirely unless the machine is Windows **and** the user asked to clean OEM junk — never on a daily
- driver, container, or remote host. Factory Windows boxes ship trial AV, OEM utilities, and partner games
- (inspired by [XFreeze](https://x.com/xfreeze/status/2090189407659999603)). **List before delete.**
-
- - **Present** the read-only inventory commands (`winget list`, `Get-AppxPackage`) as a run-block.
- - **Execute** `references/windows.md` § Inventory; build a `keep / review / remove-candidate` table.
- - Then **per removal** (mutating): one run-block per package naming what is lost, an explicit yes for that
- one package, then execute, verify it no longer lists, and record it.
-
- **Phase 2 done when:** skipped with a reason logged, or the inventory is shown and every removal is
- individually confirmed and verified.
-
- ### 3. Baseline gaps *(skipped in `tune`)*
-
- **Blocking findings first.** Four findings make installs fail or land invisibly, so they are fixed *here*
- rather than in phase 5: `no-package-manager`, `no-sudo`, `brew-bin-not-on-path`, `npm-global-bin-not-on-path`.
- Fix per `references/optimize.md`, then continue. (A missing package manager arrives as a finding, not as a
- `missing.baseline` entry.)
-
- Then act only on `missing.baseline`. **Empty list → print `✓ baseline complete — nothing to install` and skip
- to phase 4.** Never reinstall or upgrade something already present — an upgrade is mutating, and belongs to
- phase 5.
-
- | Missing item | Install command lives in |
- |--------------|--------------------------|
- | `git` (+ curl, wget, editor) | the OS reference § Baseline |
- | `zsh` / Oh My Zsh | `macos.md` / `linux.md` § Zsh (Unix only; Windows keeps PowerShell) |
- | `node`, `npm` | the OS reference § Node.js LTS |
- | `python3`, `uv` | the OS reference § Python |
-
- - **Present:** one run-block per missing item, tagged additive. Flag anything that would touch an existing
- Node or Python — that is mutating and needs its own yes.
- - **Verify:** `git --version`, `node -v`, `npm -v`, `python3 --version`, `uv --version` (Unix also
- `zsh --version`) succeed for the items just installed.
-
- **Phase 3 done when:** every item that was in `missing.baseline` verifies green, or is logged as deferred.
-
- ### 4. Agent CLI gaps *(skipped in `tune`)*
-
- Acts only on `missing.agents`, in this order: Claude Code → Codex → Pi → OpenCode. **Empty list → print
- `✓ all agent CLIs present` and skip.** Ask which the user actually wants; do not assume all four.
-
- - **Present:** one run-block per CLI from `references/agent-clis.md`, with any `curl | sh` URL visible inside
- the block (mutating — needs its own yes).
- - **Execute:** install only the requested missing ones. Requires Node on PATH from phase 3.
- - **Verify:** each prints a version (`claude --version`, `codex --version`, `pi --version`,
- `opencode --version`). If a just-installed CLI is "not found", that is
- `optimize.md#npm-global-bin-not-on-path` — carry it into phase 5 rather than reinstalling.
-
- Auth is the first interactive run of each CLI. Never write API keys into the log or into `~/.zshrc`.
-
- **Phase 4 done when:** every requested agent CLI verifies green, or is explicitly declined.
-
- ### 5. Optimize
-
- Acts on `findings[]`, highest severity first. Each finding's `fix_ref` names its section in
- `references/optimize.md`; read only the sections for findings that actually fired.
+ Full step-by-step detail for every phase is in **`references/procedure.md`** — read it at phase 0 and keep it
+ open. This table is the spine: the order, and the condition that lets you advance. Do not advance until the
+ current phase verifies green, self-skips on an empty gap set, or is explicitly deferred by the user.
- - **Present** one table — finding id · severity · what breaks today · **additive/mutating** — then one
- run-block per finding beneath it (§ Presenting commands). No command goes in the table.
- - **Approve:** ask as a numbered list. Additive fixes can be batched. **Every mutating fix needs its own
- yes** — and back up any rc file before editing it. Record each decision as it is made, not at the end.
- - **Execute** approved fixes only. A declined finding is recorded, not retried.
- - **Verify** by re-running `detect_env.py` and confirming the fixed ids are gone from `findings`. That re-run
- is the phase's proof — never verify from memory. **PATH and rc-file fixes keep reporting until a new shell
- reads them:** re-run in a fresh login shell (`zsh -l -c '<skill-dir>/scripts/detect_env.py'`, or a new
- PowerShell window); with no fresh shell available, confirm the rc file contains the line and log the finding
- as *fixed — needs new shell*.
+ | # | Phase | Done when |
+ |---|-------|-----------|
+ | 0 | Resume check | Resuming from a reconciled session file, or starting clean with any discarded session deleted |
+ | 1 | Detect and build the gap report | Gap report JSON captured, three lists shown, mode fixed, OS reference loaded, session file written |
+ | 2 | Debloat *(fresh Windows only, opt-in)* | Skipped with a reason logged, or inventory shown and every removal individually confirmed and verified |
+ | 3 | Baseline gaps *(skipped in `tune`)* | Every item in `missing.baseline` verifies green, or is logged as deferred |
+ | 4 | Agent CLI gaps *(skipped in `tune`)* | Every requested agent CLI verifies green, or is explicitly declined |
+ | 5 | Optimize | Verification re-run shows every approved fix gone from `findings`; each remaining one recorded as declined or deferred |
+ | 6 | Final report | Report printed with a `Result` line, every gap and finding accounted for, session file marked `complete` |
- Deep checks (`brew outdated`, `winget upgrade`, `npm outdated -g`) need network and are opt-in — `optimize.md`
- § Deep checks. Never volunteer disk cleanup during a setup run.
+ Three things bite often enough to belong here rather than only in the detail file:
- **Phase 5 done when:** the verification re-run shows every approved fix gone from `findings` — or logged as
- *fixed — needs new shell* — and each remaining finding is recorded as declined or deferred with its severity.
+ - **Blocking findings go first, in phase 3** — `no-package-manager`, `no-sudo`, `brew-bin-not-on-path`,
+ `npm-global-bin-not-on-path` make installs fail or land invisibly.
+ - **Phases 3 and 4 act only on `missing.*`.** Never reinstall or upgrade something already present; an upgrade
+ is mutating and belongs to phase 5.
+ - **Phase 5 verifies by re-running `detect_env.py`**, never from memory. PATH and rc-file fixes keep reporting
+ until a new shell reads them — re-run in a fresh login shell, or log the finding as *fixed — needs new shell*.
- ### 6. Final report
+ ### Zsh: one source of truth
- Nothing executes. Assemble the report from the session file (`references/report-template.md`), then set its
- `status` to `complete` so the next run starts clean instead of offering a resume.
+ `ZSH_THEME="wedisagree"`, the four-plugin list, and the `starship init zsh` line all live in
+ `assets/zshrc-config`, which this skill ships and deploys by `cp`. Never hand-write those lines into
+ `~/.zshrc` instead — edit the asset. It is a vendored **fork** of `luongnv89/inbash`'s config: upstream fixes
+ do not flow in, which is the price of having no dependency.
- **Phase 6 done when:** the report is printed with a `Result` line, every gap and finding is accounted for as
- fixed, declined, or deferred, and the session file is marked `complete`.
+ **Deploying it is not always additive.** `cp assets/zshrc-config ~/.zshrc` overwrites an existing `~/.zshrc`,
+ so it is additive only when none is present and **mutating** — backup plus its own yes — when one is. A
+ blanket "do everything" approval never covers the mutating case. Full split in
+ `optimize.md#oh-my-zsh-missing`.
## Safety
- Beyond the additive/mutating rule above:
+ Beyond the additive/mutating rule:
- Never run a third-party "debloat everything" script unattended. OEM audio/chipset tools can be load-bearing.
- Never commit secrets, and never write an API key into a shell rc file. Auth for every agent CLI is its own
interactive login after install.
- Windows ARM64 (Snapdragon / Copilot+): prefer arm64 winget packages; say so when a tool is x64-only.
- A failed step is reported, not worked around. Silently switching to `sudo`, `--force`, or
`--break-system-packages` to make a command succeed is out of scope for this skill.
## Verification report
Phase 6 prints the FINAL REPORT block from `references/report-template.md` — one line per phase, sourced from
- the session file. `Result` is decided by these three rules:
+ the session file. `Result` is one of:
- **READY** — no unresolved `high` findings, and in `setup` no baseline gap left unfilled. Declined agent CLIs
and deferred low/medium findings are still READY.
- **PARTIAL** — a step failed non-fatally, or a `high` finding was declined.
- **BLOCKED** — a required phase could not run: no package manager, no sudo for a needed system install, or
approval withheld for a step everything else depends on.
## Acceptance Criteria
- `detect_env.py` printed valid JSON with `os`, `arch`, `tools`, `missing`, `findings`, and the phase-1 gap
report was shown before anything was installed.
- Mode was fixed before phase 3; in `tune`, phases 3 and 4 installed nothing.
- - Blocking findings (`no-package-manager`, `no-sudo`, `brew-bin-not-on-path`, `npm-global-bin-not-on-path`)
- were fixed at the top of phase 3, not deferred.
+ - Blocking findings were fixed at the top of phase 3, not deferred.
- Phases 3 and 4 acted **only** on `missing.*` — nothing already present was reinstalled or upgraded.
- Every command reached the user as a run-block: none in a table cell, none with an unresolved
`<placeholder>` or an assumed cwd, each tagged `you run this` / `I can run this`.
- - The session file existed from phase 1 on, was rewritten at every Record step, and is `complete` by phase 6 —
- an interrupted run resumes by triggering the skill again.
+ - The session file existed from phase 1 on, was rewritten at every Record step, and is `complete` by phase 6.
- Every mutating step has its own recorded yes; every rc file edited has a backup path in the session file.
- - Phase 5 verified by **re-running** `detect_env.py`; the report's counts come from that re-run. On a resumed
- run that re-run and its reconcile happened before any new work.
+ - Phase 5 verified by **re-running** `detect_env.py`; the report's counts come from that re-run.
- The FINAL REPORT printed with a `Result` of READY / PARTIAL / BLOCKED and every gap and finding accounted
for as fixed, declined, or deferred.
- `quick_validate.py` exits 0 on the shipped SKILL.md.
**Expected output:** the FINAL REPORT block (`references/report-template.md`).
## Edge Cases
- No `python3`, Windows ARM64, WSL, containers and remote-SSH hosts, interrupted runs, and inbash distro limits
- live in `references/edge-cases.md`. Machine states the probe reports as findings are in `references/optimize.md`,
- keyed by finding id.
+ No `python3`, Windows ARM64, WSL, containers and remote-SSH hosts, interrupted runs, and distros outside the
+ shipped apt/dnf/pacman tables live in `references/edge-cases.md`. Machine states the probe reports as findings
+ are in `references/optimize.md`, keyed by finding id.