@sebastienrousseau/dotfiles 0.2.501 → 0.2.502

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 (65) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +38 -8
  3. package/docs/COPYRIGHT +1 -1
  4. package/docs/index.md +2 -2
  5. package/docs/manual/00-introduction.md +1 -1
  6. package/docs/manual/03-reference/01-dot-cli.md +63 -45
  7. package/docs/manual/03-reference/02-config-files.md +2 -2
  8. package/docs/manual/03-reference/05-feature-flags.md +62 -3
  9. package/docs/manual/_toc.yml +1 -1
  10. package/docs/manual/command-index.md +12 -8
  11. package/docs/manual/index.md +66 -0
  12. package/docs/operations/COVERAGE.md +36 -2
  13. package/docs/operations/HARD_AUDIT_2026.md +631 -0
  14. package/docs/operations/REGISTRY.md +89 -0
  15. package/docs/operations/ROADMAP_2026.md +665 -0
  16. package/docs/operations/TRACEABILITY.md +3 -0
  17. package/docs/operations/VERSION_SYNC.md +4 -4
  18. package/docs/reference/POWERSHELL_PARITY.md +80 -0
  19. package/docs/registry.json +6 -0
  20. package/docs/security/CI_PINNING.md +113 -0
  21. package/docs/security/COMMIT_SIGNING.md +138 -0
  22. package/docs/security/DISCLOSURE.md +130 -0
  23. package/docs/security/KEY_ROTATION.md +81 -1
  24. package/docs/security/SCORECARD.md +74 -14
  25. package/docs/security/security-pubkey.asc +15 -0
  26. package/dot_config/fish/conf.d/direnv.fish +4 -0
  27. package/dot_config/fish/conf.d/mise-activate.fish +5 -0
  28. package/dot_config/git/hooks/executable_commit-msg +1 -1
  29. package/dot_config/shell/00-core-paths.sh.tmpl +8 -1
  30. package/dot_config/shell/README.md +1 -1
  31. package/dot_config/zsh/dot_zshrc.tmpl +57 -4
  32. package/dot_config/zsh/rc.d/30-options.zsh.tmpl +1 -1
  33. package/dot_local/bin/executable_dot +49 -9
  34. package/dot_local/bin/executable_dot-bootstrap +0 -1
  35. package/dot_local/bin/executable_dot-theme-sync +8 -8
  36. package/dot_local/bin/executable_tour +2 -2
  37. package/dot_local/share/man/man1/dot.1 +1 -1
  38. package/dot_local/share/zsh/completions/_dot +4 -0
  39. package/install.sh +44 -37
  40. package/package.json +1 -1
  41. package/scripts/ci/dot-cli-startup-bench.sh +126 -0
  42. package/scripts/ci/install-chezmoi-verified.sh +4 -1
  43. package/scripts/ci/lint-reusable-pins.sh +78 -0
  44. package/scripts/ci/run-coverage.sh +89 -0
  45. package/scripts/ci/windows-smoke-test.ps1 +136 -0
  46. package/scripts/diagnostics/doctor.sh +39 -9
  47. package/scripts/dot/commands/agent.sh +19 -22
  48. package/scripts/dot/commands/agents.sh +325 -0
  49. package/scripts/dot/commands/aliases.sh +10 -8
  50. package/scripts/dot/commands/core.sh +10 -4
  51. package/scripts/dot/commands/fleet.sh +278 -3
  52. package/scripts/dot/commands/init.sh +184 -0
  53. package/scripts/dot/commands/meta.sh +7 -4
  54. package/scripts/dot/commands/registry.sh +263 -0
  55. package/scripts/dot/commands/tools.sh +49 -1
  56. package/scripts/dot/lib/bento.sh +1 -1
  57. package/scripts/dot/lib/platform.sh +21 -8
  58. package/scripts/dot/lib/ui.sh +134 -2
  59. package/scripts/dot/lib/utils.sh +1 -1
  60. package/scripts/git-hooks/pre-commit-audit.sh +1 -1
  61. package/scripts/lib/secrets_provider.sh +32 -6
  62. package/scripts/ops/rollback.sh +14 -0
  63. package/scripts/security/check-disclosure-key-expiry.sh +110 -0
  64. package/scripts/security/lock-configs.sh +11 -2
  65. package/scripts/version-sync.sh +3 -0
@@ -30,7 +30,7 @@ Regressions are triaged within one Monday cycle: identify the check
30
30
  that dropped, either remediate or file an exception entry below with
31
31
  a rationale and an expiry date.
32
32
 
33
- ## Per-check posture (snapshot)
33
+ ## Per-check posture (snapshot, master HEAD)
34
34
 
35
35
  The checks below come from OpenSSF Scorecard's published rubric. Tick
36
36
  marks reflect this repo's posture at the time of writing — the
@@ -38,30 +38,75 @@ marks reflect this repo's posture at the time of writing — the
38
38
 
39
39
  | Check | Status | Notes |
40
40
  |---|---|---|
