@sebastienrousseau/dotfiles 0.2.518 → 0.2.520
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.
- package/CHANGELOG.md +191 -0
- package/README.md +1164 -164
- package/docs/ARCHITECTURE.md +117 -0
- package/docs/COPYRIGHT +1 -1
- package/docs/ECOSYSTEM.md +220 -0
- package/docs/GOLD-STANDARD-AUDIT.md +352 -0
- package/docs/GOVERNANCE.md +1 -1
- package/docs/MAINTAINERS.md +1 -1
- package/docs/MINIMUM-TOOLCHAIN.md +100 -0
- package/docs/README.md +1 -1
- package/docs/STRUCTURE.md +1 -1
- package/docs/architecture/ARCHITECTURE.md +11 -108
- package/docs/architecture/REPO_LAYOUT.md +4 -4
- package/docs/guides/MACOS_ICLOUD_SYMLINKS.md +121 -0
- package/docs/index.md +3 -3
- package/docs/manual/00-introduction.md +1 -1
- package/docs/manual/03-reference/01-dot-cli.md +28 -3
- package/docs/manual/command-index.md +24 -4
- package/docs/manual/concept-index.md +2 -0
- package/docs/migration/README.md +81 -0
- package/docs/migration/from-bare-git-repo.md +156 -0
- package/docs/migration/from-gnu-stow.md +165 -0
- package/docs/migration/from-plain-chezmoi.md +148 -0
- package/docs/migration/from-yadm.md +187 -0
- package/docs/operations/PERFORMANCE_BUDGETS.md +196 -0
- package/docs/operations/REGISTRY.md +1 -1
- package/docs/operations/RELEASE_PIPELINE.md +4 -4
- package/docs/operations/TESTING.md +3 -3
- package/docs/operations/TRACEABILITY.md +1 -0
- package/docs/packaging.md +222 -0
- package/docs/reference/FEATURE-MATRIX.md +646 -0
- package/docs/reference/TOOLS.md +1 -1
- package/docs/reference/UTILS.md +1 -0
- package/docs/security/COMPLIANCE.md +1 -1
- package/docs/security/DISCLOSURE.md +4 -4
- package/docs/security/FUZZING.md +113 -18
- package/docs/security/KEY_ROTATION.md +1 -1
- package/docs/security/SCORECARD.md +3 -3
- package/docs/security/VERIFY_RELEASE.md +2 -2
- package/install.sh +6 -6
- package/package.json +2 -2
- package/scripts/ci/check-copyright-headers.sh +1 -1
- package/scripts/ci/check-shell-preamble.sh +1 -1
- package/scripts/ci/guard-gitleaks-checkout.sh +1 -1
- package/scripts/demo/record.sh +1 -1
- package/scripts/diagnostics/a2a-conformance.sh +1 -1
- package/scripts/diagnostics/alias-governance.sh +30 -3
- package/scripts/diagnostics/aliases-cheatsheet.sh +1 -1
- package/scripts/diagnostics/aliases-manifest.sh +1 -1
- package/scripts/diagnostics/attest-verify.sh +147 -0
- package/scripts/diagnostics/benchmark.sh +1 -1
- package/scripts/diagnostics/conflicts.sh +1 -1
- package/scripts/diagnostics/doctor-unified.sh +6 -2
- package/scripts/diagnostics/doctor.sh +56 -10
- package/scripts/diagnostics/drift-dashboard.sh +3 -2
- package/scripts/diagnostics/health.sh +43 -10
- package/scripts/diagnostics/history-analysis.sh +1 -1
- package/scripts/diagnostics/mcp-doctor.sh +2 -2
- package/scripts/diagnostics/perf.sh +1 -1
- package/scripts/diagnostics/scorecard.sh +3 -2
- package/scripts/diagnostics/secret-governance.sh +1 -1
- package/scripts/diagnostics/security-score.sh +1 -1
- package/scripts/diagnostics/smoke-test.sh +1 -1
- package/scripts/diagnostics/snapshot.sh +1 -1
- package/scripts/diagnostics/verify.sh +1 -1
- package/scripts/diagnostics/verify_state.sh +1 -1
- package/scripts/diagnostics/version-locks.sh +1 -1
- package/scripts/diagnostics/workstation-attestation.sh +26 -1
- package/scripts/dot/commands/agent.sh +64 -14
- package/scripts/dot/commands/agents.sh +24 -8
- package/scripts/dot/commands/ai.sh +20 -7
- package/scripts/dot/commands/aliases.sh +1 -1
- package/scripts/dot/commands/appearance.sh +1 -1
- package/scripts/dot/commands/completion.sh +42 -5
- package/scripts/dot/commands/core.sh +1 -1
- package/scripts/dot/commands/diagnostics.sh +1 -1
- package/scripts/dot/commands/env-emit.sh +1 -1
- package/scripts/dot/commands/fleet.sh +41 -18
- package/scripts/dot/commands/init.sh +1 -1
- package/scripts/dot/commands/lint.sh +1 -1
- package/scripts/dot/commands/manual.sh +1 -1
- package/scripts/dot/commands/meta.sh +115 -10
- package/scripts/dot/commands/patterns.sh +1 -1
- package/scripts/dot/commands/registry.sh +43 -7
- package/scripts/dot/commands/restore.sh +1 -1
- package/scripts/dot/commands/secrets.sh +1 -1
- package/scripts/dot/commands/security.sh +1 -1
- package/scripts/dot/commands/tools.sh +21 -7
- package/scripts/fonts/install-nerd-fonts.sh +1 -1
- package/scripts/fonts/patch-fonts.sh +1 -1
- package/scripts/git-hooks/install.sh +1 -1
- package/scripts/git-hooks/pre-commit-audit.sh +2 -2
- package/scripts/lib/secrets_provider.sh +19 -4
- package/scripts/nvim/headless-upgrade.lua +81 -0
- package/scripts/ops/ai-setup.sh +1 -1
- package/scripts/ops/bundle.sh +1 -1
- package/scripts/ops/chaos.sh +1 -1
- package/scripts/ops/chezmoi-apply.sh +1 -1
- package/scripts/ops/chezmoi-diff.sh +1 -1
- package/scripts/ops/chezmoi-remove.sh +3 -3
- package/scripts/ops/chezmoi-update.sh +6 -2
- package/scripts/ops/heal-chezmoi.sh +1 -1
- package/scripts/ops/heal-system.sh +1 -1
- package/scripts/ops/heal-tools.sh +1 -1
- package/scripts/ops/heal.sh +1 -1
- package/scripts/ops/post-apply-repair.sh +1 -1
- package/scripts/ops/prewarm.sh +1 -1
- package/scripts/ops/release.sh +1 -1
- package/scripts/ops/rollback.sh +11 -2
- package/scripts/ops/setup.sh +1 -1
- package/scripts/ops/teleport.sh +1 -1
- package/scripts/qa/check-feature-matrix.sh +296 -0
- package/scripts/qa/check-version-consistency.sh +7 -119
- package/scripts/qa/coverage-baseline.sh +1 -1
- package/scripts/qa/docs-coverage.sh +9 -3
- package/scripts/qa/examples-coverage.sh +1 -1
- package/scripts/qa/reliability-audit.sh +1 -1
- package/scripts/qa/scorecard-snapshot.sh +1 -1
- package/scripts/qa/traceability-coverage.sh +9 -2
- package/scripts/qa/validate-examples.sh +66 -3
- package/scripts/qa/wsl-contract.sh +1 -1
- package/scripts/secrets/age-init.sh +1 -1
- package/scripts/secrets/create-secrets-file.sh +1 -1
- package/scripts/secrets/encrypt-ssh-key.sh +1 -1
- package/scripts/security/backup.sh +1 -1
- package/scripts/security/check-disclosure-key-expiry.sh +1 -1
- package/scripts/security/dns-doh.sh +1 -1
- package/scripts/security/encryption-check.sh +1 -1
- package/scripts/security/enforce-policies.sh +315 -98
- package/scripts/security/firewall.sh +1 -1
- package/scripts/security/lock-configs.sh +1 -1
- package/scripts/security/lock-screen.sh +1 -1
- package/scripts/security/manage-secrets.sh +1 -1
- package/scripts/security/ssh-cert.sh +1 -1
- package/scripts/security/telemetry-kill.sh +1 -1
- package/scripts/security/usb-safety.sh +1 -1
- package/scripts/theme/apply-gnome-theme.sh +1 -1
- package/scripts/theme/extract-heic-frames.sh +1 -1
- package/scripts/theme/extract-theme.py +284 -6
- package/scripts/theme/install-boot-logo.sh +1 -1
- package/scripts/theme/install-catppuccin-themes.sh +1 -1
- package/scripts/theme/install-cursors.sh +1 -1
- package/scripts/theme/install-file-icons.sh +1 -1
- package/scripts/theme/install-grub-theme.sh +1 -1
- package/scripts/theme/install-lock-icon.sh +1 -1
- package/scripts/theme/merge-wallpaper.sh +1 -1
- package/scripts/theme/rebuild-themes.sh +64 -5
- package/scripts/theme/switch.sh +46 -19
- package/scripts/theme/wallpaper-rotate.sh +1 -1
- package/scripts/theme/wallpaper-sync.sh +1 -1
- package/scripts/tools/cmatrix.sh +1 -1
- package/scripts/tools/detect-collisions.py +1 -1
- package/scripts/tools/emoji-picker.sh +1 -1
- package/scripts/tools/figlet-banner.sh +1 -1
- package/scripts/tools/log-rotate.sh +1 -1
- package/scripts/tools/lolcat-wrap.sh +1 -1
- package/scripts/tools/pipes.sh +1 -1
- package/scripts/tuning/linux.sh +1 -1
- package/scripts/tuning/macos.sh +1 -1
- package/scripts/uninstall.sh +1 -1
- package/scripts/verify-release-versions +156 -0
- package/scripts/version-sync.sh +63 -3
- package/LICENSE +0 -21
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
3
|
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
4
|
## Dotfiles AI Commands.
|
|
5
5
|
##
|
|
@@ -22,12 +22,21 @@ AI_CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/dotfiles/ai"
|
|
|
22
22
|
AI_STATUS_TTL="${DOTFILES_AI_STATUS_TTL:-300}"
|
|
23
23
|
AI_STATUS_CACHE_FILE="${AI_CACHE_DIR}/status.tsv"
|
|
24
24
|
|
|
25
|
-
# Fallback to source tree if patterns don't exist in config (common in CI)
|
|
25
|
+
# Fallback to source tree if patterns don't exist in config (common in CI).
|
|
26
|
+
# `.chezmoiroot` moves the source tree one level down (it holds
|
|
27
|
+
# `defaults` here), so probing <repo>/dot_config/… alone made this a
|
|
28
|
+
# no-op and `--style` died with "Pattern not found" off-deployment.
|
|
26
29
|
if [[ ! -d "$PATTERN_DIR" ]]; then
|
|
27
30
|
_AI_SRC="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
_AI_SUB=""
|
|
32
|
+
[[ -f "$_AI_SRC/.chezmoiroot" ]] &&
|
|
33
|
+
_AI_SUB="/$(head -1 "$_AI_SRC/.chezmoiroot" | tr -d '[:space:]')"
|
|
34
|
+
for _AI_CAND in "$_AI_SRC/dot_config/ai/patterns" "$_AI_SRC$_AI_SUB/dot_config/ai/patterns"; do
|
|
35
|
+
if [[ -d "$_AI_CAND" ]]; then
|
|
36
|
+
PATTERN_DIR="$_AI_CAND"
|
|
37
|
+
break
|
|
38
|
+
fi
|
|
39
|
+
done
|
|
31
40
|
fi
|
|
32
41
|
|
|
33
42
|
_show_ai_bridge_usage() {
|
|
@@ -188,7 +197,7 @@ cmd_ai_status() {
|
|
|
188
197
|
done
|
|
189
198
|
|
|
190
199
|
# Offer to install missing providers via mise
|
|
191
|
-
if [[ ${#missing[@]} -gt 0 ]] && has_command mise; then
|
|
200
|
+
if [[ ${#missing[@]} -gt 0 ]] && has_command mise && [[ -t 0 && -t 1 ]] && [[ "${DOTFILES_NONINTERACTIVE:-0}" != "1" ]]; then
|
|
192
201
|
echo ""
|
|
193
202
|
local _ai_install_action=""
|
|
194
203
|
if has_command gum; then
|
|
@@ -288,6 +297,8 @@ cmd_ai_status() {
|
|
|
288
297
|
echo ""
|
|
289
298
|
if [ ${#installed[@]} -eq 0 ]; then
|
|
290
299
|
ui_warn "No AI CLIs installed"
|
|
300
|
+
elif [[ ! -t 0 || ! -t 1 || "${DOTFILES_NONINTERACTIVE:-0}" == "1" ]]; then
|
|
301
|
+
return 0
|
|
291
302
|
elif has_command gum; then
|
|
292
303
|
ui_info "Launch" "Select an AI CLI to start"
|
|
293
304
|
local -a choices=()
|
|
@@ -404,7 +415,9 @@ ${prompt}"
|
|
|
404
415
|
do_install=$(gum confirm "Install $tool via mise ($mise_pkg)?" && echo "yes" || echo "no")
|
|
405
416
|
else
|
|
406
417
|
printf "Install %s via mise (%s)? [y/N] " "$tool" "$mise_pkg"
|
|
407
|
-
read
|
|
418
|
+
# `|| true`: at EOF (piped, cron, CI) `read` returns 1 and
|
|
419
|
+
# `set -e` killed the script mid-prompt. EOF means "no".
|
|
420
|
+
read -r do_install || true
|
|
408
421
|
case "$do_install" in y | Y | yes) do_install="yes" ;; *) do_install="no" ;; esac
|
|
409
422
|
fi
|
|
410
423
|
if [[ "$do_install" == "yes" ]]; then
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
3
|
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
4
|
# Dotfiles CLI - Aliases Commands (extracted from tools.sh)
|
|
5
5
|
# aliases list|search|why|stats|cheatsheet|tiers, alias-check
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
3
|
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
4
|
## `dot completion <bash|zsh|fish|nu>` — emit shell completion for `dot`,
|
|
5
5
|
## generated from the canonical _dot_help_specs registry in bin/dot. Using one
|
|
@@ -35,7 +35,11 @@ _completion_commands() {
|
|
|
35
35
|
in_func && /cat <<'\''EOF'\''/ { in_block = 1; next }
|
|
36
36
|
in_block && /^EOF$/ { exit }
|
|
37
37
|
in_block && NF >= 3 && $2 !~ / / {
|
|
38
|
-
name = $2
|
|
38
|
+
name = $2
|
|
39
|
+
# Description = fields 3..NF-1 rejoined: registry descriptions can
|
|
40
|
+
# contain "|" (e.g. "--ai|-A") and the last field is the hint.
|
|
41
|
+
desc = $3
|
|
42
|
+
for (i = 4; i < NF; i++) desc = desc "|" $i
|
|
39
43
|
gsub(/^[ \t]+|[ \t]+$/, "", name)
|
|
40
44
|
gsub(/^[ \t]+|[ \t]+$/, "", desc)
|
|
41
45
|
gsub(/[":\047]/, "", desc)
|
|
@@ -54,7 +58,9 @@ _completion_subcommands() {
|
|
|
54
58
|
in_func && /cat <<'\''EOF'\''/ { in_block = 1; next }
|
|
55
59
|
in_block && /^EOF$/ { exit }
|
|
56
60
|
in_block && NF >= 3 && $2 ~ / / {
|
|
57
|
-
full = $2
|
|
61
|
+
full = $2
|
|
62
|
+
desc = $3
|
|
63
|
+
for (i = 4; i < NF; i++) desc = desc "|" $i
|
|
58
64
|
gsub(/^[ \t]+|[ \t]+$/, "", full)
|
|
59
65
|
gsub(/^[ \t]+|[ \t]+$/, "", desc)
|
|
60
66
|
gsub(/[":\047]/, "", desc)
|
|
@@ -65,11 +71,42 @@ _completion_subcommands() {
|
|
|
65
71
|
' "$cli"
|
|
66
72
|
}
|
|
67
73
|
|
|
74
|
+
# The printf templates below are the *text of the generated script*;
|
|
75
|
+
# `$cur`, `$prev`, `$commands` must reach the output unexpanded.
|
|
76
|
+
# shellcheck disable=SC2016
|
|
68
77
|
gen_bash() {
|
|
69
|
-
local names
|
|
78
|
+
local names parent child desc
|
|
70
79
|
names="$(_completion_commands | cut -f1 | tr '\n' ' ')"
|
|
71
80
|
printf '# dot bash completion — generated by: dot completion bash\n'
|
|
72
|
-
printf
|
|
81
|
+
printf '# Source this file, or install it to a bash-completion completions dir.\n'
|
|
82
|
+
printf '_dot_completions() {\n'
|
|
83
|
+
printf ' local cur prev commands\n'
|
|
84
|
+
printf ' COMPREPLY=()\n'
|
|
85
|
+
printf ' cur="${COMP_WORDS[COMP_CWORD]}"\n'
|
|
86
|
+
printf ' prev="${COMP_WORDS[COMP_CWORD - 1]}"\n'
|
|
87
|
+
printf " commands='%s'\n" "${names% }"
|
|
88
|
+
printf ' case "$prev" in\n'
|
|
89
|
+
printf ' dot)\n'
|
|
90
|
+
printf ' COMPREPLY=($(compgen -W "$commands" -- "$cur"))\n'
|
|
91
|
+
printf ' return 0\n'
|
|
92
|
+
printf ' ;;\n'
|
|
93
|
+
# One case arm per parent command that has subcommands.
|
|
94
|
+
while IFS=$'\t' read -r parent child desc; do
|
|
95
|
+
printf '%s\t%s\n' "$parent" "$child"
|
|
96
|
+
done < <(_completion_subcommands) |
|
|
97
|
+
awk -F'\t' '{ subs[$1] = subs[$1] (subs[$1] == "" ? "" : " ") $2 }
|
|
98
|
+
END { for (p in subs) printf "%s\t%s\n", p, subs[p] }' |
|
|
99
|
+
LC_ALL=C sort |
|
|
100
|
+
while IFS=$'\t' read -r parent child; do
|
|
101
|
+
printf ' %s)\n' "$parent"
|
|
102
|
+
printf ' COMPREPLY=($(compgen -W "%s" -- "$cur"))\n' "$child"
|
|
103
|
+
printf ' return 0\n'
|
|
104
|
+
printf ' ;;\n'
|
|
105
|
+
done
|
|
106
|
+
printf ' esac\n'
|
|
107
|
+
printf ' COMPREPLY=($(compgen -f -- "$cur"))\n'
|
|
108
|
+
printf '}\n'
|
|
109
|
+
printf 'complete -F _dot_completions dot\n'
|
|
73
110
|
}
|
|
74
111
|
|
|
75
112
|
gen_zsh() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
3
|
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
4
|
# Dotfiles CLI - Fleet Commands
|
|
5
5
|
# fleet status|nodes|drift|events|namespace
|
|
@@ -93,7 +93,8 @@ cmd_fleet_status() {
|
|
|
93
93
|
local drift_status="clean"
|
|
94
94
|
if has_command chezmoi; then
|
|
95
95
|
local drift_output
|
|
96
|
-
|
|
96
|
+
# --exclude=always: always-run scripts are pending by design, not drift.
|
|
97
|
+
drift_output="$(chezmoi status --exclude=always 2>/dev/null || true)"
|
|
97
98
|
if [[ -n "$drift_output" ]]; then
|
|
98
99
|
drift_status="drifted"
|
|
99
100
|
fi
|
|
@@ -168,7 +169,7 @@ cmd_fleet_drift() {
|
|
|
168
169
|
fi
|
|
169
170
|
|
|
170
171
|
local drift_output
|
|
171
|
-
drift_output="$(chezmoi status 2>/dev/null || true)"
|
|
172
|
+
drift_output="$(chezmoi status --exclude=always 2>/dev/null || true)"
|
|
172
173
|
|
|
173
174
|
_fleet_drift_append_history "$drift_output"
|
|
174
175
|
|
|
@@ -206,9 +207,12 @@ cmd_fleet_drift() {
|
|
|
206
207
|
tail -n "$count" "$_DRIFT_HISTORY_FILE" | while IFS= read -r line; do
|
|
207
208
|
local time status file_count
|
|
208
209
|
# One jq per line (was three: .time, .status, .files|length).
|
|
210
|
+
# `|| true`: on an unparsable line jq prints nothing, `read`
|
|
211
|
+
# hits EOF and returns 1, and `set -e` would otherwise abort the
|
|
212
|
+
# whole listing instead of rendering the `?` placeholders below.
|
|
209
213
|
IFS=$'\t' read -r time status file_count < <(
|
|
210
214
|
printf '%s' "$line" | jq -r '[.time, .status, (.files | length)] | @tsv' 2>/dev/null
|
|
211
|
-
)
|
|
215
|
+
) || true
|
|
212
216
|
[[ -n "$time" ]] || time="?"
|
|
213
217
|
[[ -n "$status" ]] || status="?"
|
|
214
218
|
[[ -n "$file_count" ]] || file_count=0
|
|
@@ -298,23 +302,42 @@ cmd_fleet_namespace() {
|
|
|
298
302
|
validate_name "$new_ns" "namespace"
|
|
299
303
|
local data_file
|
|
300
304
|
data_file="$(resolve_chezmoi_source_dir)/.chezmoidata.toml"
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
305
|
+
[[ -f "$data_file" ]] || die ".chezmoidata.toml not found: $data_file"
|
|
306
|
+
# Atomic write: render into a tempfile + mv so concurrent
|
|
307
|
+
# `dot fleet namespace set` callers can't corrupt the TOML.
|
|
308
|
+
# Avoids `sed -i` portability dance (GNU `-i` vs BSD `-i ''`).
|
|
309
|
+
local _tmp _rendered=0
|
|
310
|
+
_tmp="$(mktemp "${data_file}.XXXXXX")" || die "Cannot create tempfile"
|
|
311
|
+
# Explicit if/else instead of A && B || C — the latter (SC2015)
|
|
312
|
+
# silently runs C when B itself fails, masking real mv errors.
|
|
313
|
+
if grep -q "^namespace = " "$data_file"; then
|
|
309
314
|
if sed "s/^namespace = \".*\"/namespace = \"$new_ns\"/" "$data_file" >"$_tmp"; then
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
+
_rendered=1
|
|
316
|
+
fi
|
|
317
|
+
else
|
|
318
|
+
# No key yet — the shipped .chezmoidata.toml has none, and rewriting
|
|
319
|
+
# only when one already existed made `set` a silent no-op on a fresh
|
|
320
|
+
# checkout while still reporting success. Insert it after the first
|
|
321
|
+
# line: appending at the end would land the key inside whatever
|
|
322
|
+
# [table] the file happens to end with, which TOML reads as a
|
|
323
|
+
# different key entirely. `dot profile set` does the same for
|
|
324
|
+
# `profile`.
|
|
325
|
+
if awk -v ns="$new_ns" '
|
|
326
|
+
NR == 1 { print; printf "namespace = \"%s\"\n", ns; inserted = 1; next }
|
|
327
|
+
{ print }
|
|
328
|
+
END { if (!inserted) printf "namespace = \"%s\"\n", ns }
|
|
329
|
+
' "$data_file" >"$_tmp"; then
|
|
330
|
+
_rendered=1
|
|
331
|
+
fi
|
|
332
|
+
fi
|
|
333
|
+
if [[ "$_rendered" -eq 1 ]]; then
|
|
334
|
+
if ! mv "$_tmp" "$data_file"; then
|
|
315
335
|
rm -f "$_tmp"
|
|
316
|
-
die "Failed to
|
|
336
|
+
die "Failed to commit namespace update"
|
|
317
337
|
fi
|
|
338
|
+
else
|
|
339
|
+
rm -f "$_tmp"
|
|
340
|
+
die "Failed to render namespace update"
|
|
318
341
|
fi
|
|
319
342
|
ui_ok "Namespace" "Set to '$new_ns'. Run 'dot sync' to apply."
|
|
320
343
|
_fleet_emit_event "namespace_set" "ok" "namespace=$new_ns"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
3
|
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
4
|
# Dotfiles CLI - Meta Commands
|
|
5
5
|
# upgrade, prewarm, docs, learn, keys, sandbox, mcp, mode, agent
|
|
@@ -28,7 +28,12 @@ meta_banner_section() {
|
|
|
28
28
|
esac
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
# `mcp serve` owns stdout from its first byte — it is a JSON-RPC frame stream,
|
|
32
|
+
# not a terminal — so the banner is suppressed for it outright. (It already
|
|
33
|
+
# self-suppresses off a TTY; this also covers a client that allocates a pty.)
|
|
34
|
+
if [[ "${1:-}" != "mcp" || "${2:-}" != "serve" ]]; then
|
|
35
|
+
dot_ui_command_banner "$(meta_banner_section "${1:-}")" "${1:-}" "$@"
|
|
36
|
+
fi
|
|
32
37
|
|
|
33
38
|
# Last meaningful line of a captured log, used as a step's `ok` detail.
|
|
34
39
|
# Collapses carriage-return progress (git/nvim spam \r), strips ANSI,
|
|
@@ -65,7 +70,13 @@ cmd_upgrade() {
|
|
|
65
70
|
[[ "${1:-}" == "--" ]] && shift
|
|
66
71
|
local log="$log_dir/$id.log" rc=0
|
|
67
72
|
ui_step "$id" "$label" run "$running"
|
|
68
|
-
|
|
73
|
+
# stdin is closed on purpose. Output is captured to a log, so a phase
|
|
74
|
+
# that prompts (chezmoi's "X has changed since chezmoi last wrote it?",
|
|
75
|
+
# a git credential helper, nvim waiting on a keypress) would otherwise
|
|
76
|
+
# block on the terminal with its question invisible — the 2026-09-12
|
|
77
|
+
# "chezmoi update seems very slow" report. With no stdin the prompt
|
|
78
|
+
# fails fast, the step is marked failed, and the tail below says why.
|
|
79
|
+
"$@" >"$log" 2>&1 </dev/null || rc=$?
|
|
69
80
|
if [[ "$rc" -eq 0 ]]; then
|
|
70
81
|
ui_step "$id" "" ok "$(_upgrade_last_line "$log")"
|
|
71
82
|
else
|
|
@@ -84,11 +95,27 @@ cmd_upgrade() {
|
|
|
84
95
|
_upgrade_step nix-gc "Nix GC" "collecting…" -- nix-collect-garbage -d
|
|
85
96
|
fi
|
|
86
97
|
|
|
87
|
-
|
|
98
|
+
# --no-tty: chezmoi otherwise opens /dev/tty directly for its
|
|
99
|
+
# overwrite prompt, bypassing the closed stdin above and hanging.
|
|
100
|
+
_upgrade_step dotfiles "Dotfiles" "chezmoi update…" -- chezmoi update --no-tty
|
|
88
101
|
|
|
89
102
|
if has_command nvim; then
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
# scripts/nvim/headless-upgrade.lua runs Lazy sync AND waits for
|
|
104
|
+
# Mason's async install queue to drain, so ensure_installed installs
|
|
105
|
+
# (codelldb, debugpy, delve, etc.) aren't aborted by an early quitall.
|
|
106
|
+
#
|
|
107
|
+
# `nvim -l` does NOT load the user's init.lua, so without an explicit
|
|
108
|
+
# -u the script found no lazy.nvim and logged "skipping plugin update"
|
|
109
|
+
# on every run — the upgrade step was a silent no-op. Point -u at the
|
|
110
|
+
# config nvim would load itself (honouring XDG_CONFIG_HOME and
|
|
111
|
+
# NVIM_APPNAME) and skip visibly when there is none to load.
|
|
112
|
+
local nvim_init="${XDG_CONFIG_HOME:-$HOME/.config}/${NVIM_APPNAME:-nvim}/init.lua"
|
|
113
|
+
if [ -f "$nvim_init" ]; then
|
|
114
|
+
_upgrade_step nvim "Neovim plugins" "Lazy sync + Mason drain…" -- \
|
|
115
|
+
nvim --headless -u "$nvim_init" -l "$src_dir/scripts/nvim/headless-upgrade.lua"
|
|
116
|
+
else
|
|
117
|
+
ui_step nvim "Neovim plugins" skip "no init.lua at $nvim_init"
|
|
118
|
+
fi
|
|
92
119
|
fi
|
|
93
120
|
|
|
94
121
|
if [ "${DOTFILES_FONTS:-}" = "1" ] &&
|
|
@@ -109,6 +136,9 @@ cmd_upgrade() {
|
|
|
109
136
|
while ((i < n)); do
|
|
110
137
|
ui_err "${fail_labels[$i]}" "log tail:"
|
|
111
138
|
tail -n 15 "${fail_logs[$i]}" | sed 's/^/ /'
|
|
139
|
+
if grep -q 'has changed since chezmoi last wrote it' "${fail_logs[$i]}"; then
|
|
140
|
+
ui_info "Hint" "a managed file was edited locally — run 'chezmoi apply' to choose per file, or 'chezmoi update --force' to overwrite"
|
|
141
|
+
fi
|
|
112
142
|
((i++)) || true
|
|
113
143
|
done
|
|
114
144
|
ui_info "Logs" "$log_dir"
|
|
@@ -196,11 +226,33 @@ cmd_keys() {
|
|
|
196
226
|
return 0
|
|
197
227
|
fi
|
|
198
228
|
|
|
199
|
-
|
|
229
|
+
# The keybindings document lives at docs/security/KEYS.md. Probing only
|
|
230
|
+
# docs/KEYS.md meant every `dot keys` fell through to
|
|
231
|
+
# scripts/diagnostics/keys.sh, which is not in the tree either, so the
|
|
232
|
+
# command could only ever report "Keys script not found". The legacy path
|
|
233
|
+
# stays in the list for trees that predate the move.
|
|
234
|
+
local keys_doc=""
|
|
235
|
+
if [ -n "$src_dir" ]; then
|
|
236
|
+
local candidate
|
|
237
|
+
for candidate in docs/security/KEYS.md docs/KEYS.md; do
|
|
238
|
+
if [ -f "$src_dir/$candidate" ]; then
|
|
239
|
+
keys_doc="$src_dir/$candidate"
|
|
240
|
+
break
|
|
241
|
+
fi
|
|
242
|
+
done
|
|
243
|
+
fi
|
|
244
|
+
|
|
245
|
+
if [ -n "$keys_doc" ]; then
|
|
200
246
|
if [ -n "${1:-}" ]; then
|
|
201
|
-
|
|
247
|
+
# ripgrep is not installed everywhere (the CI runners have none), and
|
|
248
|
+
# a bare `rg` there printed nothing at all while exiting 0.
|
|
249
|
+
if has_command rg; then
|
|
250
|
+
rg -i --fixed-strings --context 1 "${1:-}" "$keys_doc" || true
|
|
251
|
+
else
|
|
252
|
+
grep -i -F -C 1 -- "${1:-}" "$keys_doc" || true
|
|
253
|
+
fi
|
|
202
254
|
else
|
|
203
|
-
exec cat "$
|
|
255
|
+
exec cat "$keys_doc"
|
|
204
256
|
fi
|
|
205
257
|
else
|
|
206
258
|
run_script "scripts/diagnostics/keys.sh" "Keys script" "$@"
|
|
@@ -268,13 +320,66 @@ cmd_mcp() {
|
|
|
268
320
|
exec cat "$registry_file"
|
|
269
321
|
fi
|
|
270
322
|
;;
|
|
323
|
+
serve)
|
|
324
|
+
cmd_mcp_serve "$@"
|
|
325
|
+
;;
|
|
271
326
|
*)
|
|
272
|
-
echo "Usage: dot mcp [doctor|registry]" >&2
|
|
327
|
+
echo "Usage: dot mcp [doctor|registry|serve]" >&2
|
|
273
328
|
exit 1
|
|
274
329
|
;;
|
|
275
330
|
esac
|
|
276
331
|
}
|
|
277
332
|
|
|
333
|
+
# cmd_mcp_serve — exec the stdio MCP server (dot-mcp).
|
|
334
|
+
#
|
|
335
|
+
# This is the transport `.well-known/mcp/server-card.json` advertises:
|
|
336
|
+
# JSON-RPC 2.0 over newline-delimited frames on stdin/stdout. Nothing this
|
|
337
|
+
# function prints may reach stdout, because stdout is the protocol stream from
|
|
338
|
+
# the moment the client launches us — every message below goes to stderr, and
|
|
339
|
+
# the server is exec'd so it owns the descriptors outright.
|
|
340
|
+
#
|
|
341
|
+
# The binary is deployed by run_onchange_26-build-dot-mcp.sh. When it is not
|
|
342
|
+
# built yet and a Go toolchain is available, it is built on demand into the
|
|
343
|
+
# user cache rather than failing: a client that trusted the card must get a
|
|
344
|
+
# server, not an error.
|
|
345
|
+
cmd_mcp_serve() {
|
|
346
|
+
local repo_root="" source_dir bin src cache
|
|
347
|
+
# resolve_chezmoi_source_dir points at the chezmoi source (the `defaults/`
|
|
348
|
+
# directory); the server wants the repository above it. Fall back to the
|
|
349
|
+
# checkout root when chezmoi is not configured.
|
|
350
|
+
source_dir="$(resolve_chezmoi_source_dir 2>/dev/null || true)"
|
|
351
|
+
if [[ -n "$source_dir" && -d "$source_dir" ]]; then
|
|
352
|
+
repo_root="$(cd "$source_dir/.." && pwd)"
|
|
353
|
+
fi
|
|
354
|
+
[[ -n "$repo_root" ]] || repo_root="$(require_source_dir)"
|
|
355
|
+
|
|
356
|
+
# Tell the server which tree it is serving, and which CLI to shell out to,
|
|
357
|
+
# so it never has to guess either.
|
|
358
|
+
export DOT_MCP_REPO_ROOT="$repo_root"
|
|
359
|
+
export DOT_MCP_DOT_BIN="${DOT_MCP_DOT_BIN:-$(command -v dot || echo dot)}"
|
|
360
|
+
|
|
361
|
+
if bin="$(command -v dot-mcp 2>/dev/null)"; then
|
|
362
|
+
exec "$bin" serve "$@"
|
|
363
|
+
fi
|
|
364
|
+
if [[ -x "$HOME/.local/bin/dot-mcp" ]]; then
|
|
365
|
+
exec "$HOME/.local/bin/dot-mcp" serve "$@"
|
|
366
|
+
fi
|
|
367
|
+
|
|
368
|
+
src="$repo_root/defaults/dot_local/share/dot-mcp"
|
|
369
|
+
[[ -d "$src" ]] || src="$HOME/.local/share/dot-mcp"
|
|
370
|
+
if [[ -f "$src/main.go" ]] && has_command go; then
|
|
371
|
+
cache="${XDG_CACHE_HOME:-$HOME/.cache}/dotfiles"
|
|
372
|
+
mkdir -p "$cache"
|
|
373
|
+
echo "dot mcp serve: building dot-mcp (first run)…" >&2
|
|
374
|
+
if (cd "$src" && go build -o "$cache/dot-mcp" .) >&2; then
|
|
375
|
+
exec "$cache/dot-mcp" serve "$@"
|
|
376
|
+
fi
|
|
377
|
+
fi
|
|
378
|
+
|
|
379
|
+
echo "dot mcp serve: dot-mcp is not built. Run 'chezmoi apply' (or 'go build' in $src) to install it." >&2
|
|
380
|
+
exit 1
|
|
381
|
+
}
|
|
382
|
+
|
|
278
383
|
# Dispatch — cmd_mode is defined in agent.sh (sourced above)
|
|
279
384
|
case "${1:-}" in
|
|
280
385
|
upgrade)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
3
|
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
4
|
# shellcheck shell=bash
|
|
5
5
|
#
|
|
@@ -76,6 +76,38 @@ _registry_cache_dir() {
|
|
|
76
76
|
printf '%s/dotfiles/registry\n' "${XDG_CACHE_HOME:-$HOME/.cache}"
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
## The index cache is keyed by URL, not by one fixed path.
|
|
80
|
+
##
|
|
81
|
+
## It used to live at <cache>/index.json for every registry, and be treated as
|
|
82
|
+
## fresh for six hours: a freshness window says nothing about WHICH registry
|
|
83
|
+
## produced the file, so changing DOTFILES_REGISTRY_URL — or running
|
|
84
|
+
## `dot registry set-url` — kept serving the previous registry's index for up
|
|
85
|
+
## to six hours. Keying by URL also means switching back and forth does not
|
|
86
|
+
## re-download.
|
|
87
|
+
##
|
|
88
|
+
## SHA-256 where a hasher exists, POSIX `cksum` otherwise: this is a cache
|
|
89
|
+
## key, not a security boundary, and every index is schema-validated on read.
|
|
90
|
+
_registry_cache_key() {
|
|
91
|
+
local url="$1" digest=""
|
|
92
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
93
|
+
digest="$(printf '%s' "$url" | shasum -a 256 2>/dev/null | awk '{print $1}')"
|
|
94
|
+
elif command -v sha256sum >/dev/null 2>&1; then
|
|
95
|
+
digest="$(printf '%s' "$url" | sha256sum 2>/dev/null | awk '{print $1}')"
|
|
96
|
+
fi
|
|
97
|
+
if [[ -z "$digest" ]]; then
|
|
98
|
+
digest="$(printf '%s' "$url" | cksum | awk '{print $1 "-" $2}')"
|
|
99
|
+
fi
|
|
100
|
+
printf '%s\n' "${digest:0:32}"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
## _registry_cache_file [url] — absolute path of the cached index for a URL
|
|
104
|
+
## (the active one by default). Tests seed the cache through this.
|
|
105
|
+
_registry_cache_file() {
|
|
106
|
+
local url="${1:-}"
|
|
107
|
+
[[ -n "$url" ]] || url="$(_registry_url)"
|
|
108
|
+
printf '%s/index-%s.json\n' "$(_registry_cache_dir)" "$(_registry_cache_key "$url")"
|
|
109
|
+
}
|
|
110
|
+
|
|
79
111
|
_registry_data_dir() {
|
|
80
112
|
printf '%s/dotfiles/modules\n' "${XDG_DATA_HOME:-$HOME/.local/share}"
|
|
81
113
|
}
|
|
@@ -95,15 +127,19 @@ _registry_validate_index() {
|
|
|
95
127
|
' "$index" >/dev/null 2>&1
|
|
96
128
|
}
|
|
97
129
|
|
|
130
|
+
# Prints the path of a validated index file on stdout. Every diagnostic goes
|
|
131
|
+
# to stderr: stdout is this function's return channel, and callers read it
|
|
132
|
+
# with `index="$(_registry_fetch)"` — a warning printed here would be
|
|
133
|
+
# captured into that variable and then handed to jq as a filename.
|
|
98
134
|
_registry_fetch() {
|
|
99
135
|
local url cache_dir cache_file
|
|
100
136
|
url="$(_registry_url)"
|
|
101
137
|
[[ "$url" =~ ^(https://|file://) ]] || {
|
|
102
|
-
ui_err "registry" "registry URL must use https:// (or file:// for local testing)"
|
|
138
|
+
ui_err "registry" "registry URL must use https:// (or file:// for local testing)" >&2
|
|
103
139
|
return 1
|
|
104
140
|
}
|
|
105
141
|
cache_dir="$(_registry_cache_dir)"
|
|
106
|
-
cache_file="$
|
|
142
|
+
cache_file="$(_registry_cache_file "$url")"
|
|
107
143
|
mkdir -p "$cache_dir"
|
|
108
144
|
# Refresh if older than 6h or missing.
|
|
109
145
|
local now mtime
|
|
@@ -119,7 +155,7 @@ _registry_fetch() {
|
|
|
119
155
|
fi
|
|
120
156
|
fi
|
|
121
157
|
if ! command -v curl >/dev/null 2>&1; then
|
|
122
|
-
ui_err "registry" "curl not installed"
|
|
158
|
+
ui_err "registry" "curl not installed" >&2
|
|
123
159
|
return 127
|
|
124
160
|
fi
|
|
125
161
|
local tmp
|
|
@@ -137,17 +173,17 @@ _registry_fetch() {
|
|
|
137
173
|
if ! curl "${curl_args[@]}" -o "$tmp" "$url" >/dev/null; then
|
|
138
174
|
rm -f "$tmp"
|
|
139
175
|
if [[ -s "$cache_file" ]]; then
|
|
140
|
-
ui_warn "registry" "fetch failed; using stale cache at $cache_file"
|
|
176
|
+
ui_warn "registry" "fetch failed; using stale cache at $cache_file" >&2
|
|
141
177
|
printf '%s\n' "$cache_file"
|
|
142
178
|
return 0
|
|
143
179
|
fi
|
|
144
|
-
ui_err "registry" "could not fetch $url"
|
|
180
|
+
ui_err "registry" "could not fetch $url" >&2
|
|
145
181
|
return 1
|
|
146
182
|
fi
|
|
147
183
|
mv "$tmp" "$cache_file"
|
|
148
184
|
if ! _registry_validate_index "$cache_file"; then
|
|
149
185
|
rm -f "$cache_file"
|
|
150
|
-
ui_err "registry" "index failed schema and integrity validation"
|
|
186
|
+
ui_err "registry" "index failed schema and integrity validation" >&2
|
|
151
187
|
return 1
|
|
152
188
|
fi
|
|
153
189
|
printf '%s\n' "$cache_file"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
3
|
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
4
|
# dot restore - Restore dotfiles from backup or previous state
|
|
5
5
|
# Usage: dot restore [--list|-l|--latest|-L|<backup-id>]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
3
|
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
4
|
# Dotfiles CLI - Tools Commands
|
|
5
5
|
# tools, new, packages, log-rotate, aliases, alias-check, env, profile
|
|
@@ -196,7 +196,15 @@ show_language_package_managers() {
|
|
|
196
196
|
# mid-output. Capture the pipeline separately so we can fall back
|
|
197
197
|
# cleanly without stray "N/A" lines from pipe-with-|| tricks.
|
|
198
198
|
if pipx_list_out="$(pipx list --short 2>/dev/null)"; then
|
|
199
|
-
|
|
199
|
+
# Command substitution strips the trailing newline, so counting
|
|
200
|
+
# with `printf '%s' … | wc -l` reported one package fewer than
|
|
201
|
+
# installed (and 0 when exactly one was installed). Re-add the
|
|
202
|
+
# terminator, and treat "no output" as zero rather than one.
|
|
203
|
+
if [[ -n "$pipx_list_out" ]]; then
|
|
204
|
+
pipx_installed="$(printf '%s\n' "$pipx_list_out" | wc -l | tr -d ' ')"
|
|
205
|
+
else
|
|
206
|
+
pipx_installed=0
|
|
207
|
+
fi
|
|
200
208
|
else
|
|
201
209
|
pipx_installed="N/A"
|
|
202
210
|
fi
|
|
@@ -245,11 +253,17 @@ cmd_tools() {
|
|
|
245
253
|
exit 1
|
|
246
254
|
fi
|
|
247
255
|
elif [ "$subcommand" = "docs" ]; then
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
256
|
+
# docs/reference/ first: both documents were moved there, and looking
|
|
257
|
+
# only in docs/ meant `dot tools docs` answered "TOOLS.md not found" on a
|
|
258
|
+
# complete checkout. The legacy paths stay in the list so a pre-reorg
|
|
259
|
+
# tree still resolves.
|
|
260
|
+
local doc
|
|
261
|
+
for doc in docs/reference/TOOLS.md docs/reference/UTILS.md \
|
|
262
|
+
docs/TOOLS.md docs/UTILS.md; do
|
|
263
|
+
if [ -n "$src_dir" ] && [ -f "$src_dir/$doc" ]; then
|
|
264
|
+
exec cat "$src_dir/$doc"
|
|
265
|
+
fi
|
|
266
|
+
done
|
|
253
267
|
ui_err "Docs" "TOOLS.md not found"
|
|
254
268
|
exit 1
|
|
255
269
|
else
|