@sebastienrousseau/dotfiles 0.2.511 → 0.2.513
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 +90 -0
- package/README.md +2 -2
- package/docs/AI.md +2 -1
- package/docs/COPYRIGHT +1 -1
- package/docs/architecture/AI_COST_OPTIMIZATION.md +1 -0
- package/docs/archive/LEGACY_ROADMAP.md +4 -158
- package/docs/manual/00-introduction.md +1 -1
- package/docs/manual/01-concepts/02-trust-model.md +2 -1
- package/docs/manual/02-tutorials/02-add-wallpaper.md +6 -6
- package/docs/manual/02-tutorials/03-create-profile.md +1 -1
- package/docs/manual/02-tutorials/05-deploy-fleet.md +1 -1
- package/docs/manual/03-reference/02-config-files.md +1 -1
- package/docs/manual/03-reference/03-environment.md +1 -1
- package/docs/manual/04-cookbook/03-faq.md +1 -1
- package/docs/manual/05-appendices/B-security-checklist.md +2 -1
- package/docs/manual/command-index.md +1 -0
- package/docs/operations/ARCHITECTURE_ROADMAP.md +5 -143
- package/docs/operations/COVERAGE.md +30 -8
- package/docs/operations/REGISTRY.md +20 -19
- package/docs/operations/ROADMAP.md +3 -159
- package/docs/operations/ROADMAP_2026.md +5 -663
- package/docs/operations/ROADMAP_V0_2_503.md +5 -129
- package/docs/reference/POWERSHELL_PARITY.md +28 -27
- package/docs/reference/THEMES.md +1 -1
- package/docs/reference/TOOLS.md +1 -0
- package/docs/reference/UTILS.md +1 -0
- package/docs/schema/dot-registry-v1.json +33 -0
- package/docs/security/SCORECARD.md +1 -1
- package/install.sh +20 -11
- package/package.json +1 -1
- package/scripts/diagnostics/a2a-conformance.sh +0 -0
- package/scripts/diagnostics/aliases-manifest.sh +33 -6
- package/scripts/diagnostics/benchmark.sh +27 -1
- package/scripts/diagnostics/doctor.sh +18 -11
- package/scripts/diagnostics/health.sh +12 -4
- package/scripts/diagnostics/mcp-doctor.sh +3 -0
- package/scripts/diagnostics/secret-governance.sh +7 -1
- package/scripts/diagnostics/security-score.sh +14 -5
- package/scripts/diagnostics/verify_state.sh +9 -1
- package/scripts/diagnostics/workstation-attestation.sh +0 -0
- package/scripts/dot/commands/ai.sh +40 -31
- package/scripts/dot/commands/aliases.sh +28 -4
- package/scripts/dot/commands/appearance.sh +16 -0
- package/scripts/dot/commands/core.sh +17 -0
- package/scripts/dot/commands/diagnostics.sh +19 -0
- package/scripts/dot/commands/fleet.sh +1 -1
- package/scripts/dot/commands/manual.sh +4 -4
- package/scripts/dot/commands/meta.sh +87 -12
- package/scripts/dot/commands/registry.sh +164 -15
- package/scripts/dot/commands/secrets.sh +17 -0
- package/scripts/dot/commands/security.sh +17 -0
- package/scripts/dot/commands/tools.sh +19 -0
- package/scripts/dot/powershell/Dot.psm1 +146 -26
- package/scripts/fonts/install-nerd-fonts.sh +16 -10
- package/scripts/git-hooks/pre-commit-audit.sh +1 -1
- package/scripts/ops/ai-setup.sh +13 -6
- package/scripts/ops/bundle.sh +31 -5
- package/scripts/ops/chezmoi-apply.sh +5 -0
- package/scripts/ops/heal-tools.sh +39 -111
- package/scripts/ops/post-apply-repair.sh +0 -0
- package/scripts/ops/release.sh +14 -5
- package/scripts/qa/docs-coverage.sh +1 -1
- package/scripts/qa/reliability-audit.sh +4 -4
- package/scripts/qa/scorecard-snapshot.sh +3 -3
- package/scripts/qa/validate-examples.sh +0 -0
- package/scripts/qa/wsl-contract.sh +0 -0
- package/scripts/secrets/age-init.sh +15 -4
- package/scripts/theme/apply-gnome-theme.sh +6 -2
- package/scripts/theme/extract-theme.py +97 -33
- package/scripts/theme/rebuild-themes.sh +240 -35
- package/scripts/theme/switch.sh +26 -16
- package/scripts/theme/wallpaper-sync.sh +95 -7
- package/scripts/tools/detect-collisions.py +0 -0
- package/scripts/version-sync.sh +108 -44
package/scripts/ops/bundle.sh
CHANGED
|
@@ -15,12 +15,38 @@ source "$SCRIPT_DIR/../lib/dot/ui.sh"
|
|
|
15
15
|
source "$SCRIPT_DIR/../lib/dot/log.sh"
|
|
16
16
|
export DOT_COMMAND="bundle"
|
|
17
17
|
|
|
18
|
+
usage() {
|
|
19
|
+
cat <<'EOF'
|
|
20
|
+
Usage: bundle.sh [output-dir]
|
|
21
|
+
|
|
22
|
+
Create an offline dotfiles bundle as a .tar.zst archive.
|
|
23
|
+
EOF
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
case "${1:-}" in
|
|
27
|
+
--help | -h)
|
|
28
|
+
usage
|
|
29
|
+
exit 0
|
|
30
|
+
;;
|
|
31
|
+
-*)
|
|
32
|
+
printf 'Unknown option: %s\n' "$1" >&2
|
|
33
|
+
usage >&2
|
|
34
|
+
exit 2
|
|
35
|
+
;;
|
|
36
|
+
esac
|
|
37
|
+
|
|
18
38
|
# Concurrency guard
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
LOCK_DIR="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}"
|
|
40
|
+
if [[ ! -d "$LOCK_DIR" || ! -w "$LOCK_DIR" ]]; then
|
|
41
|
+
LOCK_DIR="${TMPDIR:-/tmp}"
|
|
42
|
+
fi
|
|
43
|
+
LOCK_FILE="$LOCK_DIR/dotfiles-bundle.lock"
|
|
44
|
+
if command -v flock >/dev/null 2>&1; then
|
|
45
|
+
exec 9>"$LOCK_FILE"
|
|
46
|
+
if ! flock -n 9; then
|
|
47
|
+
ui_warn "Already running" "Another bundle instance is active"
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
24
50
|
fi
|
|
25
51
|
|
|
26
52
|
OUTPUT_DIR="${1:-$HOME/Downloads}"
|
|
@@ -199,6 +199,7 @@ _AI_PROVIDERS=(
|
|
|
199
199
|
"copilot|npm:@github/copilot|Copilot CLI"
|
|
200
200
|
"goose|native|Goose"
|
|
201
201
|
"agy|native|Antigravity CLI"
|
|
202
|
+
"kimi|native|Kimi CLI"
|
|
202
203
|
"sgpt|pipx:shell-gpt|Shell-GPT"
|
|
203
204
|
"ollama|aqua:ollama/ollama|Ollama"
|
|
204
205
|
"opencode|npm:opencode-ai|OpenCode"
|
|
@@ -276,6 +277,10 @@ if [[ ${#_ai_missing[@]} -gt 0 ]] && [[ "$INTERACTIVE" == "1" ]]; then
|
|
|
276
277
|
install_agy_native "$_label"
|
|
277
278
|
continue
|
|
278
279
|
fi
|
|
280
|
+
if [[ "$_bin" == "kimi" ]]; then
|
|
281
|
+
install_kimi_native "$_label"
|
|
282
|
+
continue
|
|
283
|
+
fi
|
|
279
284
|
if command -v gum &>/dev/null; then
|
|
280
285
|
if gum spin --spinner dot --title "Installing $_label ($_pkg)" -- \
|
|
281
286
|
mise use -g "$_pkg@latest" 2>&1; then
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
4
|
# shellcheck disable=SC2034
|
|
5
5
|
# =============================================================================
|
|
6
|
+
|
|
7
|
+
HEAL_TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
# shellcheck source=../../lib/dot/verified-download.sh disable=SC1091
|
|
9
|
+
source "$HEAL_TOOLS_DIR/../../lib/dot/verified-download.sh"
|
|
6
10
|
# heal-tools.sh — Tool installation helpers for heal.sh
|
|
7
11
|
# Sourced by heal.sh; inherits set -euo pipefail, ui.sh, and shared variables.
|
|
8
12
|
# =============================================================================
|
|
@@ -123,33 +127,56 @@ get_package_name() {
|
|
|
123
127
|
# Usage: _pkg_install "label" completed total command [args...]
|
|
124
128
|
_pkg_install() { ui_run_cmd "$@"; }
|
|
125
129
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
_mise_tool_specs() {
|
|
131
|
+
case "$1" in
|
|
132
|
+
nushell) printf '%s\n' 'aqua:nushell/nushell@0.114.1' ;;
|
|
133
|
+
pueue)
|
|
134
|
+
printf '%s\n' 'aqua:Nukesor/pueue/pueue@4.0.4'
|
|
135
|
+
printf '%s\n' 'aqua:Nukesor/pueue/pueued@4.0.4'
|
|
136
|
+
;;
|
|
137
|
+
wasmtime) printf '%s\n' 'wasmtime@47.0.3' ;;
|
|
138
|
+
sops) printf '%s\n' 'sops@3.13.3' ;;
|
|
139
|
+
yazi) printf '%s\n' 'yazi@26.5.6' ;;
|
|
140
|
+
zellij) printf '%s\n' 'zellij@0.44.3' ;;
|
|
141
|
+
*) return 1 ;;
|
|
142
|
+
esac
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
_install_with_mise() {
|
|
146
|
+
local cmd="$1" spec
|
|
147
|
+
command -v mise >/dev/null 2>&1 || return 1
|
|
148
|
+
while IFS= read -r spec; do
|
|
149
|
+
[[ -n "$spec" ]] || continue
|
|
150
|
+
# --pin writes an exact version to the user's writable global config;
|
|
151
|
+
# aqua/ubi backends verify publisher-provided checksums before install.
|
|
152
|
+
mise use --global --pin "$spec" || return 1
|
|
153
|
+
done < <(_mise_tool_specs "$cmd")
|
|
129
154
|
}
|
|
130
155
|
|
|
131
156
|
# Install a single package (dispatcher for _pkg_install to call in subshell)
|
|
132
157
|
_do_install() {
|
|
133
158
|
local cmd="$1"
|
|
134
159
|
local pkg_mgr="$2"
|
|
135
|
-
|
|
136
|
-
|
|
160
|
+
|
|
161
|
+
case "$cmd" in
|
|
162
|
+
nushell | pueue | wasmtime | sops | yazi | zellij)
|
|
163
|
+
if _install_with_mise "$cmd"; then
|
|
164
|
+
return 0
|
|
165
|
+
fi
|
|
166
|
+
log_warn "mise install unavailable for $cmd; trying $pkg_mgr"
|
|
167
|
+
;;
|
|
168
|
+
esac
|
|
137
169
|
|
|
138
170
|
# Binary/curl installers for tools not in standard apt repos
|
|
139
171
|
case "$cmd" in
|
|
140
172
|
starship)
|
|
141
173
|
local installer
|
|
142
174
|
installer=$(umask 077 && mktemp)
|
|
143
|
-
if !
|
|
175
|
+
if ! download_verified_script https://starship.rs/install.sh "$installer"; then
|
|
144
176
|
rm -f "$installer"
|
|
145
177
|
log_error "Failed to download starship installer"
|
|
146
178
|
return 1
|
|
147
179
|
fi
|
|
148
|
-
if ! head -1 "$installer" | grep -q '^#!/'; then
|
|
149
|
-
rm -f "$installer"
|
|
150
|
-
log_error "starship installer does not look like a shell script"
|
|
151
|
-
return 1
|
|
152
|
-
fi
|
|
153
180
|
sh "$installer" --yes
|
|
154
181
|
local rc=$?
|
|
155
182
|
rm -f "$installer"
|
|
@@ -158,115 +185,16 @@ _do_install() {
|
|
|
158
185
|
atuin)
|
|
159
186
|
local installer
|
|
160
187
|
installer=$(umask 077 && mktemp)
|
|
161
|
-
if !
|
|
188
|
+
if ! download_verified_script https://setup.atuin.sh "$installer"; then
|
|
162
189
|
rm -f "$installer"
|
|
163
190
|
log_error "Failed to download atuin installer"
|
|
164
191
|
return 1
|
|
165
192
|
fi
|
|
166
|
-
if ! head -1 "$installer" | grep -q '^#!/'; then
|
|
167
|
-
rm -f "$installer"
|
|
168
|
-
log_error "atuin installer does not look like a shell script"
|
|
169
|
-
return 1
|
|
170
|
-
fi
|
|
171
193
|
bash "$installer" --yes
|
|
172
194
|
local rc=$?
|
|
173
195
|
rm -f "$installer"
|
|
174
196
|
return $rc
|
|
175
197
|
;;
|
|
176
|
-
nushell)
|
|
177
|
-
local arch tag ver
|
|
178
|
-
arch=$(uname -m)
|
|
179
|
-
tag=$(_gh_latest_tag "nushell/nushell")
|
|
180
|
-
ver="${tag#v}"
|
|
181
|
-
local tmp
|
|
182
|
-
tmp=$(mktemp -d)
|
|
183
|
-
curl -fsSL -o "$tmp/nu.tar.gz" \
|
|
184
|
-
"https://github.com/nushell/nushell/releases/download/${tag}/nu-${ver}-${arch}-unknown-linux-musl.tar.gz" &&
|
|
185
|
-
tar xzf "$tmp/nu.tar.gz" -C "$tmp" --strip-components=1 &&
|
|
186
|
-
install -m 755 "$tmp/nu" "$bin_dir/nu"
|
|
187
|
-
local rc=$?
|
|
188
|
-
rm -rf "$tmp"
|
|
189
|
-
return $rc
|
|
190
|
-
;;
|
|
191
|
-
pueue)
|
|
192
|
-
local arch
|
|
193
|
-
arch=$(uname -m)
|
|
194
|
-
curl -fsSL -o "$bin_dir/pueue" \
|
|
195
|
-
"https://github.com/Nukesor/pueue/releases/latest/download/pueue-${arch}-unknown-linux-musl" &&
|
|
196
|
-
chmod +x "$bin_dir/pueue"
|
|
197
|
-
curl -fsSL -o "$bin_dir/pueued" \
|
|
198
|
-
"https://github.com/Nukesor/pueue/releases/latest/download/pueued-${arch}-unknown-linux-musl" &&
|
|
199
|
-
chmod +x "$bin_dir/pueued"
|
|
200
|
-
return $?
|
|
201
|
-
;;
|
|
202
|
-
wasmtime)
|
|
203
|
-
local arch tag
|
|
204
|
-
arch=$(uname -m)
|
|
205
|
-
tag=$(_gh_latest_tag "bytecodealliance/wasmtime")
|
|
206
|
-
local tmp
|
|
207
|
-
tmp=$(mktemp -d)
|
|
208
|
-
# xz-utils required for .tar.xz extraction
|
|
209
|
-
command -v xz >/dev/null 2>&1 || sudo apt-get install -y -qq xz-utils >/dev/null 2>&1 || true
|
|
210
|
-
curl -fsSL -o "$tmp/wasmtime.tar.xz" \
|
|
211
|
-
"https://github.com/bytecodealliance/wasmtime/releases/download/${tag}/wasmtime-${tag}-${arch}-linux.tar.xz" &&
|
|
212
|
-
tar xJf "$tmp/wasmtime.tar.xz" -C "$tmp" --strip-components=1 &&
|
|
213
|
-
install -m 755 "$tmp/wasmtime" "$bin_dir/wasmtime"
|
|
214
|
-
local rc=$?
|
|
215
|
-
rm -rf "$tmp"
|
|
216
|
-
return $rc
|
|
217
|
-
;;
|
|
218
|
-
sops)
|
|
219
|
-
local arch tag
|
|
220
|
-
arch=$(uname -m)
|
|
221
|
-
[[ "$arch" == "x86_64" ]] && arch="amd64"
|
|
222
|
-
[[ "$arch" == "aarch64" ]] && arch="arm64"
|
|
223
|
-
tag=$(_gh_latest_tag "getsops/sops")
|
|
224
|
-
curl -fsSL -o "$bin_dir/sops" \
|
|
225
|
-
"https://github.com/getsops/sops/releases/download/${tag}/sops-${tag}.linux.${arch}" &&
|
|
226
|
-
chmod +x "$bin_dir/sops"
|
|
227
|
-
return $?
|
|
228
|
-
;;
|
|
229
|
-
hyperfine)
|
|
230
|
-
local arch tag
|
|
231
|
-
arch=$(uname -m)
|
|
232
|
-
tag=$(_gh_latest_tag "sharkdp/hyperfine")
|
|
233
|
-
local tmp
|
|
234
|
-
tmp=$(mktemp -d)
|
|
235
|
-
curl -fsSL -o "$tmp/hyperfine.tar.gz" \
|
|
236
|
-
"https://github.com/sharkdp/hyperfine/releases/download/${tag}/hyperfine-${tag}-${arch}-unknown-linux-musl.tar.gz" &&
|
|
237
|
-
tar xzf "$tmp/hyperfine.tar.gz" -C "$tmp" --strip-components=1 &&
|
|
238
|
-
install -m 755 "$tmp/hyperfine" "$bin_dir/hyperfine"
|
|
239
|
-
local rc=$?
|
|
240
|
-
rm -rf "$tmp"
|
|
241
|
-
return $rc
|
|
242
|
-
;;
|
|
243
|
-
yazi)
|
|
244
|
-
local arch
|
|
245
|
-
arch=$(uname -m)
|
|
246
|
-
local url="https://github.com/sxyazi/yazi/releases/latest/download/yazi-${arch}-unknown-linux-musl.zip"
|
|
247
|
-
local tmp
|
|
248
|
-
tmp=$(mktemp -d)
|
|
249
|
-
command -v unzip >/dev/null 2>&1 || sudo apt-get install -y -qq unzip
|
|
250
|
-
curl -fsSL -o "$tmp/yazi.zip" "$url" &&
|
|
251
|
-
(cd "$tmp" && unzip -oq yazi.zip) &&
|
|
252
|
-
install -m 755 "$tmp"/yazi-*/yazi "$bin_dir/yazi"
|
|
253
|
-
local rc=$?
|
|
254
|
-
rm -rf "$tmp"
|
|
255
|
-
return $rc
|
|
256
|
-
;;
|
|
257
|
-
zellij)
|
|
258
|
-
local arch
|
|
259
|
-
arch=$(uname -m)
|
|
260
|
-
local url="https://github.com/zellij-org/zellij/releases/latest/download/zellij-${arch}-unknown-linux-musl.tar.gz"
|
|
261
|
-
local tmp
|
|
262
|
-
tmp=$(mktemp -d)
|
|
263
|
-
curl -fsSL -o "$tmp/zellij.tar.gz" "$url" &&
|
|
264
|
-
tar xzf "$tmp/zellij.tar.gz" -C "$tmp" &&
|
|
265
|
-
install -m 755 "$tmp/zellij" "$bin_dir/zellij"
|
|
266
|
-
local rc=$?
|
|
267
|
-
rm -rf "$tmp"
|
|
268
|
-
return $rc
|
|
269
|
-
;;
|
|
270
198
|
esac
|
|
271
199
|
|
|
272
200
|
# System package manager
|
|
File without changes
|
package/scripts/ops/release.sh
CHANGED
|
@@ -19,6 +19,8 @@ source "$SCRIPT_DIR/../../lib/dot/ui.sh"
|
|
|
19
19
|
source "$SCRIPT_DIR/../../lib/dot/log.sh"
|
|
20
20
|
export DOT_COMMAND="release"
|
|
21
21
|
ui_init
|
|
22
|
+
BOLD="${BOLD:-}"
|
|
23
|
+
NC="${NC:-}"
|
|
22
24
|
|
|
23
25
|
log_info() { ui_info "$@"; }
|
|
24
26
|
log_success() { ui_ok "$@"; }
|
|
@@ -129,11 +131,18 @@ main() {
|
|
|
129
131
|
done
|
|
130
132
|
|
|
131
133
|
# Concurrency guard
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if !
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
local lock_dir
|
|
135
|
+
lock_dir="${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}"
|
|
136
|
+
if [[ ! -d "$lock_dir" || ! -w "$lock_dir" ]]; then
|
|
137
|
+
lock_dir="${TMPDIR:-/tmp}"
|
|
138
|
+
fi
|
|
139
|
+
LOCK_FILE="$lock_dir/dotfiles-release.lock"
|
|
140
|
+
if command -v flock >/dev/null 2>&1; then
|
|
141
|
+
exec 9>"$LOCK_FILE"
|
|
142
|
+
if ! flock -n 9; then
|
|
143
|
+
ui_warn "Already running" "Another release is in progress"
|
|
144
|
+
exit 0
|
|
145
|
+
fi
|
|
137
146
|
fi
|
|
138
147
|
|
|
139
148
|
cd "$PROJECT_ROOT"
|
|
@@ -54,7 +54,7 @@ check_dot_command_docs() {
|
|
|
54
54
|
check_ai_provider_docs() {
|
|
55
55
|
local provider=""
|
|
56
56
|
|
|
57
|
-
for provider in cl copilot agy kiro sgpt ollama opencode aider; do
|
|
57
|
+
for provider in cl copilot kimi agy kiro sgpt ollama opencode aider; do
|
|
58
58
|
record_doc_check "dot $provider in AI.md" "\`dot $provider\`" "$AI_DOC"
|
|
59
59
|
done
|
|
60
60
|
}
|
|
@@ -65,12 +65,12 @@ shell_syntax() {
|
|
|
65
65
|
|
|
66
66
|
unit_tests() {
|
|
67
67
|
cd "$REPO_ROOT"
|
|
68
|
-
./tests/framework/test_runner.sh
|
|
68
|
+
./tests/framework/test_runner.sh --jobs auto
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
integration_tests() {
|
|
72
72
|
cd "$REPO_ROOT"
|
|
73
|
-
|
|
73
|
+
./tests/framework/test_runner.sh --jobs auto --integration-only
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
coverage_gate() {
|
|
@@ -123,11 +123,11 @@ fi
|
|
|
123
123
|
|
|
124
124
|
printf 'Reliability Audit\n'
|
|
125
125
|
printf 'Platform: %s\n' "$(platform_name)"
|
|
126
|
-
printf '
|
|
126
|
+
printf 'Executable module-mapping floor: %s%%\n' "$min_coverage"
|
|
127
127
|
|
|
128
128
|
run_step "Shell syntax" shell_syntax
|
|
129
129
|
run_step "Unit suite" unit_tests
|
|
130
|
-
run_step "
|
|
130
|
+
run_step "Executable module mapping" coverage_gate
|
|
131
131
|
run_step "Docs coverage" docs_coverage_gate
|
|
132
132
|
run_step "Traceability coverage" traceability_gate
|
|
133
133
|
run_step "Executable examples" example_gate
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
set -euo pipefail
|
|
22
22
|
|
|
23
|
-
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
23
|
+
REPO_ROOT="${REPO_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
|
|
24
24
|
cd "$REPO_ROOT"
|
|
25
25
|
|
|
26
26
|
mode="write"
|
|
@@ -51,7 +51,7 @@ if ! command -v jq >/dev/null 2>&1; then
|
|
|
51
51
|
exit 2
|
|
52
52
|
fi
|
|
53
53
|
|
|
54
|
-
payload="$(curl -
|
|
54
|
+
payload="$(curl --proto '=https' --tlsv1.2 -fsSL --connect-timeout 10 --max-time 30 "$api")" || {
|
|
55
55
|
echo "Scorecard API unreachable: $api" >&2
|
|
56
56
|
exit 2
|
|
57
57
|
}
|
|
@@ -75,7 +75,7 @@ trap 'rm -f "$block"' EXIT
|
|
|
75
75
|
| [.name, (.score|tostring), ((.reason // "") | gsub("\\|"; "\\|") | .[0:100])]
|
|
76
76
|
| "| " + (. | join(" | ")) + " |"'
|
|
77
77
|
# shellcheck disable=SC2016 # backticks here are markdown code-spans, not subshells
|
|
78
|
-
printf '\n_Refresh: `scripts/qa/scorecard-snapshot.sh` ·
|
|
78
|
+
printf '\n_Refresh: `scripts/qa/scorecard-snapshot.sh` · Monitoring: `.github/workflows/scorecard.yml`._\n'
|
|
79
79
|
printf '<!-- END scorecard-snapshot -->\n'
|
|
80
80
|
} >"$block"
|
|
81
81
|
|
|
File without changes
|
|
File without changes
|
|
@@ -39,7 +39,8 @@ if config_path.exists():
|
|
|
39
39
|
|
|
40
40
|
lines = content.splitlines()
|
|
41
41
|
|
|
42
|
-
# Remove existing encryption
|
|
42
|
+
# Remove existing encryption keys and age blocks regardless of where a previous
|
|
43
|
+
# run placed them.
|
|
43
44
|
out = []
|
|
44
45
|
in_age = False
|
|
45
46
|
for line in lines:
|
|
@@ -57,9 +58,19 @@ for line in lines:
|
|
|
57
58
|
continue
|
|
58
59
|
out.append(line)
|
|
59
60
|
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
# Keep encryption at top level; chezmoi warns when it is nested under [data].
|
|
62
|
+
insert_at = 0
|
|
63
|
+
for i, line in enumerate(out):
|
|
64
|
+
if line.strip().startswith("["):
|
|
65
|
+
insert_at = i
|
|
66
|
+
break
|
|
67
|
+
else:
|
|
68
|
+
insert_at = len(out)
|
|
69
|
+
|
|
70
|
+
top_level = ["encryption = \"age\"", ""]
|
|
71
|
+
out[insert_at:insert_at] = top_level
|
|
72
|
+
|
|
73
|
+
# Append updated age config (use json.dumps for safe string escaping)
|
|
63
74
|
out.append("")
|
|
64
75
|
out.append("[age]")
|
|
65
76
|
out.append(f"identity = {json.dumps(key_file)}")
|
|
@@ -56,7 +56,7 @@ success() {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
# Check if we're running GNOME
|
|
59
|
-
if [ "$XDG_CURRENT_DESKTOP" != "GNOME" ] && [ "$XDG_SESSION_DESKTOP" != "gnome" ]; then
|
|
59
|
+
if [ "${XDG_CURRENT_DESKTOP:-}" != "GNOME" ] && [ "${XDG_SESSION_DESKTOP:-}" != "gnome" ]; then
|
|
60
60
|
warn "Not running GNOME desktop. Some settings may not apply."
|
|
61
61
|
fi
|
|
62
62
|
|
|
@@ -208,7 +208,11 @@ apply_wallpaper() {
|
|
|
208
208
|
# Pick a random wallpaper matching the mode from curated collection
|
|
209
209
|
local wp=""
|
|
210
210
|
if [ -d "$wallpaper_dir" ]; then
|
|
211
|
-
|
|
211
|
+
if command -v shuf >/dev/null 2>&1; then
|
|
212
|
+
wp="$(find "$wallpaper_dir" -maxdepth 1 -type f -iname "*-${mode}.jpg" 2>/dev/null | shuf -n 1 || true)"
|
|
213
|
+
else
|
|
214
|
+
wp="$(find "$wallpaper_dir" -maxdepth 1 -type f -iname "*-${mode}.jpg" 2>/dev/null | sort | head -n 1 || true)"
|
|
215
|
+
fi
|
|
212
216
|
fi
|
|
213
217
|
|
|
214
218
|
# Fallback: check Catppuccin system/user wallpapers
|
|
@@ -339,42 +339,77 @@ def _nvim_from_hue(hue: float, is_dark: bool) -> Tuple[str, str]:
|
|
|
339
339
|
|
|
340
340
|
|
|
341
341
|
def _macos_accent_from_hue(hue: float) -> int:
|
|
342
|
-
"""Map
|
|
343
|
-
|
|
342
|
+
"""Map a CIELAB hue angle to a macOS accent-colour integer.
|
|
343
|
+
|
|
344
|
+
Boundaries are calibrated to the CIELAB hues of Apple's own accent
|
|
345
|
+
swatches (not HSL): red≈36°, orange≈67°, yellow≈87°, green≈144°,
|
|
346
|
+
blue≈287°, purple≈317°. The previous HSL-style cutoffs pushed blue
|
|
347
|
+
accents (~283°) into Purple and purple into Pink.
|
|
348
|
+
"""
|
|
349
|
+
hue = hue % 360
|
|
350
|
+
if hue < 15 or hue >= 345:
|
|
351
|
+
return 6 # Pink / magenta
|
|
352
|
+
if hue < 50:
|
|
344
353
|
return 0 # Red
|
|
345
|
-
if hue <
|
|
354
|
+
if hue < 77:
|
|
346
355
|
return 1 # Orange
|
|
347
|
-
if hue <
|
|
356
|
+
if hue < 110:
|
|
348
357
|
return 2 # Yellow
|
|
349
|
-
if hue <
|
|
358
|
+
if hue < 200:
|
|
350
359
|
return 3 # Green
|
|
351
|
-
if hue < 255:
|
|
352
|
-
return 4 # Blue
|
|
353
360
|
if hue < 300:
|
|
354
|
-
return
|
|
355
|
-
return
|
|
361
|
+
return 4 # Blue (incl. teal-blue and navy)
|
|
362
|
+
return 5 # Purple / indigo (300–345)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _env_hex(key: str, fallback: str) -> str:
|
|
366
|
+
"""A #rrggbb value from env, or the fallback when unset/invalid."""
|
|
367
|
+
v = os.environ.get(key, "")
|
|
368
|
+
if len(v) == 7 and v[0] == "#":
|
|
369
|
+
try:
|
|
370
|
+
hex_to_rgb(v)
|
|
371
|
+
return v
|
|
372
|
+
except ValueError:
|
|
373
|
+
pass
|
|
374
|
+
return fallback
|
|
356
375
|
|
|
357
376
|
|
|
358
377
|
def _compute_bg_fg(clusters, is_dark):
|
|
359
|
-
"""
|
|
360
|
-
|
|
378
|
+
"""Terminal background/foreground — fixed, engineered neutrals per mode.
|
|
379
|
+
|
|
380
|
+
Deliberately NOT derived from the wallpaper: a terminal wants a
|
|
381
|
+
high-contrast, low-eye-strain surface that stays stable across themes.
|
|
382
|
+
Dark mode uses a deep neutral grey with off-white text; light mode a warm
|
|
383
|
+
paper cream with charcoal text. The wallpaper still drives the accent and
|
|
384
|
+
the 16 ANSI colours (which are computed against this bg for contrast).
|
|
385
|
+
Override per mode via DOTFILES_TERM_BG_DARK / _FG_DARK / _BG_LIGHT /
|
|
386
|
+
_FG_LIGHT.
|
|
387
|
+
"""
|
|
388
|
+
_ = clusters # bg/fg no longer wallpaper-derived
|
|
361
389
|
if is_dark:
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
fg_lab = (88.0, bg_lab[1] * 0.1, bg_lab[2] * 0.1)
|
|
390
|
+
bg_rgb = hex_to_rgb(_env_hex("DOTFILES_TERM_BG_DARK", "#1e1e2e"))
|
|
391
|
+
fg_rgb = hex_to_rgb(_env_hex("DOTFILES_TERM_FG_DARK", "#d4d4d4"))
|
|
365
392
|
else:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
bg_rgb = lab_to_rgb(*bg_lab)
|
|
370
|
-
fg_rgb = ensure_contrast(lab_to_rgb(*fg_lab), bg_rgb, 7.0, is_dark)
|
|
393
|
+
bg_rgb = hex_to_rgb(_env_hex("DOTFILES_TERM_BG_LIGHT", "#fbf1c7"))
|
|
394
|
+
fg_rgb = hex_to_rgb(_env_hex("DOTFILES_TERM_FG_LIGHT", "#3c3836"))
|
|
395
|
+
bg_lab = rgb_to_lab(*bg_rgb)
|
|
371
396
|
return bg_lab, bg_rgb, fg_rgb
|
|
372
397
|
|
|
373
398
|
|
|
374
399
|
def _compute_accent(clusters, is_dark):
|
|
375
|
-
"""Select
|
|
376
|
-
|
|
377
|
-
|
|
400
|
+
"""Select the accent from the wallpaper's DOMINANT chromatic colour.
|
|
401
|
+
|
|
402
|
+
Rank clusters by population x chroma so the accent follows the *main*
|
|
403
|
+
colour of the wallpaper (a large, colourful region) rather than the
|
|
404
|
+
single most-saturated cluster — which is often a tiny vivid splash
|
|
405
|
+
that doesn't represent the image. Near-neutral clusters score ~0 and
|
|
406
|
+
are skipped; if the whole image is neutral we fall back to the most
|
|
407
|
+
saturated cluster so the accent still carries a hue. The selected
|
|
408
|
+
colour is then AAA-darkened below (ADR-009 contrast requirement)."""
|
|
409
|
+
ranked = sorted(clusters, key=lambda c: c[1] * lab_chroma(*c[0]), reverse=True)
|
|
410
|
+
accent_lab = ranked[0][0]
|
|
411
|
+
if lab_chroma(*accent_lab) < 5.0:
|
|
412
|
+
accent_lab = max(clusters, key=lambda c: lab_chroma(*c[0]))[0]
|
|
378
413
|
if is_dark:
|
|
379
414
|
accent_lab = (max(accent_lab[0], 35.0), accent_lab[1], accent_lab[2])
|
|
380
415
|
else:
|
|
@@ -412,16 +447,30 @@ def _compute_panel_border(bg_lab, bg_rgb, is_dark):
|
|
|
412
447
|
|
|
413
448
|
def _build_ansi_color(base_lab, accent_lab, bg_rgb, is_dark):
|
|
414
449
|
"""Build normal + bright ANSI variant from a base Lab color."""
|
|
450
|
+
normal_L = (
|
|
451
|
+
max(55.0, min(75.0, base_lab[0])) if is_dark
|
|
452
|
+
else max(30.0, min(50.0, base_lab[0]))
|
|
453
|
+
)
|
|
454
|
+
normal = adjust_lightness(base_lab, normal_L)
|
|
415
455
|
if is_dark:
|
|
416
|
-
|
|
417
|
-
|
|
456
|
+
# Dark bg: the bright variant pops by getting lighter.
|
|
457
|
+
bright = adjust_lightness(base_lab, normal_L + 12)
|
|
458
|
+
bright_min = 4.5
|
|
418
459
|
else:
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
460
|
+
# Light bg: a lighter bright would wash out against near-white, so
|
|
461
|
+
# brighten by vividness at equal lightness instead — the bright is
|
|
462
|
+
# never darker than the normal (matches Apple's light ANSI ramp,
|
|
463
|
+
# where brights read as more saturated, not muddier).
|
|
464
|
+
bright = (normal[0], normal[1] * 1.25, normal[2] * 1.25)
|
|
465
|
+
bright_min = 7.0
|
|
466
|
+
# WCAG AAA (7:1) for the chromatic slots on a light bg so coloured text
|
|
467
|
+
# (paths, syntax) is unambiguously legible — AA (4.5:1) still read as washed
|
|
468
|
+
# out on cream. Brights stay AAA too and differ from normals by saturation
|
|
469
|
+
# (Apple's light ramp), not lightness. Dark mode keeps its lower floor
|
|
470
|
+
# (light text on dark reads comfortably at a lower ratio).
|
|
471
|
+
normal_min = 3.0 if is_dark else 7.0
|
|
472
|
+
normal_rgb = ensure_contrast(lab_to_rgb(*normal), bg_rgb, normal_min, is_dark)
|
|
473
|
+
bright_rgb = ensure_contrast(lab_to_rgb(*bright), bg_rgb, bright_min, is_dark)
|
|
425
474
|
return normal_rgb, bright_rgb
|
|
426
475
|
|
|
427
476
|
|
|
@@ -455,11 +504,18 @@ def _structural_colors(bg_lab, bg_rgb, is_dark):
|
|
|
455
504
|
ensure_contrast(lab_to_rgb(bg_lab[0] + 25, bg_lab[1], bg_lab[2]), bg_rgb, 2.5, True),
|
|
456
505
|
ensure_contrast(lab_to_rgb(90.0, bg_lab[1] * 0.05, bg_lab[2] * 0.05), bg_rgb, 7.0, True),
|
|
457
506
|
)
|
|
507
|
+
# Light bg: the neutral ramp increases in lightness (c0 < c8 < c7 < c15).
|
|
508
|
+
# c0/c8/c7 stay readable (>= AA); c15 ("bright white") is the LIGHTEST tone,
|
|
509
|
+
# strictly lighter than c7 — bright-white text legibility is gated by fg, not
|
|
510
|
+
# c15, and forcing c15 to the same floor as c7 made them converge (c15 ended
|
|
511
|
+
# a hair darker, breaking the ramp). Start c15 well above c7 with only a mild
|
|
512
|
+
# floor so it stays the lightest. (Terminals render the dark palette; this
|
|
513
|
+
# ramp only feeds non-terminal light-palette consumers.)
|
|
458
514
|
return (
|
|
459
515
|
ensure_contrast(lab_to_rgb(18.0, bg_lab[1] * 0.2, bg_lab[2] * 0.2), bg_rgb, 7.0, False),
|
|
460
|
-
ensure_contrast(lab_to_rgb(
|
|
516
|
+
ensure_contrast(lab_to_rgb(50.0, bg_lab[1] * 0.15, bg_lab[2] * 0.15), bg_rgb, 4.5, False),
|
|
461
517
|
ensure_contrast(lab_to_rgb(35.0, bg_lab[1] * 0.2, bg_lab[2] * 0.2), bg_rgb, 4.5, False),
|
|
462
|
-
ensure_contrast(lab_to_rgb(
|
|
518
|
+
ensure_contrast(lab_to_rgb(64.0, bg_lab[1] * 0.08, bg_lab[2] * 0.08), bg_rgb, 2.5, False),
|
|
463
519
|
)
|
|
464
520
|
|
|
465
521
|
|
|
@@ -664,7 +720,15 @@ def main():
|
|
|
664
720
|
# Generate theme
|
|
665
721
|
theme = generate_theme(clusters, name, is_dark)
|
|
666
722
|
wp_base = args.image.split("[")[0] if "[" in args.image else args.image
|
|
667
|
-
|
|
723
|
+
wp_abs = os.path.abspath(wp_base)
|
|
724
|
+
# Store home-relative with a `~` so the committed themes.toml is not tied to
|
|
725
|
+
# one machine's username. Consumers (wallpaper-sync.sh) expand `~/`. System
|
|
726
|
+
# wallpapers (/System/..., /usr/share/...) stay absolute — identical on
|
|
727
|
+
# every host anyway.
|
|
728
|
+
home = os.path.expanduser("~")
|
|
729
|
+
if wp_abs == home or wp_abs.startswith(home + os.sep):
|
|
730
|
+
wp_abs = "~" + wp_abs[len(home):]
|
|
731
|
+
theme["wallpaper"] = wp_abs
|
|
668
732
|
theme["source"] = args.source
|
|
669
733
|
|
|
670
734
|
# Output
|