@sebastienrousseau/dotfiles 0.2.512 → 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 +48 -0
- package/README.md +2 -2
- package/docs/COPYRIGHT +1 -1
- package/docs/manual/00-introduction.md +1 -1
- package/docs/manual/01-concepts/02-trust-model.md +2 -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/operations/COVERAGE.md +7 -8
- package/docs/operations/REGISTRY.md +20 -19
- package/docs/reference/POWERSHELL_PARITY.md +28 -27
- 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/health.sh +4 -1
- package/scripts/diagnostics/mcp-doctor.sh +0 -0
- package/scripts/diagnostics/verify_state.sh +0 -0
- package/scripts/diagnostics/workstation-attestation.sh +0 -0
- package/scripts/dot/commands/ai.sh +3 -1
- package/scripts/dot/commands/fleet.sh +1 -1
- package/scripts/dot/commands/registry.sh +164 -15
- 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/heal-tools.sh +39 -111
- package/scripts/ops/post-apply-repair.sh +0 -0
- package/scripts/qa/reliability-audit.sh +3 -3
- 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/tools/detect-collisions.py +0 -0
- package/scripts/version-sync.sh +27 -0
|
@@ -5,24 +5,18 @@
|
|
|
5
5
|
#
|
|
6
6
|
# scripts/dot/commands/registry.sh
|
|
7
7
|
#
|
|
8
|
-
# `dot registry` —
|
|
8
|
+
# `dot registry` — verified module registry for reusable chezmoi sources.
|
|
9
9
|
#
|
|
10
10
|
# §3 audit roadmap: ship a registry of reusable dotfile modules
|
|
11
11
|
# ("rust-dev-setup", "k8s-operator-laptop") to seed network effects.
|
|
12
12
|
# Hosted as a GitHub-Pages-indexed JSON file to keep ops cost near
|
|
13
13
|
# zero.
|
|
14
14
|
#
|
|
15
|
-
# Current state: SCAFFOLD ONLY. This file ships the CLI surface and
|
|
16
|
-
# the JSON contract (see _registry_default_url). The registry itself
|
|
17
|
-
# is empty; populating it is its own roadmap item.
|
|
18
|
-
#
|
|
19
15
|
# Subcommands:
|
|
20
16
|
# list Show modules in the configured registry
|
|
21
17
|
# search <q> Filter modules by keyword (name, description, tags)
|
|
22
18
|
# info <name> Print full metadata for a module
|
|
23
|
-
# install <name>
|
|
24
|
-
# prints what would be installed; full implementation
|
|
25
|
-
# needs a sandboxed apply pipeline)
|
|
19
|
+
# install <name> Verify and preview a module; --yes applies it.
|
|
26
20
|
# url Show the active registry URL
|
|
27
21
|
# set-url <u> Override the registry URL (writes to user config)
|
|
28
22
|
#
|
|
@@ -37,7 +31,8 @@
|
|
|
37
31
|
# "tags": ["rust", "dev", "language"],
|
|
38
32
|
# "maintainer": "alice@example.com",
|
|
39
33
|
# "version": "1.2.0",
|
|
40
|
-
# "
|
|
34
|
+
# "archive_url": "https://example.com/rust-dev-setup-1.2.0.tar.gz",
|
|
35
|
+
# "sha256": "<64 lowercase hexadecimal characters>" }
|
|
41
36
|
# ]
|
|
42
37
|
# }
|
|
43
38
|
|
|
@@ -48,6 +43,8 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
48
43
|
source "$SCRIPT_DIR/../../../lib/dot/ui.sh"
|
|
49
44
|
# shellcheck source=../../../lib/dot/utils.sh disable=SC1091
|
|
50
45
|
source "$SCRIPT_DIR/../../../lib/dot/utils.sh"
|
|
46
|
+
# shellcheck source=../../../lib/dot/verified-download.sh disable=SC1091
|
|
47
|
+
source "$SCRIPT_DIR/../../../lib/dot/verified-download.sh"
|
|
51
48
|
|
|
52
49
|
_registry_default_url() {
|
|
53
50
|
printf '%s\n' "https://sebastienrousseau.github.io/dotfiles/registry.json"
|
|
@@ -79,9 +76,32 @@ _registry_cache_dir() {
|
|
|
79
76
|
printf '%s/dotfiles/registry\n' "${XDG_CACHE_HOME:-$HOME/.cache}"
|
|
80
77
|
}
|
|
81
78
|
|
|
79
|
+
_registry_data_dir() {
|
|
80
|
+
printf '%s/dotfiles/modules\n' "${XDG_DATA_HOME:-$HOME/.local/share}"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_registry_validate_index() {
|
|
84
|
+
local index="$1"
|
|
85
|
+
jq -e '
|
|
86
|
+
.version == 1 and
|
|
87
|
+
(.modules | type == "array") and
|
|
88
|
+
all(.modules[];
|
|
89
|
+
(.name | test("^[a-z0-9][a-z0-9-]{0,31}$")) and
|
|
90
|
+
(.version | test("^[0-9]+\\.[0-9]+\\.[0-9]+([+-][0-9A-Za-z.-]+)?$")) and
|
|
91
|
+
(.description | type == "string" and length <= 200) and
|
|
92
|
+
(.archive_url | test("^(https|file)://")) and
|
|
93
|
+
(.sha256 | test("^[0-9a-f]{64}$"))
|
|
94
|
+
)
|
|
95
|
+
' "$index" >/dev/null 2>&1
|
|
96
|
+
}
|
|
97
|
+
|
|
82
98
|
_registry_fetch() {
|
|
83
99
|
local url cache_dir cache_file
|
|
84
100
|
url="$(_registry_url)"
|
|
101
|
+
[[ "$url" =~ ^(https://|file://) ]] || {
|
|
102
|
+
ui_err "registry" "registry URL must use https:// (or file:// for local testing)"
|
|
103
|
+
return 1
|
|
104
|
+
}
|
|
85
105
|
cache_dir="$(_registry_cache_dir)"
|
|
86
106
|
cache_file="$cache_dir/index.json"
|
|
87
107
|
mkdir -p "$cache_dir"
|
|
@@ -89,7 +109,9 @@ _registry_fetch() {
|
|
|
89
109
|
local now mtime
|
|
90
110
|
now="$(date +%s)"
|
|
91
111
|
if [[ -s "$cache_file" ]]; then
|
|
92
|
-
if
|
|
112
|
+
if ! _registry_validate_index "$cache_file"; then
|
|
113
|
+
rm -f "$cache_file"
|
|
114
|
+
elif mtime="$(stat -c %Y "$cache_file" 2>/dev/null || stat -f %m "$cache_file" 2>/dev/null)"; then
|
|
93
115
|
if ((now - mtime < 21600)); then
|
|
94
116
|
printf '%s\n' "$cache_file"
|
|
95
117
|
return 0
|
|
@@ -108,7 +130,11 @@ _registry_fetch() {
|
|
|
108
130
|
# into `jq FILE` as a two-argument invocation
|
|
109
131
|
# (`jq \n /path/to/file`), producing a confusing
|
|
110
132
|
# "Could not open file" error. Silence stdout explicitly.
|
|
111
|
-
|
|
133
|
+
local curl_args=(-fsSL --max-time 15)
|
|
134
|
+
if [[ "$url" == https://* ]]; then
|
|
135
|
+
curl_args+=(--proto '=https' --tlsv1.2)
|
|
136
|
+
fi
|
|
137
|
+
if ! curl "${curl_args[@]}" -o "$tmp" "$url" >/dev/null; then
|
|
112
138
|
rm -f "$tmp"
|
|
113
139
|
if [[ -s "$cache_file" ]]; then
|
|
114
140
|
ui_warn "registry" "fetch failed; using stale cache at $cache_file"
|
|
@@ -119,6 +145,11 @@ _registry_fetch() {
|
|
|
119
145
|
return 1
|
|
120
146
|
fi
|
|
121
147
|
mv "$tmp" "$cache_file"
|
|
148
|
+
if ! _registry_validate_index "$cache_file"; then
|
|
149
|
+
rm -f "$cache_file"
|
|
150
|
+
ui_err "registry" "index failed schema and integrity validation"
|
|
151
|
+
return 1
|
|
152
|
+
fi
|
|
122
153
|
printf '%s\n' "$cache_file"
|
|
123
154
|
}
|
|
124
155
|
|
|
@@ -129,6 +160,101 @@ _registry_require_jq() {
|
|
|
129
160
|
}
|
|
130
161
|
}
|
|
131
162
|
|
|
163
|
+
_registry_archive_is_safe() {
|
|
164
|
+
local archive="$1"
|
|
165
|
+
local entry
|
|
166
|
+
while IFS= read -r entry; do
|
|
167
|
+
[[ -n "$entry" ]] || continue
|
|
168
|
+
if [[ "$entry" == /* || "$entry" == ../* || "$entry" == *"/../"* || "$entry" == *"/.." ]]; then
|
|
169
|
+
ui_err "registry" "archive contains unsafe path: $entry"
|
|
170
|
+
return 1
|
|
171
|
+
fi
|
|
172
|
+
done < <(tar -tzf "$archive")
|
|
173
|
+
if tar -tvzf "$archive" | awk 'substr($1,1,1) == "l" || substr($1,1,1) == "h" { found=1 } END { exit !found }'; then
|
|
174
|
+
ui_err "registry" "archive contains links; links are forbidden in registry modules"
|
|
175
|
+
return 1
|
|
176
|
+
fi
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_registry_install() (
|
|
180
|
+
local name="$1"
|
|
181
|
+
local apply="${2:-0}"
|
|
182
|
+
local index metadata version archive_url expected tmp archive extract module_root actual destination
|
|
183
|
+
|
|
184
|
+
[[ "$name" =~ ^[a-z0-9][a-z0-9-]{0,31}$ ]] || {
|
|
185
|
+
ui_err "install" "invalid module name: $name"
|
|
186
|
+
return 1
|
|
187
|
+
}
|
|
188
|
+
_registry_require_jq || return $?
|
|
189
|
+
index="$(_registry_fetch)" || return $?
|
|
190
|
+
_registry_validate_index "$index" || {
|
|
191
|
+
ui_err "registry" "index failed schema validation"
|
|
192
|
+
return 1
|
|
193
|
+
}
|
|
194
|
+
metadata="$(jq -c --arg name "$name" '.modules[] | select(.name == $name)' "$index")"
|
|
195
|
+
[[ -n "$metadata" ]] || {
|
|
196
|
+
ui_err "install" "module not found: $name"
|
|
197
|
+
return 1
|
|
198
|
+
}
|
|
199
|
+
version="$(jq -r '.version' <<<"$metadata")"
|
|
200
|
+
archive_url="$(jq -r '.archive_url' <<<"$metadata")"
|
|
201
|
+
expected="$(jq -r '.sha256' <<<"$metadata")"
|
|
202
|
+
|
|
203
|
+
tmp="$(mktemp -d -t dot-registry.XXXXXX)"
|
|
204
|
+
archive="$tmp/module.tar.gz"
|
|
205
|
+
extract="$tmp/source"
|
|
206
|
+
trap 'rm -rf "$tmp"' EXIT
|
|
207
|
+
mkdir -p "$extract"
|
|
208
|
+
local curl_args=(-fsSL --max-time 60)
|
|
209
|
+
if [[ "$archive_url" == https://* ]]; then
|
|
210
|
+
curl_args+=(--proto '=https' --tlsv1.2)
|
|
211
|
+
fi
|
|
212
|
+
if ! curl "${curl_args[@]}" -o "$archive" "$archive_url"; then
|
|
213
|
+
ui_err "install" "could not download $archive_url"
|
|
214
|
+
return 1
|
|
215
|
+
fi
|
|
216
|
+
local archive_size
|
|
217
|
+
archive_size="$(wc -c <"$archive" | tr -d '[:space:]')"
|
|
218
|
+
if ((archive_size > 52428800)); then
|
|
219
|
+
ui_err "install" "archive exceeds the 50 MiB safety limit"
|
|
220
|
+
return 1
|
|
221
|
+
fi
|
|
222
|
+
actual="$(_dot_sha256_file "$archive")" || return $?
|
|
223
|
+
[[ "$actual" == "$expected" ]] || {
|
|
224
|
+
ui_err "install" "SHA-256 mismatch for $name@$version"
|
|
225
|
+
return 1
|
|
226
|
+
}
|
|
227
|
+
_registry_archive_is_safe "$archive" || return $?
|
|
228
|
+
tar -xzf "$archive" -C "$extract" --no-same-owner --no-same-permissions
|
|
229
|
+
|
|
230
|
+
module_root="$extract"
|
|
231
|
+
local roots=()
|
|
232
|
+
while IFS= read -r entry; do roots+=("$entry"); done < <(find "$extract" -mindepth 1 -maxdepth 1 -print)
|
|
233
|
+
if [[ ${#roots[@]} -eq 1 && -d "${roots[0]}" ]]; then
|
|
234
|
+
module_root="${roots[0]}"
|
|
235
|
+
fi
|
|
236
|
+
[[ -n "$(find "$module_root" -mindepth 1 -print -quit)" ]] || {
|
|
237
|
+
ui_err "install" "module archive is empty"
|
|
238
|
+
return 1
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
ui_ok "Verified" "$name@$version ($actual)"
|
|
242
|
+
ui_section "Chezmoi preview"
|
|
243
|
+
chezmoi apply --source "$module_root" --destination "$HOME" --dry-run --no-tty
|
|
244
|
+
if [[ "$apply" != "1" ]]; then
|
|
245
|
+
ui_info "Preview only" "rerun with --yes to install and apply"
|
|
246
|
+
return 0
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
destination="$(_registry_data_dir)/$name/$version"
|
|
250
|
+
mkdir -p "$(dirname "$destination")"
|
|
251
|
+
rm -rf "$destination"
|
|
252
|
+
mv "$module_root" "$destination"
|
|
253
|
+
chezmoi apply --source "$destination" --destination "$HOME" --no-tty
|
|
254
|
+
printf '%s\n' "$metadata" >"$(dirname "$destination")/installed.json"
|
|
255
|
+
ui_ok "Installed" "$name@$version"
|
|
256
|
+
)
|
|
257
|
+
|
|
132
258
|
cmd_registry() {
|
|
133
259
|
local subcommand="${1:-list}"
|
|
134
260
|
shift || true
|
|
@@ -239,9 +365,31 @@ cmd_registry() {
|
|
|
239
365
|
ui_err "install" "missing module name"
|
|
240
366
|
return 1
|
|
241
367
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
368
|
+
shift || true
|
|
369
|
+
local apply=0
|
|
370
|
+
case "${1:-}" in
|
|
371
|
+
"") ;;
|
|
372
|
+
--yes | -y) apply=1 ;;
|
|
373
|
+
--dry-run | -n) apply=0 ;;
|
|
374
|
+
*)
|
|
375
|
+
ui_err "install" "unknown option: $1"
|
|
376
|
+
return 2
|
|
377
|
+
;;
|
|
378
|
+
esac
|
|
379
|
+
_registry_install "$name" "$apply"
|
|
380
|
+
;;
|
|
381
|
+
installed)
|
|
382
|
+
_registry_require_jq || return $?
|
|
383
|
+
local modules_dir
|
|
384
|
+
modules_dir="$(_registry_data_dir)"
|
|
385
|
+
if [[ ! -d "$modules_dir" ]]; then
|
|
386
|
+
ui_info "registry" "no modules installed"
|
|
387
|
+
return 0
|
|
388
|
+
fi
|
|
389
|
+
find "$modules_dir" -name installed.json -type f -exec jq -r '"\(.name)\t\(.version)\t\(.description)"' {} \; |
|
|
390
|
+
while IFS=$'\t' read -r module version description; do
|
|
391
|
+
ui_ok "$module" "v$version — $description"
|
|
392
|
+
done
|
|
245
393
|
;;
|
|
246
394
|
--help | -h | help)
|
|
247
395
|
cat <<EOF
|
|
@@ -251,7 +399,8 @@ Subcommands:
|
|
|
251
399
|
list List modules in the configured registry
|
|
252
400
|
search <q> Filter modules by keyword (name, description, tags)
|
|
253
401
|
info <name> Print metadata for a single module
|
|
254
|
-
install <name>
|
|
402
|
+
install <name> Verify and preview a module; pass --yes to apply
|
|
403
|
+
installed List locally installed modules
|
|
255
404
|
url Show the active registry URL
|
|
256
405
|
set-url <url> Override the registry URL (persists to user config)
|
|
257
406
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Dot — native PowerShell module for the dotfiles framework.
|
|
2
2
|
#
|
|
3
|
-
# This module provides native PowerShell cmdlets for the
|
|
4
|
-
#
|
|
5
|
-
#
|
|
3
|
+
# This module provides native PowerShell cmdlets for the daily `dot`
|
|
4
|
+
# workflow. Commands with Unix-specific orchestration remain available
|
|
5
|
+
# through the dispatcher's explicit bash bridge.
|
|
6
6
|
#
|
|
7
7
|
# Loaded by bin/dot.ps1 dispatcher; cmdlets are also callable
|
|
8
8
|
# directly:
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
# Invoke-DotHelp
|
|
12
12
|
# Test-DotAgentsSync -Verbose
|
|
13
13
|
#
|
|
14
|
-
# Tested via
|
|
14
|
+
# Tested via tools/ci/windows-smoke-test.ps1.
|
|
15
15
|
|
|
16
16
|
#requires -Version 7.0
|
|
17
17
|
|
|
@@ -66,6 +66,25 @@ function script:Write-DotBanner {
|
|
|
66
66
|
Write-Host "$esc[1;38;5;212mDot · $Section$esc[0m"
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
function script:Assert-DotCommand {
|
|
70
|
+
param([Parameter(Mandatory)][string]$Name)
|
|
71
|
+
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
|
|
72
|
+
throw "$Name is required but was not found on PATH"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function script:Invoke-DotNativeCommand {
|
|
77
|
+
param(
|
|
78
|
+
[Parameter(Mandatory)][string]$Name,
|
|
79
|
+
[Parameter(Mandatory)][string[]]$Arguments
|
|
80
|
+
)
|
|
81
|
+
Assert-DotCommand -Name $Name
|
|
82
|
+
& $Name @Arguments
|
|
83
|
+
if ($LASTEXITCODE -ne 0) {
|
|
84
|
+
throw "$Name exited with status $LASTEXITCODE"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
69
88
|
# Public cmdlets --------------------------------------------------------------
|
|
70
89
|
|
|
71
90
|
<#
|
|
@@ -110,9 +129,8 @@ function Get-DotVersion {
|
|
|
110
129
|
Print the dot CLI help overview.
|
|
111
130
|
|
|
112
131
|
.DESCRIPTION
|
|
113
|
-
Native PowerShell implementation of `dot help`.
|
|
114
|
-
|
|
115
|
-
NOT shell out to bash.
|
|
132
|
+
Native PowerShell implementation of `dot help`. Presents the
|
|
133
|
+
native command surface without shelling out to bash.
|
|
116
134
|
|
|
117
135
|
The full Linux-style help (every subcommand grouped by category)
|
|
118
136
|
requires the bash dispatcher; this cmdlet shows the Windows-
|
|
@@ -132,15 +150,19 @@ function Invoke-DotHelp {
|
|
|
132
150
|
Write-Host ''
|
|
133
151
|
Write-Host ' Get-DotVersion Print framework version'
|
|
134
152
|
Write-Host ' Invoke-DotHelp This screen'
|
|
135
|
-
Write-Host ' Test-DotAgentsSync Check AGENTS.md
|
|
153
|
+
Write-Host ' Test-DotAgentsSync Check AGENTS.md against CLAUDE.md'
|
|
154
|
+
Write-Host ' Get-DotStatus Show chezmoi drift'
|
|
155
|
+
Write-Host ' Invoke-DotChezmoi Run a core chezmoi operation'
|
|
156
|
+
Write-Host ' Invoke-DotDoctor Audit the native Windows setup'
|
|
157
|
+
Write-Host ' Get-DotEnvironment List mise-managed tools'
|
|
158
|
+
Write-Host ' Get-DotAgents List agent harness targets'
|
|
159
|
+
Write-Host ' Get-DotFleetStatus Show local fleet node status'
|
|
136
160
|
Write-Host ''
|
|
137
161
|
Write-Host 'Bash-bridged subcommands (require bash on PATH):'
|
|
138
162
|
Write-Host ''
|
|
139
|
-
Write-Host ' dot doctor Deep environment audit'
|
|
140
|
-
Write-Host ' dot env list List installed mise tools'
|
|
141
163
|
Write-Host ' dot env emit Emit v1 environment manifest'
|
|
142
164
|
Write-Host ' dot agents render Re-render every AGENTS.md harness'
|
|
143
|
-
Write-Host ' dot
|
|
165
|
+
Write-Host ' dot registry ... Verified module registry operations'
|
|
144
166
|
Write-Host ''
|
|
145
167
|
Write-Host 'See: dot help all (via bash) — full command index'
|
|
146
168
|
}
|
|
@@ -176,24 +198,122 @@ function Test-DotAgentsSync {
|
|
|
176
198
|
return $false
|
|
177
199
|
}
|
|
178
200
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
Write-Verbose "sync surface line not found — claude:[$claudeLine] agents:[$agentsLine]"
|
|
189
|
-
return $false
|
|
201
|
+
$claudeBody = Get-Content -Raw $claude
|
|
202
|
+
$agentsBody = Get-Content -Raw $agents
|
|
203
|
+
foreach ($name in @('claudeBody', 'agentsBody')) {
|
|
204
|
+
$value = Get-Variable -Name $name -ValueOnly
|
|
205
|
+
$value = $value -replace '(?s)^\s*<!--.*?-->\s*', ''
|
|
206
|
+
$value = $value -replace '(?m)^# (CLAUDE|AGENTS)\.md.*\r?\n', ''
|
|
207
|
+
$value = $value -replace '(?s)\r?\n---\r?\n\r?\n\*\*Need richer context\?.*$', ''
|
|
208
|
+
$value = ($value -replace "`r`n", "`n").Trim()
|
|
209
|
+
Set-Variable -Name $name -Value $value
|
|
190
210
|
}
|
|
191
|
-
if ($
|
|
192
|
-
Write-Verbose
|
|
211
|
+
if ($claudeBody -cne $agentsBody) {
|
|
212
|
+
Write-Verbose 'AGENTS.md content differs from the canonical CLAUDE.md body'
|
|
193
213
|
return $false
|
|
194
214
|
}
|
|
195
|
-
Write-Verbose 'AGENTS.md
|
|
215
|
+
Write-Verbose 'AGENTS.md content matches the canonical CLAUDE.md body'
|
|
196
216
|
return $true
|
|
197
217
|
}
|
|
198
218
|
|
|
199
|
-
|
|
219
|
+
function Invoke-DotChezmoi {
|
|
220
|
+
[CmdletBinding()]
|
|
221
|
+
param(
|
|
222
|
+
[Parameter(Mandatory)]
|
|
223
|
+
[ValidateSet('apply', 'diff', 'update', 'add', 'remove', 'init')]
|
|
224
|
+
[string]$Operation,
|
|
225
|
+
[Parameter(ValueFromRemainingArguments = $true)]
|
|
226
|
+
[string[]]$Arguments = @()
|
|
227
|
+
)
|
|
228
|
+
Invoke-DotNativeCommand -Name 'chezmoi' -Arguments (@($Operation) + $Arguments)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function Get-DotStatus {
|
|
232
|
+
[CmdletBinding()]
|
|
233
|
+
param([switch]$AsObject)
|
|
234
|
+
Assert-DotCommand -Name 'chezmoi'
|
|
235
|
+
$output = @(& chezmoi status 2>&1)
|
|
236
|
+
if ($LASTEXITCODE -ne 0) {
|
|
237
|
+
throw "chezmoi status exited with status $LASTEXITCODE`: $($output -join [Environment]::NewLine)"
|
|
238
|
+
}
|
|
239
|
+
$state = if ($output.Count -eq 0) { 'clean' } else { 'drifted' }
|
|
240
|
+
if ($AsObject) {
|
|
241
|
+
return [pscustomobject]@{ State = $state; Changes = $output; Native = $true }
|
|
242
|
+
}
|
|
243
|
+
Write-DotBanner -Section 'Status'
|
|
244
|
+
if ($state -eq 'clean') { Write-Host '[OK] Clean - no local drift detected' }
|
|
245
|
+
else { $output | Write-Output }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function Get-DotSourcePath {
|
|
249
|
+
[CmdletBinding()]
|
|
250
|
+
param()
|
|
251
|
+
Assert-DotCommand -Name 'chezmoi'
|
|
252
|
+
$path = (& chezmoi source-path 2>&1 | Out-String).Trim()
|
|
253
|
+
if ($LASTEXITCODE -ne 0 -or -not $path) { throw 'chezmoi source-path failed' }
|
|
254
|
+
return $path
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function Invoke-DotDoctor {
|
|
258
|
+
[CmdletBinding()]
|
|
259
|
+
param([switch]$AsObject)
|
|
260
|
+
$checks = @(
|
|
261
|
+
[pscustomobject]@{ Name = 'PowerShell 7.4+'; Ok = ($PSVersionTable.PSVersion -ge [version]'7.4') }
|
|
262
|
+
[pscustomobject]@{ Name = 'chezmoi'; Ok = [bool](Get-Command chezmoi -ErrorAction SilentlyContinue) }
|
|
263
|
+
[pscustomobject]@{ Name = 'git'; Ok = [bool](Get-Command git -ErrorAction SilentlyContinue) }
|
|
264
|
+
[pscustomobject]@{ Name = 'repository data'; Ok = (Test-Path $script:DataFile) }
|
|
265
|
+
[pscustomobject]@{ Name = 'PowerShell module'; Ok = (Test-Path (Join-Path $PSScriptRoot 'Dot.psm1')) }
|
|
266
|
+
)
|
|
267
|
+
if ($AsObject) { return $checks }
|
|
268
|
+
Write-DotBanner -Section 'Doctor'
|
|
269
|
+
foreach ($check in $checks) {
|
|
270
|
+
$prefix = if ($check.Ok) { '[OK]' } else { '[FAIL]' }
|
|
271
|
+
Write-Host "$prefix $($check.Name)"
|
|
272
|
+
}
|
|
273
|
+
return -not ($checks.Ok -contains $false)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function Get-DotEnvironment {
|
|
277
|
+
[CmdletBinding()]
|
|
278
|
+
param([switch]$AsJson)
|
|
279
|
+
Assert-DotCommand -Name 'mise'
|
|
280
|
+
$arguments = if ($AsJson) { @('ls', '--json') } else { @('ls') }
|
|
281
|
+
Invoke-DotNativeCommand -Name 'mise' -Arguments $arguments
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function Get-DotAgents {
|
|
285
|
+
[CmdletBinding()]
|
|
286
|
+
param()
|
|
287
|
+
$targets = [ordered]@{
|
|
288
|
+
'agents-md' = 'AGENTS.md'; 'cursor' = '.cursor/rules/dotfiles.mdc'
|
|
289
|
+
'codex' = '.codex/config.toml'; 'windsurf' = '.windsurf/rules.md'
|
|
290
|
+
'zed' = '.zed/agent-config.toml'; 'roo' = '.roo/rules.md'
|
|
291
|
+
'cline' = '.clinerules'; 'aider' = '.aider.conf.yml'
|
|
292
|
+
'continue' = '.continuerc.json'; 'jules' = '.jules/system.md'
|
|
293
|
+
'agy' = '.agy/AGY.md'
|
|
294
|
+
}
|
|
295
|
+
foreach ($entry in $targets.GetEnumerator()) {
|
|
296
|
+
$path = Join-Path $script:RepoRoot $entry.Value
|
|
297
|
+
[pscustomobject]@{ Harness = $entry.Key; Path = $path; Rendered = (Test-Path $path) }
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function Get-DotFleetStatus {
|
|
302
|
+
[CmdletBinding()]
|
|
303
|
+
param([switch]$AsJson)
|
|
304
|
+
$status = Get-DotStatus -AsObject
|
|
305
|
+
$record = [pscustomobject]@{
|
|
306
|
+
NodeId = [Environment]::MachineName
|
|
307
|
+
Namespace = 'default'
|
|
308
|
+
Version = Get-DotfilesVersionFromData
|
|
309
|
+
OS = [System.Runtime.InteropServices.RuntimeInformation]::OSDescription
|
|
310
|
+
Architecture = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString()
|
|
311
|
+
Drift = $status.State
|
|
312
|
+
}
|
|
313
|
+
if ($AsJson) { return $record | ConvertTo-Json -Compress }
|
|
314
|
+
return $record
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
Export-ModuleMember -Function Get-DotVersion, Invoke-DotHelp, Test-DotAgentsSync, `
|
|
318
|
+
Invoke-DotChezmoi, Get-DotStatus, Get-DotSourcePath, Invoke-DotDoctor, `
|
|
319
|
+
Get-DotEnvironment, Get-DotAgents, Get-DotFleetStatus
|
|
@@ -7,32 +7,38 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
7
7
|
# shellcheck source=../../lib/dot/ui.sh
|
|
8
8
|
# shellcheck disable=SC1091
|
|
9
9
|
source "$SCRIPT_DIR/../../lib/dot/ui.sh"
|
|
10
|
+
# shellcheck source=../../lib/dot/verified-download.sh disable=SC1091
|
|
11
|
+
source "$SCRIPT_DIR/../../lib/dot/verified-download.sh"
|
|
10
12
|
|
|
11
13
|
ui_init
|
|
12
14
|
ui_header "Nerd Fonts"
|
|
13
15
|
|
|
14
16
|
DEFAULT_FONTS="JetBrainsMono FiraCode Iosevka"
|
|
15
17
|
FONT_LIST="${*:-$DEFAULT_FONTS}"
|
|
18
|
+
FONT_VERSION="v3.4.0"
|
|
19
|
+
FONT_BASE_URL="https://github.com/ryanoasis/nerd-fonts/releases/download/${FONT_VERSION}"
|
|
20
|
+
FONT_CHECKSUM_URL="${FONT_BASE_URL}/SHA-256.txt"
|
|
16
21
|
|
|
17
|
-
install_linux()
|
|
18
|
-
font_name="$1"
|
|
19
|
-
target_dir="$HOME/.local/share/fonts/${font_name}NerdFont"
|
|
22
|
+
install_linux() (
|
|
23
|
+
local font_name="$1"
|
|
24
|
+
local target_dir="$HOME/.local/share/fonts/${font_name}NerdFont"
|
|
20
25
|
mkdir -p "$target_dir"
|
|
26
|
+
local tmp_dir
|
|
21
27
|
tmp_dir="$(umask 077 && mktemp -d)"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
url="
|
|
28
|
+
trap 'rm -rf "$tmp_dir"' EXIT
|
|
29
|
+
local asset="${font_name}.zip"
|
|
30
|
+
local url="${FONT_BASE_URL}/${asset}"
|
|
25
31
|
ui_info "Downloading" "$font_name Nerd Font"
|
|
26
|
-
|
|
27
|
-
if ! unzip -o "$tmp_dir/$
|
|
28
|
-
ui_err "Unzip failed" "$
|
|
32
|
+
download_verified_asset "$url" "$FONT_CHECKSUM_URL" "$asset" "$tmp_dir/$asset" 104857600
|
|
33
|
+
if ! unzip -o "$tmp_dir/$asset" -d "$target_dir" >/dev/null; then
|
|
34
|
+
ui_err "Unzip failed" "$asset" >&2
|
|
29
35
|
return 1
|
|
30
36
|
fi
|
|
31
37
|
if command -v fc-cache >/dev/null; then
|
|
32
38
|
fc-cache -f "$target_dir"
|
|
33
39
|
fi
|
|
34
40
|
ui_ok "Installed" "$target_dir"
|
|
35
|
-
|
|
41
|
+
)
|
|
36
42
|
|
|
37
43
|
install_macos() {
|
|
38
44
|
font_name="$1"
|
|
@@ -141,6 +141,6 @@ if [[ $FAILED -eq 1 ]]; then
|
|
|
141
141
|
printf '%b\\n' " (Use --no-verify to bypass if absolutely necessary)"
|
|
142
142
|
exit 1
|
|
143
143
|
else
|
|
144
|
-
printf '%b\n' "${GREEN}${BOLD}✅ Audit passed.${NC} v0.2.
|
|
144
|
+
printf '%b\n' "${GREEN}${BOLD}✅ Audit passed.${NC} v0.2.513 standards maintained."
|
|
145
145
|
exit 0
|
|
146
146
|
fi
|