41
- | Branch-Protection | | `master` requires signed commits, linear history, required checks. Codified at `.github/rulesets/master.json` and `.github/branch-protection-config.json`. |
42
- | Code-Review | | Every PR requires at least one approval; CODEOWNERS in place. |
41
+ | Branch-Protection | | `master` requires signed commits, linear history, required checks (`.github/rulesets/master.json`). Scorecard can't read classic protection rules without a fine-grained PAT, so this check reports as `-1` ("internal error"). |
42
+ | Code-Review | | Single-maintainer repo; PRs are self-merged after CI. Scorecard scores 0/30 here by design — see `Open findings` below. |
43
43
  | Signed-Commits | ✓ | Enforced by pre-push hook (`scripts/git-hooks/pre-push`) and at-push by branch protection (#853 + #857). |
44
44
  | Dependency-Update-Tool | ✓ | Dependabot configured for github-actions / npm / docker / devcontainers / uv. |
45
- | Fuzzing | ⚠ | `install.sh` fuzz harness lives under `tests/fuzz/` (closes #881). Coverage is the script's CLI surface plus chaos input fixtures not full corpus fuzzing yet. |
45
+ | Fuzzing | ⚠ | `install.sh` fuzz harness lives under `tests/fuzz/` (closes #881), but it's shell-based and Scorecard's heuristic only recognizes OSS-Fuzz / ClusterFuzzLite / native Go fuzz / libFuzzer / Atheris. None support shell. Property tests under `tests/unit/functions/test_property_*.sh` cover the closest equivalent surface. |
46
46
  | License | ✓ | Apache-2.0 at repo root. |
47
- | Maintained | ✓ | Active commit cadence; the [README](../../README.md) lists the current `dotfiles_version` (v0.2.501). |
48
- | Pinned-Dependencies | | Every workflow action is pinned to a 40-char commit SHA; the lint rule under `actionlint` enforces this on PR. |
47
+ | Maintained | ✓ | Active commit cadence; the [README](../../README.md) lists the current `dotfiles_version`. |
48
+ | Pinned-Dependencies | | Closed 8 of 14 findings this cycle (every Dockerfile base + every workflow action + 2 `curl \| sh` installers + the `npm install -g npm` upgrade step). 5 residual findings stay open by design — see `Open findings`. |
49
49
  | SAST | ✓ | CodeQL (`.github/workflows/codeql.yml`) + Checkov + Grype. |
50
50
  | SBOM | ✓ | Generated per PR by `sbom-diff.yml` and per release by `security-release.yml`. |
51
51
  | Security-Policy | ✓ | `.github/SECURITY.md` + this page + `docs/security/THREAT_MODEL.md`. |
52
- | Token-Permissions | ✓ | Workflows use least-privilege `permissions:` blocks. `harden-runner` adoption (#878) tightens this further. |
52
+ | Token-Permissions | ✓ | Top-level `permissions:` blocks restricted to `contents: read`. `write` scopes scoped to the jobs that need them (#886). |
53
53
  | Vulnerabilities | ✓ | Grype gate hard-fails on `high` / `critical` on `master` (#852). |
54
54
  | Webhooks | n/a | No external webhooks configured. |
55
- | CI-Tests | ✓ | Comprehensive CI matrix (Linux + macOS Intel + Apple Silicon, optional Windows). |
56
- | CII-Best-Practices | ☐ | Not applied yet — see "Open work" below. |
55
+ | CI-Tests | ✓ | Linux + macOS Intel + Apple Silicon matrix; optional Windows. |
56
+ | CII-Best-Practices | ☐ | Not applied yet — see `Open work` below. |
57
57
  | Dangerous-Workflow | ✓ | No `pull_request_target` with checkout-PR-code anti-pattern. |
58
58
  | Packaging | ✓ | `npm publish --provenance` via OIDC (`.github/workflows/npm-publish.yml`); `policy-bundle-release.yml` for the policy artifact. |
59
+ | Signed-Releases | ⚠ | SLSA provenance + minisig attached via `security-release.yml`; Cosign keyless signing tracked at #876. |
60
+
61
+ ## Open findings
62
+
63
+ The eight items below are surfaced as alerts at
64
+ <https://github.com/sebastienrousseau/dotfiles/security/code-scanning>.
65
+ Each row gives the dismissal **reason** and the exact **comment text**
66
+ to paste when triaging.
67
+
68
+ ### Bucket 1 — Dismiss in the UI (5 items)
69
+
70
+ Scorecard flags every `npm install`, `pip install`, `go install`, and
71
+ `curl | sh` invocation under the `Pinned-Dependencies` check. The five
72
+ below either belong to user-facing "update everything" aliases or are
73
+ false positives in Scorecard's regex.
74
+
75
+ | Path | Reason | Comment to paste |
76
+ |---|---|---|
77
+ | `.chezmoitemplates/aliases/legal/legal.aliases.sh:64` | **Won't fix** | User-facing convenience alias — `go install <fortune-style joke command>@latest`. Pinning by SHA would require users to update the alias manually on every upstream change. Intentional. |
78
+ | `.chezmoitemplates/aliases/update/update.aliases.sh:173` | **Won't fix** | User-facing convenience alias — bulk-update wrapper around `npm`. The alias's purpose is to update everything; pinning defeats it. Intentional. |
79
+ | `dot_local/bin/executable_update:99` | **Won't fix** | `dot_local/bin/executable_update` is the user-invoked "update everything" command. The literal point of `npm update -g` is to update to whatever's latest. Pinning defeats it. Intentional. |
80
+ | `scripts/dot/commands/tools.sh:121` | **False positive** | `npm install --package-lock-only --ignore-scripts --silent` generates a lockfile and does NOT fetch packages. Scorecard's regex matches `npm install` blindly; this invocation has no supply-chain surface. |
81
+ | `install/provision/run_onchange_10-linux-packages.sh.tmpl:369` | **Won't fix** | Already exact-version-pins `aider-chat==0.86.2`. Scorecard wants sha256-hash pinning, which would require a ~2,700-line lockfile covering all 108 transitive deps and re-generated on every aider release — disproportionate maintenance for a single optional provisioning install. |
82
+
83
+ ### Bucket 2 — Architectural (1 item)
84
+
85
+ | Alert | Why architectural |
86
+ |---|---|
87
+ | `CodeReviewID` (high) — score 0 | Scorecard requires every merged PR to record an approval review by a different GitHub user than the author. This repo is single-maintainer; PRs are self-merged after CI. The fix path is either (a) onboard a co-maintainer who reviews PRs before merge, or (b) accept the finding. Dismiss as **Won't fix** with comment: "Single-maintainer project — no second reviewer available. CI gates (47 required checks per PR) substitute for human review." |
88
+
89
+ ### Bucket 3 — External action (2 items)
90
+
91
+ | Alert | Action |
92
+ |---|---|
93
+ | `FuzzingID` (medium) | Dismiss as **Won't fix** with comment: "Repo is bash + Go-template + Lua. ClusterFuzzLite / OSS-Fuzz / native Go fuzz / libFuzzer / Atheris (the frameworks Scorecard recognises) all target compiled languages, none support shell. The `tests/fuzz/fuzz_install.sh` harness + property tests under `tests/unit/functions/test_property_*.sh` cover the equivalent surface." |
94
+ | `CIIBestPracticesID` (low) | Apply at <https://www.bestpractices.dev/projects/new>. ~67 self-attested questions, free, 1-2 hours. After silver-tier approval, paste the badge into `README.md` next to the existing Scorecard badge. Most criteria already met (signed commits, CI, security policy, license). |
95
+
96
+ ## Closed this cycle
97
+
98
+ | Date | Score | Alerts open | Change |
99
+ |---|---|---|---|
100
+ | 2026-05-14 | 6.5 | 28 | First clean publish after fixing the scorecard.yml `uses:`-only restriction (#885). Findings had been hidden by publish-step 400s until then. |
101
+ | 2026-05-14 | 6.5 | 11 | Closed 17 of 28: 10× TokenPermissions (#886), 6× Dockerfile bases (#886), 1× gitleaks fixture (#884). |
102
+ | 2026-05-14 | 6.5 | 9 | Closed 2× `curl \| sh` installers (#888). |
103
+ | 2026-05-14 | 6.5 | 8 | Closed 1× `npm install -g npm@…` via Node 24 bump (#889). |
59
104
 
60
105
  ## Open work
61
106
 
62
- - Apply for the [OpenSSF Best Practices badge (CII)](https://www.bestpractices.dev/) and embed it next to the Scorecard badge. (Maintainer action needs the project to register with the BestPractices.dev site.)
63
- - Once `harden-runner` is in `block` mode across all jobs (#878), the **Token-Permissions** check should hit its maximum.
64
- - Once Cosign keyless signing is wired into the release pipeline (#876), the **Signed-Releases** check should hit its maximum.
107
+ - **Apply for the OpenSSF Best Practices Badge**see Bucket 3 above.
108
+ - **Wire Cosign keyless signing into the release pipeline (#876)** should push `Signed-Releases` to its maximum.
109
+ - **`harden-runner` block-mode adoption (#878)** should tighten the `Token-Permissions` check further.
65
110
 
66
111
  ## Exceptions
67
112
 
@@ -72,9 +117,24 @@ marks reflect this repo's posture at the time of writing — the
72
117
  If you add an exception, include the check name, an expiry date
73
118
  (don't allow indefinite), and the rationale. Re-evaluate every quarter.
74
119
 
120
+ ## Refreshing this document
121
+
122
+ ```bash
123
+ # Re-count open alerts:
124
+ gh api 'repos/sebastienrousseau/dotfiles/code-scanning/alerts?state=open&per_page=50' \
125
+ --jq '[.[].rule.id] | group_by(.) | map({rule: .[0], count: length})'
126
+
127
+ # Trigger Scorecard manually:
128
+ gh workflow run scorecard.yml --ref master
129
+ ```
130
+
131
+ If a new category appears, add a row to the matching bucket. If a
132
+ remediation closes one of the rows above, delete the row.
133
+
75
134
  ## References
76
135
 
77
- - `.github/workflows/scorecard.yml` — the scanner.
136
+ - `.github/workflows/scorecard.yml` — the scanner workflow (split into
137
+ `analysis` + `track-regression` jobs per #885).
78
138
  - [Scorecard project](https://github.com/ossf/scorecard).
79
139
  - [Scorecard checks reference](https://github.com/ossf/scorecard/blob/main/docs/checks.md).
80
- - Issue [#869](https://github.com/sebastienrousseau/dotfiles/issues/869).
140
+ - Tracking issue [#869](https://github.com/sebastienrousseau/dotfiles/issues/869).
@@ -0,0 +1,15 @@
1
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
2
+
3
+ mDMEagg20hYJKwYBBAHaRw8BAQdAk2HMi5IFTHBGhCtSFzqwsyuVb7mO0iIhoUXR
4
+ mTv/PNK0R1NlYmFzdGllbiBSb3Vzc2VhdSAoRG90ZmlsZXMgU2VjdXJpdHkpIDxz
5
+ ZWN1cml0eUBzZWJhc3RpZW5yb3Vzc2VhdS5jb20+iLUEExYKAF0WIQRVr602T9nb
6
+ OBnmHwyNaI+vqRRGkwUCagg20hsUgAAAAAAEAA5tYW51MiwyLjUrMS4xMiwwLDMC
7
+ GwMFCQWjmoAFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQjWiPr6kURpOH
8
+ qQEA4gl4pfhkR9Y3IM6oMHydavrx3CE6mX4YRIkYJ7fYj4UA/iO/54bdjG+wmIrE
9
+ cQ077+ijTTCMnHrosn3E13oQ6NIJuDgEagg20hIKKwYBBAGXVQEFAQEHQKdKJLxe
10
+ xsXBXELbxGHUz2asoTuOnsKk8WEdOyESH3dnAwEIB4iUBBgWCgA8FiEEVa+tNk/Z
11
+ 2zgZ5h8MjWiPr6kURpMFAmoINtIbFIAAAAAABAAObWFudTIsMi41KzEuMTIsMCwz
12
+ AhsMAAoJEI1oj6+pFEaTUw0A/14oNMWsx1gcyx9wl/vy+OgEXoOksxSV+FetAI2Q
13
+ Q7qOAP9pohjVAi40dqfBXv3vet7CV0V4p2kQ+qpQTOPTVafkDA==
14
+ =UtsL
15
+ -----END PGP PUBLIC KEY BLOCK-----
@@ -0,0 +1,4 @@
1
+ # Shadow vendor `/opt/homebrew/share/fish/vendor_conf.d/direnv.fish`.
2
+ # fish dedupes conf.d files by basename — user conf.d wins. Direnv is
3
+ # loaded lazily by `_dotfiles_async_init` in init.fish via `_cached_eval
4
+ # direnv hook fish`, avoiding the unconditional cost on every shell start.
@@ -0,0 +1,5 @@
1
+ # Shadow vendor `/opt/homebrew/share/fish/vendor_conf.d/mise-activate.fish`.
2
+ # fish dedupes conf.d files by basename — user conf.d wins. Mise is loaded
3
+ # lazily by `_dotfiles_async_init` in init.fish via `_cached_eval mise
4
+ # activate fish`, which is ~10× faster than the vendor's eager hook-env
5
+ # call on every shell start (saves ~140ms in fish startup).
@@ -106,7 +106,7 @@ detect_ai_agent() {
106
106
  }
107
107
 
108
108
  # ── Branding Signature ────────────────────────────────────────────────
109
- sig_file="/Users/seb/.euxis/data/config/branding/signature.txt"
109
+ sig_file="${HOME}/.euxis/data/config/branding/signature.txt"
110
110
  msg_file="${1:-}"
111
111
 
112
112
  [[ -n "$msg_file" && -f "$msg_file" ]] || exit 0
@@ -41,7 +41,14 @@ path_prepend() {
41
41
  }
42
42
 
43
43
  # 1. System Paths (Prepend, but will be pushed down by later prepends)
44
- PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${PATH}"
44
+ # Use path_prepend so existing entries are removed first — avoids the
45
+ # duplicate-system-path bloat that `PATH="...:${PATH}"` produces when
46
+ # the parent shell already had these (e.g. macOS launchd default PATH).
47
+ path_prepend "/sbin"
48
+ path_prepend "/usr/sbin"
49
+ path_prepend "/bin"
50
+ path_prepend "/usr/bin"
51
+ path_prepend "/usr/local/bin"
45
52
 
46
53
  # 2. Homebrew Paths (Prepend -> Higher Priority)
47
54
  path_prepend "/opt/homebrew/sbin"
@@ -27,7 +27,7 @@ Welcome to your universally compatible, high-performance dotfiles configuration,
27
27
  To install these dotfiles on a new machine, run:
28
28
 
29
29
  ```bash
30
- sh -c "$(curl -fsSL https://raw.githubusercontent.com/sebastienrousseau/dotfiles/v0.2.501/install.sh)"
30
+ sh -c "$(curl -fsSL https://raw.githubusercontent.com/sebastienrousseau/dotfiles/v0.2.502/install.sh)"
31
31
  ```
32
32
 
33
33
  This command will:
@@ -217,11 +217,17 @@ _cached_eval_impl() {
217
217
  return 1
218
218
  fi
219
219
 
220
- print -r -- "$init_output" > "${cache}.tmp.$$"
221
- mv "${cache}.tmp.$$" "$cache"
220
+ # `mktemp` instead of `${cache}.tmp.$$` — under PID recycling (very
221
+ # rare but possible) two concurrent shells with the same $$ could
222
+ # otherwise race on the same temp file. mktemp gives a unique name.
223
+ # Use `>|` so we overwrite the just-created mktemp file even when the
224
+ # user has `setopt noclobber` (see dot_config/shell/05-core-safety.sh).
225
+ local _tmp _tmp_pin
226
+ _tmp="$(mktemp "${cache}.XXXXXX")" || return 1
227
+ print -r -- "$init_output" >| "$_tmp" && mv "$_tmp" "$cache" || { rm -f "$_tmp"; return 1; }
222
228
  if [[ -n "$real_bin" ]]; then
223
- print -r -- "$real_bin" > "${pin}.tmp.$$"
224
- mv "${pin}.tmp.$$" "$pin"
229
+ _tmp_pin="$(mktemp "${pin}.XXXXXX")" || return 1
230
+ print -r -- "$real_bin" >| "$_tmp_pin" && mv "$_tmp_pin" "$pin" || { rm -f "$_tmp_pin"; return 1; }
225
231
  fi
226
232
  source "$cache"
227
233
  { zcompile "$cache" } &!
@@ -245,6 +251,48 @@ _cached_eval_clear() {
245
251
  printf 'Cleared %d cached eval file(s) from %s\n' "$count" "$cache_dir"
246
252
  }
247
253
 
254
+ # Lazy-load helper for user-supplied slow tools.
255
+ #
256
+ # Usage in ~/.config/zsh/rc.d/99-local.zsh:
257
+ # _dot_lazy nvm nvm node npm npx # nvm.sh sourced on first nvm/node/npm/npx call
258
+ # _dot_lazy pyenv pyenv python python3 pip pip3
259
+ # _dot_lazy conda conda
260
+ #
261
+ # First arg is the "trigger" — also the function name that bootstraps
262
+ # the real tool. Remaining args are the commands that, when invoked,
263
+ # bootstrap the trigger then re-exec the command. Bootstrap functions
264
+ # must export `_DOT_LAZY_<TRIGGER>_LOADED` so subsequent calls bypass
265
+ # the stub.
266
+ #
267
+ # Why this matters: every `eval "$(slowtool init zsh)"` at startup
268
+ # costs 50-200ms; lazy-load defers that to first use, keeping
269
+ # `dot version` cold-start under 100ms.
270
+ _dot_lazy() {
271
+ local trigger="$1"; shift || return 1
272
+ local cmd
273
+ for cmd in "$trigger" "$@"; do
274
+ # shellcheck disable=SC2139,SC2140
275
+ eval "$cmd() {
276
+ local _trigger=\"$trigger\"
277
+ local _loaded_var=\"_DOT_LAZY_\${(U)_trigger//[^A-Z0-9]/_}_LOADED\"
278
+ if [[ -z \${(P)_loaded_var} ]]; then
279
+ # Unset every stub so the real binary takes over.
280
+ local _c
281
+ for _c in \"$trigger\" $@; do
282
+ unset -f -- \"\$_c\" 2>/dev/null
283
+ done
284
+ # User must define a __dot_lazy_init_<trigger> bootstrap.
285
+ local _init=\"__dot_lazy_init_\${_trigger//-/_}\"
286
+ if (( \${+functions[\$_init]} )); then
287
+ \"\$_init\" || return \$?
288
+ fi
289
+ export \"\$_loaded_var=1\"
290
+ fi
291
+ command \"$cmd\" \"\$@\"
292
+ }"
293
+ done
294
+ }
295
+
248
296
  # Lightweight precmd hook manager (faster than add-zsh-hook)
249
297
  _dotfiles_add_precmd() {
250
298
  local fn="$1"
@@ -603,6 +651,11 @@ _dotfiles_async_init() {
603
651
  esac
604
652
  fi
605
653
  done
654
+ # mise/atuin/etc emit `export PATH=...` which bypasses zsh's -U flag on
655
+ # the `path` array — re-apply uniqueness to drop any duplicates baked
656
+ # into the cached output (e.g. system paths inherited from a dirty
657
+ # parent shell).
658
+ typeset -U path
606
659
  }
607
660
 
608
661
  # Deferred Hydration for Zero-Jank Experience
@@ -64,7 +64,7 @@ fi
64
64
  {{- end }}
65
65
  {{- /* When node_manager = "mise", Node.js is handled by mise activate in dot_zshrc.tmpl */}}
66
66
 
67
- # Completions: pre-warmed by `dot prewarm`, generated on-demand as fallback
67
+ # Completions: generated on-demand on first interactive shell and cached
68
68
  if [[ "${DOTFILES_FAST:-0}" != "1" ]] && [[ "${DOTFILES_ULTRA_FAST:-0}" != "1" ]]; then
69
69
  [[ ! -f "${ZSH_COMPLETIONS_DIR}/_gh" ]] && command -v gh >/dev/null &&
70
70
  command gh completion -s zsh >| "${ZSH_COMPLETIONS_DIR}/_gh"
@@ -3,7 +3,7 @@
3
3
  # Copyright (c) 2015-2026 Dotfiles. All rights reserved.
4
4
  set -euo pipefail
5
5
 
6
- # Dotfiles CLI Entry Point - v0.2.501
6
+ # Dotfiles CLI Entry Point - v0.2.502
7
7
 
8
8
  # Ensure XDG Base Directory variables are set
9
9
  export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
@@ -21,7 +21,7 @@ if [ -e "${HOME}/.nix-profile/etc/profile.d/nix.sh" ]; then
21
21
  . "${HOME}/.nix-profile/etc/profile.d/nix.sh"
22
22
  fi
23
23
 
24
- VERSION="0.2.501"
24
+ VERSION="0.2.502"
25
25
 
26
26
  COMMAND="${1:-}"
27
27
  if [ -n "${1:-}" ]; then
@@ -160,6 +160,7 @@ Start Here|sync|Apply dotfiles to this machine.|Use after clone or after changes
160
160
  Start Here|doctor|Check the environment and surface issues.|Use when something feels off.
161
161
  Start Here|learn|Open the guided tour.|Best first stop for new users.
162
162
  Start Here|help|Show the overview or help for one command.|Use `dot help <command>` for detail.
163
+ Daily Use|init|Bootstrap a foreign dotfiles repo through chezmoi + dot.|Usage: `dot init <github-user>`.
163
164
  Daily Use|status|Show local drift.|See what changed since the last apply.
164
165
  Daily Use|diff|Preview pending changes.|Safe way to inspect before applying.
165
166
  Daily Use|edit|Open the source directory.|Use the configured editor.
@@ -172,6 +173,8 @@ AI and Agents|ai|Show installed AI tools.|Launches the AI status view.
172
173
  AI and Agents|mcp|Inspect MCP policy and registry.|Supports `--strict|-s` and `--json|-j`.
173
174
  AI and Agents|mode|Show or set the agent profile.|Profiles: ask, plan, apply, audit.
174
175
  AI and Agents|agent|Show agent metadata, logs, checkpoints, and conformance.|Use for card, log, checkpoint, and conformance.
176
+ AI and Agents|agents|Multi-harness agent context (AGENTS.md sync, Cursor/Codex stubs).|Subcommands: list, check, render.
177
+ Fleet|registry|Browse reusable dotfile modules from the dot registry.|Subcommands: list, search, info, install, url, set-url.
175
178
  Fleet|fleet|Show fleet node status, drift, and namespace.|Enterprise fleet management.
176
179
  Configuration|env|Show managed tool versions.|Backed by mise.
177
180
  Configuration|profile|Show or switch the active profile.|Use for machine-specific behavior.
@@ -269,7 +272,6 @@ health|Run the health dashboard with optional fixes.|dot health -v\ndot health -
269
272
  perf|Measure shell performance and emit structured timing.|dot perf -j -r 1\ndot perf -p\ndot perf -t 200
270
273
  security-score|Assess workstation security posture.|dot security-score -j\ndot security-score -q
271
274
  snapshot|Capture the current workstation snapshot.|dot snapshot -b\ndot snapshot -b -f
272
- verify|Run post-change verification checks.|dot verify\ndot verify -s
273
275
  restore|Restore from a backup or git ref.|dot restore -l\ndot restore -L\ndot restore -g HEAD~1
274
276
  rollback|Restore a previous configuration snapshot.|dot rollback
275
277
  lint|Run shell quality checks for the repo.|dot lint\ndot lint -c\ndot lint -f
@@ -304,15 +306,14 @@ _dot_command_routes() {
304
306
  cat <<'EOF'
305
307
  sync|core
306
308
  apply|core
309
+ init|init
307
310
  update|core
308
311
  add|core
309
312
  diff|core
310
313
  status|core
311
- remove|core
312
314
  cd|core
313
315
  edit|core
314
316
  commit|core
315
- clean-cache|core
316
317
  doctor|diagnostics
317
318
  scorecard|diagnostics
318
319
  score|diagnostics
@@ -328,12 +329,10 @@ heal|diagnostics
328
329
  health|diagnostics
329
330
  health-check|diagnostics
330
331
  smoke-test|diagnostics
331
- verify|diagnostics
332
332
  attest|diagnostics
333
333
  attestation|diagnostics
334
334
  security-score|diagnostics
335
335
  drift|diagnostics
336
- benchmark|diagnostics
337
336
  intelligence|diagnostics
338
337
  conflicts|diagnostics
339
338
  locks|diagnostics
@@ -385,7 +384,6 @@ lock-screen|security
385
384
  usb-safety|security
386
385
  upgrade|meta
387
386
  cache-refresh|meta
388
- prewarm|meta
389
387
  docs|meta
390
388
  learn|meta
391
389
  keys|meta
@@ -393,7 +391,9 @@ sandbox|meta
393
391
  mcp|meta
394
392
  mode|meta
395
393
  agent|meta
394
+ agents|agents
396
395
  fleet|fleet
396
+ registry|registry
397
397
  patterns|patterns
398
398
  manual|manual
399
399
  search|search
@@ -539,6 +539,8 @@ if [[ -z "$COMMAND" ]]; then
539
539
  exit 0
540
540
  fi
541
541
 
542
+ # `|| true` is intentional: an unknown command yields an empty route, which falls
543
+ # through the case below into the user-command path that emits the proper error.
542
544
  route="$(_dot_command_route "$COMMAND" || true)"
543
545
 
544
546
  case "$route" in
@@ -546,6 +548,42 @@ case "$route" in
546
548
  _dispatch "$route" "$@"
547
549
  ;;
548
550
 
551
+ agents)
552
+ if [[ -n "$_SOURCE_DIR" ]] && [[ -f "$_SOURCE_DIR/scripts/dot/commands/agents.sh" ]]; then
553
+ # shellcheck disable=SC1091
554
+ source "$_SOURCE_DIR/scripts/dot/commands/agents.sh"
555
+ cmd_agents "$@"
556
+ exit $?
557
+ else
558
+ ui_fail "agents.sh not found"
559
+ exit 1
560
+ fi
561
+ ;;
562
+
563
+ init)
564
+ if [[ -n "$_SOURCE_DIR" ]] && [[ -f "$_SOURCE_DIR/scripts/dot/commands/init.sh" ]]; then
565
+ # shellcheck disable=SC1091
566
+ source "$_SOURCE_DIR/scripts/dot/commands/init.sh"
567
+ cmd_init "$@"
568
+ exit $?
569
+ else
570
+ ui_fail "init.sh not found"
571
+ exit 1
572
+ fi
573
+ ;;
574
+
575
+ registry)
576
+ if [[ -n "$_SOURCE_DIR" ]] && [[ -f "$_SOURCE_DIR/scripts/dot/commands/registry.sh" ]]; then
577
+ # shellcheck disable=SC1091
578
+ source "$_SOURCE_DIR/scripts/dot/commands/registry.sh"
579
+ cmd_registry "$@"
580
+ exit $?
581
+ else
582
+ ui_fail "registry.sh not found"
583
+ exit 1
584
+ fi
585
+ ;;
586
+
549
587
  manual)
550
588
  if [[ -n "$_SOURCE_DIR" ]] && [[ -f "$_SOURCE_DIR/scripts/dot/commands/manual.sh" ]]; then
551
589
  exec bash "$_SOURCE_DIR/scripts/dot/commands/manual.sh" "$@"
@@ -586,7 +624,9 @@ case "$route" in
586
624
  *)
587
625
  _user_cmd="${XDG_CONFIG_HOME:-$HOME/.config}/dotfiles/commands/${COMMAND}.sh"
588
626
  if [[ -f "$_user_cmd" ]]; then
589
- source "$_user_cmd" "$@"
627
+ # Run in a subshell so a user `exit` doesn't kill the dot CLI.
628
+ (source "$_user_cmd" "$@")
629
+ exit $?
590
630
  else
591
631
  echo "Unknown command: ${COMMAND:-}" >&2
592
632
  echo "Run 'dot help' for available commands." >&2
@@ -30,7 +30,6 @@ step "Starting Deterministic Bootstrap (v0.2.493 Frontier)"
30
30
  # 1. Install Nix (if not present)
31
31
  if ! command -v nix >/dev/null 2>&1; then
32
32
  step "Installing Nix Package Manager..."
33
- local nix_installer
34
33
  nix_installer=$(umask 077 && mktemp)
35
34
  if ! curl -fsSL -o "$nix_installer" https://nixos.org/nix/install; then
36
35
  rm -f "$nix_installer"
@@ -466,14 +466,14 @@ reload_desktop() {
466
466
  local highlight_color
467
467
  case "$accent" in
468
468
  -1) highlight_color="0.847059 0.847059 0.862745 Graphite" ;;
469
- 0) highlight_color="0.968627 0.831373 0.792157 Red" ;;
470
- 1) highlight_color="0.968627 0.890196 0.745098 Orange" ;;
471
- 2) highlight_color="1.000000 0.937255 0.690196 Yellow" ;;
472
- 3) highlight_color="0.752941 0.964706 0.678431 Green" ;;
473
- 4) highlight_color="0.698039 0.843137 1.000000 Blue" ;;
474
- 5) highlight_color="0.968627 0.831373 1.000000 Purple" ;;
475
- 6) highlight_color="1.000000 0.749020 0.823529 Pink" ;;
476
- *) highlight_color="0.698039 0.843137 1.000000 Blue" ;;
469
+ 0) highlight_color="0.968627 0.831373 0.792157 Red" ;;
470
+ 1) highlight_color="0.968627 0.890196 0.745098 Orange" ;;
471
+ 2) highlight_color="1.000000 0.937255 0.690196 Yellow" ;;
472
+ 3) highlight_color="0.752941 0.964706 0.678431 Green" ;;
473
+ 4) highlight_color="0.698039 0.843137 1.000000 Blue" ;;
474
+ 5) highlight_color="0.968627 0.831373 1.000000 Purple" ;;
475
+ 6) highlight_color="1.000000 0.749020 0.823529 Pink" ;;
476
+ *) highlight_color="0.698039 0.843137 1.000000 Blue" ;;
477
477
  esac
478
478
 
479
479
  if defaults write -g AppleAccentColor -int "$accent" >/dev/null 2>&1 &&
@@ -1,6 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Interactive Tour using Gum
3
- # Part of Dotfiles v0.2.501
3
+ # Part of Dotfiles v0.2.502
4
4
 
5
5
  set -eu
6
6
 
@@ -39,7 +39,7 @@ banner='██████╗ ██████╗ ████████╗
39
39
  ██████╔╝╚██████╔╝ ██║ ██║ ██║███████╗███████╗███████║
40
40
  ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝
41
41
 
42
- v0.2.501 · Infrastructure‑grade shell environment'
42
+ v0.2.502 · Infrastructure‑grade shell environment'
43
43
 
44
44
  gum style \
45
45
  --foreground 212 --border-foreground 212 --border double \
@@ -1,4 +1,4 @@
1
- .TH DOT 1 "April 2026" "dotfiles v0.2.500" "User Commands"
1
+ .TH DOT 1 "May 2026" "dotfiles v0.2.502" "User Commands"
2
2
  .SH NAME
3
3
  dot \- dotfiles management CLI
4
4
  .SH SYNOPSIS
@@ -29,6 +29,10 @@ _dot() {
29
29
  'edit:Open chezmoi source in \$EDITOR'
30
30
  'commit:AI-powered conventional commit'
31
31
  'patterns:Manage AI steering patterns'
32
+ 'init:Bootstrap a foreign dotfiles repo through chezmoi + dot'
33
+ 'agents:Multi-harness agent context (AGENTS.md sync, Cursor/Codex stubs)'
34
+ 'manual:Open or download the multi-format manual'
35
+ 'search:Find commands by keyword'
32
36
 
33
37
  # Diagnostics
34
38
  'doctor:Check system health and configuration'
package/install.sh CHANGED
@@ -52,7 +52,7 @@ show_help() {
52
52
  Usage: install.sh [version] [options]
53
53
 
54
54
  Arguments:
55
- version The version (tag or branch) to install (default: v0.2.501)
55
+ version The version (tag or branch) to install (default: v0.2.502)
56
56
 
57
57
  Options:
58
58
  --help Show this help message
@@ -64,7 +64,7 @@ EOF
64
64
  }
65
65
 
66
66
  main() {
67
- local version="v0.2.501"
67
+ local version="v0.2.502"
68
68
  local version_set=0
69
69
  local minimal=0
70
70
  local _cleanup_files=()
@@ -92,7 +92,7 @@ main() {
92
92
  # like `foobar` doesn't trigger a 30s+ network download attempt.
93
93
  # Caught by the install.sh fuzz harness (#881).
94
94
  if [[ ! "$arg" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+([-+][a-zA-Z0-9.-]+)?$ ]]; then
95
- error "Unrecognized positional argument '$arg' — expected a semver version (e.g. v0.2.501)."
95
+ error "Unrecognized positional argument '$arg' — expected a semver version (e.g. v0.2.502)."
96
96
  fi
97
97
  version="$arg"
98
98
  version_set=1
@@ -135,10 +135,29 @@ main() {
135
135
  sudo mkdir -p /etc/apt/keyrings
136
136
  curl -fsSL https://repo.charm.sh/apt/gpg.key |
137
137
  sudo gpg --batch --yes --dearmor -o /etc/apt/keyrings/charm.gpg
138
- # Verify Charm GPG key fingerprint
139
- if ! gpg --no-default-keyring --keyring /etc/apt/keyrings/charm.gpg \
140
- --list-keys --with-colons 2>/dev/null | grep -q "fid:"; then
141
- echo "Warning: Could not verify Charm GPG key" >&2
138
+ # Pin the Charm GPG key fingerprint — a DNS attacker swapping
139
+ # repo.charm.sh/apt/gpg.key for a forged key with the same uid
140
+ # would otherwise pass the prior "any fid: present" check.
141
+ # Pinned value taken from https://repo.charm.sh/apt/gpg.key on
142
+ # 2026-05-16. If Charm rotates, update this and call it out in
143
+ # CHANGELOG.md.
144
+ CHARM_GPG_EXPECTED_FPR="C026D31B92F9BBE91D5DB75AB07AE17C9E0A6585"
145
+ charm_actual_fpr="$(gpg --no-default-keyring \
146
+ --keyring /etc/apt/keyrings/charm.gpg \
147
+ --with-colons --fingerprint 2>/dev/null |
148
+ awk -F: '/^fpr:/{print $10; exit}')"
149
+ if [[ -z "$charm_actual_fpr" ]]; then
150
+ echo "Error: Could not extract Charm GPG fingerprint — aborting" >&2
151
+ sudo rm -f /etc/apt/keyrings/charm.gpg
152
+ return 1
153
+ fi
154
+ if [[ "$charm_actual_fpr" != "$CHARM_GPG_EXPECTED_FPR" ]]; then
155
+ echo "Error: Charm GPG fingerprint mismatch" >&2
156
+ echo " expected: $CHARM_GPG_EXPECTED_FPR" >&2
157
+ echo " got: $charm_actual_fpr" >&2
158
+ echo " aborting (possible DNS / keyserver hijack)" >&2
159
+ sudo rm -f /etc/apt/keyrings/charm.gpg
160
+ return 1
142
161
  fi
143
162
  echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list
144
163
  sudo apt-get update && sudo apt-get install gum -y >/dev/null 2>&1
@@ -160,43 +179,31 @@ main() {
160
179
  mkdir -p "$bin_dir"
161
180
  echo " Installing chezmoi via binary download..."
162
181
 
163
- # Prefer verified installer with SHA256 checksum when available
182
+ # Prefer verified installer with SHA256 checksum when available.
183
+ # When verification fails or the verified installer isn't present
184
+ # we refuse to bootstrap rather than silently downloading and
185
+ # executing an unverified script (the previous fall-back to
186
+ # `get.chezmoi.io` was an unsigned bootstrap and a security hole).
164
187
  local verified_installer
165
188
  verified_installer="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/scripts/ci/install-chezmoi-verified.sh"
166
189
  if [[ -x "$verified_installer" ]] || [[ -f "$verified_installer" ]]; then
167
190
  echo " Using checksum-verified installer..."
168
191
  if ! bash "$verified_installer" "${CHEZMOI_VERSION:-2.47.1}" "$bin_dir"; then
169
- echo " Verified installer failed, falling back to get.chezmoi.io" >&2
170
- else
171
- return 0
192
+ echo "" >&2
193
+ echo " The verified chezmoi installer failed." >&2
194
+ echo " Refusing to fall back to an unverified bootstrap path." >&2
195
+ echo " Install chezmoi manually from a trusted source, then re-run install.sh:" >&2
196
+ echo " macOS: brew install chezmoi" >&2
197
+ echo " Linux: see https://www.chezmoi.io/install/" >&2
198
+ return 1
172
199
  fi
200
+ return 0
173
201
  fi
174
-
175
- # Fallback: download installer with size/shebang validation
176
- local installer
177
- installer=$(umask 077 && mktemp)
178
- if ! curl -fsSL -o "$installer" https://get.chezmoi.io; then
179
- rm -f "$installer"
180
- echo " Failed to download chezmoi installer" >&2
181
- return 1
182
- fi
183
- # Validate: must be a shell script and not suspiciously large
184
- if [[ "$(wc -c <"$installer")" -gt 102400 ]]; then
185
- rm -f "$installer"
186
- echo " Chezmoi installer suspiciously large. Aborting." >&2
187
- return 1
188
- fi
189
- if ! head -1 "$installer" | grep -q '^#!/'; then
190
- rm -f "$installer"
191
- echo " Chezmoi installer doesn't look like a shell script. Aborting." >&2
192
- return 1
193
- fi
194
- if ! BINDIR="$bin_dir" sh "$installer"; then
195
- rm -f "$installer"
196
- echo " chezmoi binary installer failed" >&2
197
- return 1
198
- fi
199
- rm -f "$installer"
202
+ echo "" >&2
203
+ echo " scripts/ci/install-chezmoi-verified.sh is missing; cannot verify chezmoi bootstrap." >&2
204
+ echo " Install chezmoi manually (brew install chezmoi / official binary)" >&2
205
+ echo " and re-run install.sh." >&2
206
+ return 1
200
207
  fi
201
208
  }
202
209
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sebastienrousseau/dotfiles",
3
- "version": "0.2.501",
3
+ "version": "0.2.502",
4
4
  "description": "The Trusted Shell Platform — Universal dotfiles managed by Chezmoi. Features Bash & Zsh for macOS, Linux & WSL. Rust modern tooling & enterprise-grade security.",
5
5
  "main": "install.sh",
6
6
  "bin": {