@sebastienrousseau/dotfiles 0.2.520 → 0.2.521
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 +18 -0
- package/LICENSE-APACHE +190 -0
- package/LICENSE-MIT +21 -0
- package/README.md +43 -37
- package/install.sh +76 -10
- package/package.json +7 -7
- package/tools/README.md +49 -0
- package/tools/ci/install-chezmoi-verified.sh +68 -0
- package/docs/.vitepress/reports/localization-readability-audit.md +0 -73
- package/docs/AI.md +0 -179
- package/docs/ARCHITECTURE.md +0 -117
- package/docs/CNAME +0 -1
- package/docs/CONFIG_STRATEGY.md +0 -124
- package/docs/COPYRIGHT +0 -7
- package/docs/ECOSYSTEM.md +0 -220
- package/docs/GOLD-STANDARD-AUDIT.md +0 -352
- package/docs/GOVERNANCE.md +0 -98
- package/docs/MAINTAINERS.md +0 -41
- package/docs/MINIMUM-TOOLCHAIN.md +0 -100
- package/docs/NAMING_CONVENTIONS.md +0 -102
- package/docs/OPENCODE.md +0 -127
- package/docs/README.md +0 -84
- package/docs/STRUCTURE.md +0 -102
- package/docs/adr/ADR-001-ci-cd-pipeline.md +0 -118
- package/docs/adr/ADR-002-shell-performance.md +0 -130
- package/docs/adr/ADR-003-security-first.md +0 -158
- package/docs/adr/ADR-004-cli-architecture.md +0 -171
- package/docs/adr/ADR-005-chezmoi-choice.md +0 -99
- package/docs/adr/ADR-006-shell-selection.md +0 -124
- package/docs/adr/ADR-007-multi-shell-parity.md +0 -62
- package/docs/adr/ADR-008-alias-system-architecture.md +0 -95
- package/docs/adr/ADR-009-wallpaper-driven-theming.md +0 -131
- package/docs/adr/ADR-010-starship-transient-prompt.md +0 -144
- package/docs/adr/ADR-011-nushell-tier3-keep.md +0 -144
- package/docs/adr/ADR-012-ai-fleet-local-proxy.md +0 -79
- package/docs/adr/README.md +0 -40
- package/docs/architecture/AI_COST_OPTIMIZATION.md +0 -144
- package/docs/architecture/ARCHITECTURE.md +0 -20
- package/docs/architecture/INTEROP.md +0 -44
- package/docs/architecture/REPO_LAYOUT.md +0 -241
- package/docs/architecture/WALKTHROUGH.md +0 -86
- package/docs/architecture/fleet-deployment.md +0 -77
- package/docs/archive/EUXIS_2026_REVIEW.md +0 -127
- package/docs/archive/LEGACY_ROADMAP.md +0 -6
- package/docs/archive/MILESTONE_v0.2.493.md +0 -47
- package/docs/archive/PLAN.md +0 -199
- package/docs/archive/REPO_AUDIT.md +0 -31
- package/docs/articles/.pages +0 -6
- package/docs/articles/2026-07-05-custom-mkdocs-material-dark-theme.md +0 -216
- package/docs/articles/2026-07-05-fish-startup-abbr.md +0 -153
- package/docs/articles/2026-07-05-master-to-main-rename-runbook.md +0 -128
- package/docs/articles/index.md +0 -36
- package/docs/guides/INSTALL.md +0 -144
- package/docs/guides/MACOS_ICLOUD_SYMLINKS.md +0 -121
- package/docs/guides/NEOVIM_IDE_GUIDE.md +0 -61
- package/docs/guides/THEMING.md +0 -230
- package/docs/guides/TROUBLESHOOTING.md +0 -176
- package/docs/guides/WSL2_NIX_TROUBLESHOOTING.md +0 -792
- package/docs/index.md +0 -132
- package/docs/interop/A2A.md +0 -39
- package/docs/interop/POWERSHELL.md +0 -102
- package/docs/manual/00-introduction.md +0 -89
- package/docs/manual/01-concepts/01-architecture.md +0 -138
- package/docs/manual/01-concepts/02-trust-model.md +0 -183
- package/docs/manual/01-concepts/03-theme-engine.md +0 -186
- package/docs/manual/01-concepts/04-fleet.md +0 -148
- package/docs/manual/01-concepts/05-self-healing.md +0 -204
- package/docs/manual/02-tutorials/01-first-install.md +0 -197
- package/docs/manual/02-tutorials/02-add-wallpaper.md +0 -216
- package/docs/manual/02-tutorials/03-create-profile.md +0 -244
- package/docs/manual/02-tutorials/04-encrypt-secret.md +0 -281
- package/docs/manual/02-tutorials/05-deploy-fleet.md +0 -283
- package/docs/manual/03-reference/01-dot-cli.md +0 -475
- package/docs/manual/03-reference/02-config-files.md +0 -265
- package/docs/manual/03-reference/03-environment.md +0 -124
- package/docs/manual/03-reference/04-templates.md +0 -190
- package/docs/manual/03-reference/05-feature-flags.md +0 -187
- package/docs/manual/04-cookbook/01-recipes.md +0 -285
- package/docs/manual/04-cookbook/02-troubleshooting.md +0 -351
- package/docs/manual/04-cookbook/03-faq.md +0 -175
- package/docs/manual/05-appendices/A-platform-matrix.md +0 -101
- package/docs/manual/05-appendices/B-security-checklist.md +0 -85
- package/docs/manual/05-appendices/C-glossary.md +0 -40
- package/docs/manual/05-appendices/D-bibliography.md +0 -58
- package/docs/manual/05-appendices/E-license.md +0 -38
- package/docs/manual/_toc.yml +0 -58
- package/docs/manual/command-index.md +0 -175
- package/docs/manual/concept-index.md +0 -170
- package/docs/manual/index.md +0 -66
- package/docs/migration/README.md +0 -81
- package/docs/migration/from-bare-git-repo.md +0 -156
- package/docs/migration/from-gnu-stow.md +0 -165
- package/docs/migration/from-plain-chezmoi.md +0 -148
- package/docs/migration/from-yadm.md +0 -187
- package/docs/operations/ARCHITECTURE_ROADMAP.md +0 -7
- package/docs/operations/ATTESTATION.md +0 -44
- package/docs/operations/CI_CADENCE.md +0 -107
- package/docs/operations/CI_COMPOSITES.md +0 -156
- package/docs/operations/COMPLETIONS.md +0 -123
- package/docs/operations/COVERAGE.md +0 -204
- package/docs/operations/DRIFT.md +0 -107
- package/docs/operations/HARD_AUDIT_2026.md +0 -631
- package/docs/operations/MAINTENANCE.md +0 -63
- package/docs/operations/MANIFEST.md +0 -127
- package/docs/operations/MIGRATION.md +0 -109
- package/docs/operations/OPERATIONS.md +0 -188
- package/docs/operations/PERFORMANCE.md +0 -133
- package/docs/operations/PERFORMANCE_BUDGETS.md +0 -196
- package/docs/operations/REGISTRY.md +0 -90
- package/docs/operations/RELEASE_PIPELINE.md +0 -128
- package/docs/operations/RELIABILITY.md +0 -122
- package/docs/operations/RFC_v0_2_503_reorganization.md +0 -280
- package/docs/operations/ROADMAP.md +0 -10
- package/docs/operations/ROADMAP_2026.md +0 -7
- package/docs/operations/ROADMAP_V0_2_503.md +0 -10
- package/docs/operations/TESTING.md +0 -216
- package/docs/operations/TRACEABILITY.md +0 -44
- package/docs/operations/TRUSTED_AGENT_WORKSTATION.md +0 -65
- package/docs/operations/VERSION_SYNC.md +0 -393
- package/docs/packaging.md +0 -222
- package/docs/reference/ALIASES.md +0 -131
- package/docs/reference/ALIASES_CHEATSHEET.md +0 -32
- package/docs/reference/ALIASES_DEPRECATIONS.md +0 -13
- package/docs/reference/FEATURE-MATRIX.md +0 -646
- package/docs/reference/FEATURES.md +0 -66
- package/docs/reference/FONTS.md +0 -112
- package/docs/reference/POWERSHELL_PARITY.md +0 -82
- package/docs/reference/PROFILES.md +0 -69
- package/docs/reference/SCREENSHOTS.md +0 -121
- package/docs/reference/SCRIPTS.md +0 -71
- package/docs/reference/SUPPORT_MATRIX.md +0 -80
- package/docs/reference/THEMES.md +0 -117
- package/docs/reference/TOOLS.md +0 -110
- package/docs/reference/UTILS.md +0 -243
- package/docs/registry.json +0 -6
- package/docs/schema/dot-env-v1.json +0 -110
- package/docs/schema/dot-registry-v1.json +0 -33
- package/docs/security/AI_ACT_COMPLIANCE.md +0 -94
- package/docs/security/AUDIT_BYPASS.md +0 -103
- package/docs/security/AUTOMATION_SECRETS.md +0 -26
- package/docs/security/CI_EGRESS_ALLOWLIST.md +0 -127
- package/docs/security/CI_PINNING.md +0 -129
- package/docs/security/COMMIT_SIGNING.md +0 -138
- package/docs/security/COMPLIANCE.md +0 -458
- package/docs/security/DEPS_DEV_EXCEPTIONS.md +0 -86
- package/docs/security/DISCLOSURE.md +0 -130
- package/docs/security/ENCRYPTION.md +0 -57
- package/docs/security/FMEA.md +0 -159
- package/docs/security/FUZZING.md +0 -209
- package/docs/security/HISTORY_FILTERING.md +0 -132
- package/docs/security/INCIDENT_RESPONSE.md +0 -579
- package/docs/security/INSTALL_VERIFICATION.md +0 -122
- package/docs/security/KEYS.md +0 -49
- package/docs/security/KEY_ROTATION.md +0 -303
- package/docs/security/MCP_POLICY.md +0 -78
- package/docs/security/POLICY_RELEASES.md +0 -37
- package/docs/security/README.md +0 -28
- package/docs/security/SCORECARD.md +0 -195
- package/docs/security/SECRETS.md +0 -158
- package/docs/security/SECURITY.md +0 -45
- package/docs/security/SECURITY_CHECKLIST.md +0 -55
- package/docs/security/SHELL_EXEMPTIONS.md +0 -145
- package/docs/security/SOUP_REGISTER.md +0 -36
- package/docs/security/THREAT_MODEL.md +0 -130
- package/docs/security/VERIFICATION_VALIDATION.md +0 -228
- package/docs/security/VERIFY_RELEASE.md +0 -201
- package/docs/security/security-pubkey.asc +0 -15
- package/docs/stylesheets/extra.css +0 -444
- package/docs/themes/README.md +0 -10
- package/docs/themes/VISUAL_INTEGRITY_REPORT.md +0 -30
- package/docs/themes/hero-shot.svg +0 -78
- package/scripts/README.md +0 -123
- package/scripts/ci/check-copyright-headers.sh +0 -8
- package/scripts/ci/check-shell-preamble.sh +0 -8
- package/scripts/ci/guard-gitleaks-checkout.sh +0 -8
- package/scripts/demo/record.sh +0 -43
- package/scripts/diagnostics/a2a-conformance.sh +0 -163
- package/scripts/diagnostics/alias-governance.sh +0 -165
- package/scripts/diagnostics/aliases-cheatsheet.sh +0 -74
- package/scripts/diagnostics/aliases-manifest.sh +0 -77
- package/scripts/diagnostics/attest-verify.sh +0 -147
- package/scripts/diagnostics/benchmark.sh +0 -408
- package/scripts/diagnostics/conflicts.sh +0 -73
- package/scripts/diagnostics/doctor-unified.sh +0 -43
- package/scripts/diagnostics/doctor.sh +0 -797
- package/scripts/diagnostics/drift-dashboard.sh +0 -203
- package/scripts/diagnostics/health.sh +0 -656
- package/scripts/diagnostics/history-analysis.sh +0 -86
- package/scripts/diagnostics/mcp-doctor.sh +0 -582
- package/scripts/diagnostics/perf.sh +0 -453
- package/scripts/diagnostics/scorecard.sh +0 -120
- package/scripts/diagnostics/secret-governance.sh +0 -65
- package/scripts/diagnostics/security-score.sh +0 -467
- package/scripts/diagnostics/smoke-test.sh +0 -88
- package/scripts/diagnostics/snapshot.sh +0 -90
- package/scripts/diagnostics/verify.sh +0 -108
- package/scripts/diagnostics/verify_state.sh +0 -73
- package/scripts/diagnostics/version-locks.sh +0 -94
- package/scripts/diagnostics/workstation-attestation.sh +0 -212
- package/scripts/dot/commands/agent.sh +0 -535
- package/scripts/dot/commands/agents.sh +0 -352
- package/scripts/dot/commands/ai.sh +0 -600
- package/scripts/dot/commands/aliases.sh +0 -277
- package/scripts/dot/commands/appearance.sh +0 -110
- package/scripts/dot/commands/completion.sh +0 -171
- package/scripts/dot/commands/core.sh +0 -217
- package/scripts/dot/commands/diagnostics.sh +0 -265
- package/scripts/dot/commands/env-emit.sh +0 -203
- package/scripts/dot/commands/fleet.sh +0 -711
- package/scripts/dot/commands/init.sh +0 -185
- package/scripts/dot/commands/lint.sh +0 -208
- package/scripts/dot/commands/manual.sh +0 -169
- package/scripts/dot/commands/meta.sh +0 -438
- package/scripts/dot/commands/patterns.sh +0 -55
- package/scripts/dot/commands/registry.sh +0 -455
- package/scripts/dot/commands/restore.sh +0 -232
- package/scripts/dot/commands/secrets.sh +0 -296
- package/scripts/dot/commands/security.sh +0 -102
- package/scripts/dot/commands/tools.sh +0 -570
- package/scripts/dot/data/alias-deprecations.tsv +0 -2
- package/scripts/dot/powershell/Dot.psm1 +0 -319
- package/scripts/fonts/install-nerd-fonts.sh +0 -75
- package/scripts/fonts/patch-fonts.sh +0 -36
- package/scripts/git-hooks/install.sh +0 -12
- package/scripts/git-hooks/pre-commit +0 -12
- package/scripts/git-hooks/pre-commit-audit.sh +0 -146
- package/scripts/git-hooks/pre-push +0 -105
- package/scripts/git-hooks/prepare-commit-msg +0 -29
- package/scripts/lib/secrets_provider.sh +0 -200
- package/scripts/nvim/headless-upgrade.lua +0 -81
- package/scripts/ops/ai-setup.sh +0 -71
- package/scripts/ops/bundle.sh +0 -104
- package/scripts/ops/chaos.sh +0 -50
- package/scripts/ops/chezmoi-apply.sh +0 -333
- package/scripts/ops/chezmoi-diff.sh +0 -16
- package/scripts/ops/chezmoi-remove.sh +0 -46
- package/scripts/ops/chezmoi-update.sh +0 -67
- package/scripts/ops/heal-chezmoi.sh +0 -87
- package/scripts/ops/heal-system.sh +0 -129
- package/scripts/ops/heal-tools.sh +0 -297
- package/scripts/ops/heal.sh +0 -223
- package/scripts/ops/post-apply-repair.sh +0 -107
- package/scripts/ops/prewarm.sh +0 -128
- package/scripts/ops/release.sh +0 -262
- package/scripts/ops/rollback.sh +0 -613
- package/scripts/ops/setup.sh +0 -138
- package/scripts/ops/teleport.sh +0 -34
- package/scripts/qa/check-feature-matrix.sh +0 -296
- package/scripts/qa/check-version-consistency.sh +0 -12
- package/scripts/qa/coverage-baseline.sh +0 -61
- package/scripts/qa/docs-coverage.sh +0 -118
- package/scripts/qa/examples-coverage.sh +0 -94
- package/scripts/qa/powershell-contract.ps1 +0 -95
- package/scripts/qa/reliability-audit.sh +0 -139
- package/scripts/qa/scorecard-snapshot.sh +0 -128
- package/scripts/qa/traceability-coverage.sh +0 -124
- package/scripts/qa/validate-examples.sh +0 -90
- package/scripts/qa/wsl-contract.sh +0 -12
- package/scripts/secrets/age-init.sh +0 -82
- package/scripts/secrets/create-secrets-file.sh +0 -46
- package/scripts/secrets/encrypt-ssh-key.sh +0 -44
- package/scripts/security/backup.sh +0 -58
- package/scripts/security/check-disclosure-key-expiry.sh +0 -111
- package/scripts/security/dns-doh.sh +0 -52
- package/scripts/security/encryption-check.sh +0 -55
- package/scripts/security/enforce-policies.sh +0 -552
- package/scripts/security/firewall.sh +0 -91
- package/scripts/security/lock-configs.sh +0 -67
- package/scripts/security/lock-screen.sh +0 -56
- package/scripts/security/manage-secrets.sh +0 -429
- package/scripts/security/ssh-cert.sh +0 -204
- package/scripts/security/telemetry-kill.sh +0 -51
- package/scripts/security/usb-safety.sh +0 -52
- package/scripts/theme/apply-gnome-theme.sh +0 -333
- package/scripts/theme/extract-heic-frames.sh +0 -115
- package/scripts/theme/extract-theme.py +0 -1020
- package/scripts/theme/install-boot-logo.sh +0 -63
- package/scripts/theme/install-catppuccin-themes.sh +0 -371
- package/scripts/theme/install-cursors.sh +0 -26
- package/scripts/theme/install-file-icons.sh +0 -27
- package/scripts/theme/install-grub-theme.sh +0 -62
- package/scripts/theme/install-lock-icon.sh +0 -31
- package/scripts/theme/merge-wallpaper.sh +0 -146
- package/scripts/theme/rebuild-themes.sh +0 -603
- package/scripts/theme/switch.sh +0 -476
- package/scripts/theme/wallpaper-rotate.sh +0 -137
- package/scripts/theme/wallpaper-sync.sh +0 -690
- package/scripts/tools/cmatrix.sh +0 -22
- package/scripts/tools/detect-collisions.py +0 -103
- package/scripts/tools/emoji-picker.sh +0 -49
- package/scripts/tools/figlet-banner.sh +0 -19
- package/scripts/tools/log-rotate.sh +0 -31
- package/scripts/tools/lolcat-wrap.sh +0 -20
- package/scripts/tools/pipes.sh +0 -49
- package/scripts/tuning/linux.sh +0 -186
- package/scripts/tuning/macos.sh +0 -56
- package/scripts/uninstall.sh +0 -86
- package/scripts/verify-release-versions +0 -156
- package/scripts/version-sync.sh +0 -714
- package/templates/chezmoi-data/geekom-a9.toml.example +0 -21
- package/templates/chezmoi-data/mac-m1.toml.example +0 -16
- package/templates/chezmoi-data/mac-t2-linux.toml.example +0 -21
- package/templates/chezmoi-data/surface-pro-7p.toml.example +0 -21
- package/templates/projects/go/.github/workflows/ci.yml +0 -31
- package/templates/projects/go/README.md +0 -7
- package/templates/projects/go/cmd/__PROJECT_NAME__/main.go +0 -8
- package/templates/projects/go/go.mod +0 -3
- package/templates/projects/go/go.sum +0 -0
- package/templates/projects/molecule/README.md +0 -7
- package/templates/projects/molecule/converge.yml +0 -7
- package/templates/projects/molecule/molecule.yml +0 -16
- package/templates/projects/node/.github/workflows/ci.yml +0 -30
- package/templates/projects/node/README.md +0 -7
- package/templates/projects/node/package-lock.json +0 -12
- package/templates/projects/node/package.json +0 -10
- package/templates/projects/node/src/index.js +0 -3
- package/templates/projects/packer/README.md +0 -15
- package/templates/projects/packer/main.pkr.hcl +0 -15
- package/templates/projects/python/.github/workflows/ci.yml +0 -34
- package/templates/projects/python/README.md +0 -7
- package/templates/projects/python/pyproject.toml +0 -25
- package/templates/projects/python/src/__PROJECT_NAME__/__init__.py +0 -2
- package/templates/projects/python/tests/test_basic.py +0 -3
|
@@ -1,797 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
3
|
-
# Copyright (c) 2015-2026 Sebastien Rousseau
|
|
4
|
-
## Dotfiles Doctor.
|
|
5
|
-
##
|
|
6
|
-
## Diagnoses dotfiles environment health by checking dependencies, paths,
|
|
7
|
-
## and configuration integrity. Reports errors, warnings, and suggests
|
|
8
|
-
## remediation steps.
|
|
9
|
-
##
|
|
10
|
-
## # Usage
|
|
11
|
-
## dot doctor
|
|
12
|
-
##
|
|
13
|
-
## # Exit Codes
|
|
14
|
-
## - 0: All checks passed (may have warnings)
|
|
15
|
-
## - 1: Critical errors detected
|
|
16
|
-
##
|
|
17
|
-
## # Idempotency
|
|
18
|
-
## Safe to run repeatedly. Read-only checks.
|
|
19
|
-
|
|
20
|
-
set -euo pipefail
|
|
21
|
-
|
|
22
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
23
|
-
# shellcheck source=../../lib/dot/ui.sh
|
|
24
|
-
# shellcheck disable=SC1091
|
|
25
|
-
source "$SCRIPT_DIR/../../lib/dot/ui.sh"
|
|
26
|
-
# shellcheck source=../../lib/dot/platform.sh
|
|
27
|
-
# shellcheck disable=SC1091
|
|
28
|
-
source "$SCRIPT_DIR/../../lib/dot/platform.sh"
|
|
29
|
-
# shellcheck source=../../lib/dot/log.sh
|
|
30
|
-
# shellcheck disable=SC1091
|
|
31
|
-
source "$SCRIPT_DIR/../../lib/dot/log.sh"
|
|
32
|
-
export DOT_COMMAND="doctor"
|
|
33
|
-
|
|
34
|
-
ui_init
|
|
35
|
-
|
|
36
|
-
# Extend PATH to include common non-standard install locations
|
|
37
|
-
export PATH="$HOME/.atuin/bin:$HOME/.local/bin:$PATH"
|
|
38
|
-
|
|
39
|
-
Errors=0
|
|
40
|
-
Warnings=0
|
|
41
|
-
AI_DEBUG=0
|
|
42
|
-
|
|
43
|
-
for arg in "$@"; do
|
|
44
|
-
case "$arg" in
|
|
45
|
-
--ai) AI_DEBUG=1 ;;
|
|
46
|
-
esac
|
|
47
|
-
done
|
|
48
|
-
|
|
49
|
-
# --- Output helpers (delegate to shared ui.sh) ---
|
|
50
|
-
_ok() { ui_ok "$1" "${2:-}"; }
|
|
51
|
-
_fail() {
|
|
52
|
-
ui_err "$1" "${2:-}"
|
|
53
|
-
Errors=$((Errors + 1))
|
|
54
|
-
}
|
|
55
|
-
_warn() {
|
|
56
|
-
ui_warn "$1" "${2:-}"
|
|
57
|
-
Warnings=$((Warnings + 1))
|
|
58
|
-
}
|
|
59
|
-
_section() {
|
|
60
|
-
echo ""
|
|
61
|
-
ui_section "$1"
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
pretty_path() {
|
|
65
|
-
local value="${1:-}"
|
|
66
|
-
if [[ -n "$value" ]]; then
|
|
67
|
-
printf '%s' "${value/#$HOME/\~}"
|
|
68
|
-
fi
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
tool_source() {
|
|
72
|
-
local path="${1:-}"
|
|
73
|
-
if [[ "$path" == "$HOME/.local/share/mise/"* ]]; then
|
|
74
|
-
echo "mise"
|
|
75
|
-
elif [[ "$path" == /usr/bin/* || "$path" == /bin/* || "$path" == /usr/sbin/* || "$path" == /sbin/* ]]; then
|
|
76
|
-
echo "system"
|
|
77
|
-
else
|
|
78
|
-
echo "custom"
|
|
79
|
-
fi
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
check_cmd() {
|
|
83
|
-
local cmd="$1"
|
|
84
|
-
if command -v "$cmd" &>/dev/null; then return 0; fi
|
|
85
|
-
if command -v mise &>/dev/null; then
|
|
86
|
-
if mise ls --installed 2>/dev/null | grep -qE "($cmd|aqua:.*$cmd)"; then
|
|
87
|
-
return 0
|
|
88
|
-
fi
|
|
89
|
-
fi
|
|
90
|
-
return 1
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
get_cmd_path() {
|
|
94
|
-
local cmd="$1"
|
|
95
|
-
if command -v "$cmd" &>/dev/null; then
|
|
96
|
-
command -v "$cmd"
|
|
97
|
-
elif command -v mise &>/dev/null; then
|
|
98
|
-
local bin_path
|
|
99
|
-
bin_path=$(mise bin-paths 2>/dev/null | grep -E "/$cmd(/|$)" | head -n 1)
|
|
100
|
-
if [ -n "$bin_path" ]; then
|
|
101
|
-
echo "$bin_path/$cmd"
|
|
102
|
-
else
|
|
103
|
-
echo "$cmd"
|
|
104
|
-
fi
|
|
105
|
-
else
|
|
106
|
-
echo "$cmd"
|
|
107
|
-
fi
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
# --- Header ---
|
|
111
|
-
ui_header "Dotfiles Doctor"
|
|
112
|
-
|
|
113
|
-
# --- Core Shells ---
|
|
114
|
-
_section "Core Shells"
|
|
115
|
-
for cmd in zsh fish starship; do
|
|
116
|
-
if check_cmd "$cmd"; then
|
|
117
|
-
cmd_path="$(get_cmd_path "$cmd")"
|
|
118
|
-
_ok "$cmd" "$(pretty_path "$cmd_path") ($(tool_source "$cmd_path"))"
|
|
119
|
-
elif [[ "$cmd" == "fish" ]]; then
|
|
120
|
-
_warn "$cmd" "optional"
|
|
121
|
-
else
|
|
122
|
-
_fail "$cmd" "missing"
|
|
123
|
-
fi
|
|
124
|
-
done
|
|
125
|
-
|
|
126
|
-
if check_cmd "nu"; then
|
|
127
|
-
cmd_path="$(get_cmd_path "nu")"
|
|
128
|
-
_ok "nu" "$(pretty_path "$cmd_path") ($(tool_source "$cmd_path"))"
|
|
129
|
-
elif check_cmd "nushell"; then
|
|
130
|
-
cmd_path="$(get_cmd_path "nushell")"
|
|
131
|
-
_ok "nu" "$(pretty_path "$cmd_path") ($(tool_source "$cmd_path"))"
|
|
132
|
-
else
|
|
133
|
-
_warn "nu" "optional"
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
|
-
# --- Modern CLI Tools ---
|
|
137
|
-
_section "Modern CLI Tools"
|
|
138
|
-
for cmd in rg bat chezmoi fzf zoxide atuin yazi zellij; do
|
|
139
|
-
if check_cmd "$cmd"; then
|
|
140
|
-
_ok "$cmd" "$(pretty_path "$(get_cmd_path "$cmd")")"
|
|
141
|
-
elif [[ "$cmd" == "bat" ]] && check_cmd "batcat"; then
|
|
142
|
-
_ok "$cmd" "$(pretty_path "$(get_cmd_path "batcat")") (batcat)"
|
|
143
|
-
else
|
|
144
|
-
_fail "$cmd" "missing"
|
|
145
|
-
fi
|
|
146
|
-
done
|
|
147
|
-
|
|
148
|
-
# --- Infrastructure ---
|
|
149
|
-
_section "Infrastructure"
|
|
150
|
-
for cmd in pueue wasmtime nix sops age hyperfine; do
|
|
151
|
-
if check_cmd "$cmd"; then
|
|
152
|
-
_ok "$cmd" "$(pretty_path "$(get_cmd_path "$cmd")")"
|
|
153
|
-
elif [[ "$cmd" == "nix" ]]; then
|
|
154
|
-
_ok "$cmd" "optional (not installed)"
|
|
155
|
-
else
|
|
156
|
-
_fail "$cmd" "missing"
|
|
157
|
-
fi
|
|
158
|
-
done
|
|
159
|
-
|
|
160
|
-
if check_cmd pueue; then
|
|
161
|
-
if "$(get_cmd_path pueue)" status >/dev/null 2>&1; then
|
|
162
|
-
_ok "pueue daemon" "running"
|
|
163
|
-
elif command -v pueued >/dev/null 2>&1 && pueued -d >/dev/null 2>&1 && "$(get_cmd_path pueue)" status >/dev/null 2>&1; then
|
|
164
|
-
_ok "pueue daemon" "started"
|
|
165
|
-
else
|
|
166
|
-
_warn "pueue daemon" "not running (pueued -d)"
|
|
167
|
-
fi
|
|
168
|
-
fi
|
|
169
|
-
|
|
170
|
-
# --- AI CLIs ---
|
|
171
|
-
_section "AI CLIs"
|
|
172
|
-
for cmd in codex claude copilot kimi agy sgpt ollama opencode aider kiro-cli; do
|
|
173
|
-
if check_cmd "$cmd"; then
|
|
174
|
-
_ok "$cmd" "$(pretty_path "$(get_cmd_path "$cmd")")"
|
|
175
|
-
else
|
|
176
|
-
ui_info "$cmd" "optional (not installed)"
|
|
177
|
-
fi
|
|
178
|
-
done
|
|
179
|
-
|
|
180
|
-
# --- Environment ---
|
|
181
|
-
_section "Environment"
|
|
182
|
-
if [[ -n "${XDG_CONFIG_HOME:-}" ]]; then
|
|
183
|
-
_ok "XDG_CONFIG_HOME" "$(pretty_path "$XDG_CONFIG_HOME")"
|
|
184
|
-
else
|
|
185
|
-
_warn "XDG_CONFIG_HOME" "defaulting to ~/.config"
|
|
186
|
-
fi
|
|
187
|
-
|
|
188
|
-
# Validate XDG paths are absolute
|
|
189
|
-
for var in XDG_CONFIG_HOME XDG_DATA_HOME XDG_CACHE_HOME XDG_STATE_HOME; do
|
|
190
|
-
val="${!var:-}"
|
|
191
|
-
if [[ -n "$val" ]] && [[ "$val" != /* ]]; then
|
|
192
|
-
_warn "$var" "not absolute: $val"
|
|
193
|
-
fi
|
|
194
|
-
done
|
|
195
|
-
|
|
196
|
-
if [[ -n "${PIPX_HOME:-}" ]]; then
|
|
197
|
-
_ok "PIPX_HOME" "$(pretty_path "$PIPX_HOME")"
|
|
198
|
-
else
|
|
199
|
-
_warn "PIPX_HOME" "not set"
|
|
200
|
-
fi
|
|
201
|
-
|
|
202
|
-
# --- Platform ---
|
|
203
|
-
_section "Platform"
|
|
204
|
-
|
|
205
|
-
platform_id="$(dot_platform_id)"
|
|
206
|
-
_os_name="$(uname -s)"
|
|
207
|
-
_kernel="$(uname -sr)"
|
|
208
|
-
_arch="$(uname -m)"
|
|
209
|
-
_user="$(whoami 2>/dev/null || echo "${USER:-unknown}")"
|
|
210
|
-
_hostname_val="$(hostname -s 2>/dev/null || hostname 2>/dev/null || echo "unknown")"
|
|
211
|
-
|
|
212
|
-
# Shell version
|
|
213
|
-
_shell_name="${SHELL##*/}"
|
|
214
|
-
_shell_ver="$("$SHELL" --version 2>&1 | head -1 | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1 || true)"
|
|
215
|
-
_shell="${_shell_name}${_shell_ver:+ $_shell_ver}"
|
|
216
|
-
|
|
217
|
-
# Terminal
|
|
218
|
-
_terminal="${TERM_PROGRAM:-${TERM:-unknown}}"
|
|
219
|
-
|
|
220
|
-
# Uptime (portable)
|
|
221
|
-
if uptime -p >/dev/null 2>&1; then
|
|
222
|
-
_uptime="$(uptime -p 2>/dev/null | sed 's/^up //')"
|
|
223
|
-
else
|
|
224
|
-
_uptime="$(uptime 2>/dev/null | sed -E 's/^.* up ([^,]+(, [^,]+){0,2}), [0-9]+ users?.*$/\1/' || true)"
|
|
225
|
-
fi
|
|
226
|
-
|
|
227
|
-
# --- OS-specific detection ---
|
|
228
|
-
_os="" _host="" _cpu="" _cpu_cores="" _gpu="" _mem="" _resolution="" _packages="" _de=""
|
|
229
|
-
os_release_file="${DOT_DOCTOR_OS_RELEASE:-/etc/os-release}"
|
|
230
|
-
proc_root="${DOT_DOCTOR_PROC_ROOT:-/proc}"
|
|
231
|
-
sys_root="${DOT_DOCTOR_SYS_ROOT:-/sys}"
|
|
232
|
-
|
|
233
|
-
if [[ "$_os_name" == "Darwin" ]]; then
|
|
234
|
-
# macOS
|
|
235
|
-
_os="macOS $(sw_vers -productVersion 2>/dev/null || echo "unknown")"
|
|
236
|
-
_host="$(/usr/sbin/system_profiler SPHardwareDataType 2>/dev/null | awk -F': ' '/Model Name/{print $2}' || sysctl -n hw.model 2>/dev/null || echo "Mac")"
|
|
237
|
-
_cpu="$(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo "$_arch")"
|
|
238
|
-
_cpu_cores="$(sysctl -n hw.ncpu 2>/dev/null || echo "?")"
|
|
239
|
-
_gpu="$(system_profiler SPDisplaysDataType 2>/dev/null | awk -F': ' '/Chipset Model|Chip/{print $2; exit}' || echo "n/a")"
|
|
240
|
-
_mem_total="$(sysctl -n hw.memsize 2>/dev/null || echo 0)"
|
|
241
|
-
_mem_total_gb="$(awk "BEGIN{printf \"%.2f\", ${_mem_total}/1073741824}")"
|
|
242
|
-
_mem_pages="$(vm_stat 2>/dev/null | awk '/Pages active/{gsub(/\./,"",$3); print $3}')"
|
|
243
|
-
_mem_used_gb="$(awk "BEGIN{printf \"%.2f\", ${_mem_pages:-0}*4096/1073741824}")"
|
|
244
|
-
_mem="${_mem_used_gb} GiB / ${_mem_total_gb} GiB"
|
|
245
|
-
_resolution="$(system_profiler SPDisplaysDataType 2>/dev/null | awk '/Resolution/{gsub(/^ +/,""); print; exit}' | sed 's/Resolution: //' || echo "n/a")"
|
|
246
|
-
_packages="$(brew list --formula 2>/dev/null | wc -l | tr -d ' ') (brew)"
|
|
247
|
-
_de="Aqua"
|
|
248
|
-
|
|
249
|
-
elif [[ -r "$os_release_file" ]]; then
|
|
250
|
-
# Linux (Debian, Ubuntu, Arch, Fedora, RHEL, etc.)
|
|
251
|
-
# shellcheck disable=SC1091
|
|
252
|
-
. "$os_release_file"
|
|
253
|
-
_os="${PRETTY_NAME:-$ID}"
|
|
254
|
-
|
|
255
|
-
_host="$(cat "$sys_root/devices/virtual/dmi/id/product_name" 2>/dev/null || cat "$sys_root/firmware/devicetree/base/model" 2>/dev/null || echo "Linux")"
|
|
256
|
-
|
|
257
|
-
if command -v lscpu >/dev/null 2>&1; then
|
|
258
|
-
_cpu="$(lscpu | awk -F': +' '/Model name/{print $2}')"
|
|
259
|
-
_cpu_cores="$(lscpu | awk -F': +' '/^CPU\(s\):/{print $2}')"
|
|
260
|
-
else
|
|
261
|
-
_cpu="$(grep -m1 'model name' "$proc_root/cpuinfo" 2>/dev/null | cut -d: -f2 | sed 's/^ //' || uname -p)"
|
|
262
|
-
_cpu_cores="$(grep -c '^processor' "$proc_root/cpuinfo" 2>/dev/null || echo "?")"
|
|
263
|
-
fi
|
|
264
|
-
|
|
265
|
-
if command -v lspci >/dev/null 2>&1; then
|
|
266
|
-
# Headless hosts (VMs, containers, CI runners) expose no display
|
|
267
|
-
# controller, so `grep` exits 1 and — under `set -euo pipefail` —
|
|
268
|
-
# would abort the whole report mid-section. Absorb the miss.
|
|
269
|
-
_gpu="$(lspci 2>/dev/null | grep -iE 'vga|3d|display' | head -1 | sed 's/.*: //' || true)"
|
|
270
|
-
_gpu="${_gpu:-n/a}"
|
|
271
|
-
else
|
|
272
|
-
_gpu="n/a"
|
|
273
|
-
fi
|
|
274
|
-
|
|
275
|
-
if command -v free >/dev/null 2>&1; then
|
|
276
|
-
_mem="$(free -b | awk '/Mem:/{printf "%.2f GiB / %.2f GiB", $3/1073741824, $2/1073741824}')"
|
|
277
|
-
elif [[ -r "$proc_root/meminfo" ]]; then
|
|
278
|
-
_mem_total_kb="$(awk '/MemTotal/{print $2}' "$proc_root/meminfo")"
|
|
279
|
-
_mem_avail_kb="$(awk '/MemAvailable/{print $2}' "$proc_root/meminfo")"
|
|
280
|
-
_mem_used_kb=$((_mem_total_kb - _mem_avail_kb))
|
|
281
|
-
_mem="$(awk "BEGIN{printf \"%.2f GiB / %.2f GiB\", ${_mem_used_kb}/1048576, ${_mem_total_kb}/1048576}")"
|
|
282
|
-
else
|
|
283
|
-
_mem="n/a"
|
|
284
|
-
fi
|
|
285
|
-
|
|
286
|
-
# Resolution (Wayland or X11)
|
|
287
|
-
if command -v wlr-randr >/dev/null 2>&1; then
|
|
288
|
-
_resolution="$(wlr-randr 2>/dev/null | awk '/current/{print $1; exit}' || echo "n/a")"
|
|
289
|
-
elif command -v xrandr >/dev/null 2>&1; then
|
|
290
|
-
_resolution="$(xrandr 2>/dev/null | awk '/\*/{print $1; exit}' || echo "n/a")"
|
|
291
|
-
elif command -v xdpyinfo >/dev/null 2>&1; then
|
|
292
|
-
_resolution="$(xdpyinfo 2>/dev/null | awk '/dimensions/{print $2}' || echo "n/a")"
|
|
293
|
-
else
|
|
294
|
-
_resolution="n/a"
|
|
295
|
-
fi
|
|
296
|
-
|
|
297
|
-
# Packages (multi-distro)
|
|
298
|
-
_pkg_count=0
|
|
299
|
-
_pkg_mgr="pkg"
|
|
300
|
-
if command -v dpkg >/dev/null 2>&1; then
|
|
301
|
-
_pkg_count="$(dpkg --get-selections 2>/dev/null | wc -l | tr -d ' ')"
|
|
302
|
-
_pkg_mgr="dpkg"
|
|
303
|
-
elif command -v rpm >/dev/null 2>&1; then
|
|
304
|
-
_pkg_count="$(rpm -qa 2>/dev/null | wc -l | tr -d ' ')"
|
|
305
|
-
_pkg_mgr="rpm"
|
|
306
|
-
elif command -v pacman >/dev/null 2>&1; then
|
|
307
|
-
_pkg_count="$(pacman -Q 2>/dev/null | wc -l | tr -d ' ')"
|
|
308
|
-
_pkg_mgr="pacman"
|
|
309
|
-
fi
|
|
310
|
-
_packages="${_pkg_count} (${_pkg_mgr})"
|
|
311
|
-
|
|
312
|
-
_de="${XDG_CURRENT_DESKTOP:-${DESKTOP_SESSION:-n/a}}"
|
|
313
|
-
|
|
314
|
-
else
|
|
315
|
-
# Fallback (unknown OS)
|
|
316
|
-
_os="$(uname -sr)"
|
|
317
|
-
_host="unknown"
|
|
318
|
-
_cpu="$(uname -p 2>/dev/null || echo "unknown")"
|
|
319
|
-
_cpu_cores="?"
|
|
320
|
-
_gpu="n/a"
|
|
321
|
-
_mem="n/a"
|
|
322
|
-
_resolution="n/a"
|
|
323
|
-
_packages="n/a"
|
|
324
|
-
_de="n/a"
|
|
325
|
-
fi
|
|
326
|
-
|
|
327
|
-
# WSL detection and overrides
|
|
328
|
-
_wsl=""
|
|
329
|
-
if [[ -f "$proc_root/version" ]] && grep -qi microsoft "$proc_root/version" 2>/dev/null; then
|
|
330
|
-
_wsl="yes"
|
|
331
|
-
_os="${_os} (WSL)"
|
|
332
|
-
_de="Windows Desktop (WSL)"
|
|
333
|
-
_terminal="${TERM_PROGRAM:-Windows Terminal}"
|
|
334
|
-
fi
|
|
335
|
-
|
|
336
|
-
# Print platform info
|
|
337
|
-
_C='\033[0;36m'
|
|
338
|
-
_W='\033[1;37m'
|
|
339
|
-
_N='\033[0m'
|
|
340
|
-
_D='\033[2m'
|
|
341
|
-
|
|
342
|
-
printf '\n'
|
|
343
|
-
printf ' %b%s%b@%b%s%b\n' "$_C" "$_user" "$_N" "$_C" "$_hostname_val" "$_N"
|
|
344
|
-
printf ' %b%s%b\n' "$_D" "$(printf '%*s' "$((${#_user} + 1 + ${#_hostname_val}))" '' | tr ' ' '-')" "$_N"
|
|
345
|
-
printf ' %bOS:%b %s\n' "$_W" "$_N" "$_os"
|
|
346
|
-
printf ' %bHost:%b %s\n' "$_W" "$_N" "$_host"
|
|
347
|
-
printf ' %bKernel:%b %s\n' "$_W" "$_N" "$_kernel"
|
|
348
|
-
printf ' %bUptime:%b %s\n' "$_W" "$_N" "${_uptime:-n/a}"
|
|
349
|
-
printf ' %bPackages:%b %s\n' "$_W" "$_N" "$_packages"
|
|
350
|
-
printf ' %bShell:%b %s\n' "$_W" "$_N" "$_shell"
|
|
351
|
-
printf ' %bResolution:%b %s\n' "$_W" "$_N" "$_resolution"
|
|
352
|
-
printf ' %bDE:%b %s\n' "$_W" "$_N" "$_de"
|
|
353
|
-
printf ' %bTerminal:%b %s\n' "$_W" "$_N" "$_terminal"
|
|
354
|
-
printf ' %bCPU:%b %s (%s)\n' "$_W" "$_N" "$_cpu" "$_cpu_cores"
|
|
355
|
-
printf ' %bGPU:%b %s\n' "$_W" "$_N" "${_gpu:-n/a}"
|
|
356
|
-
printf ' %bMemory:%b %s\n' "$_W" "$_N" "$_mem"
|
|
357
|
-
printf ' %bArch:%b %s\n' "$_W" "$_N" "$_arch"
|
|
358
|
-
|
|
359
|
-
if [[ -n "$_wsl" ]]; then
|
|
360
|
-
echo ""
|
|
361
|
-
if command -v wslpath >/dev/null 2>&1; then
|
|
362
|
-
_ok "WSL bridge" "wslpath available"
|
|
363
|
-
else
|
|
364
|
-
_warn "WSL bridge" "wslpath missing"
|
|
365
|
-
fi
|
|
366
|
-
if [[ "$PWD" == /mnt/* ]]; then
|
|
367
|
-
_warn "WSL filesystem" "/mnt causes IO latency"
|
|
368
|
-
else
|
|
369
|
-
_ok "WSL filesystem" "native"
|
|
370
|
-
fi
|
|
371
|
-
fi
|
|
372
|
-
|
|
373
|
-
# --- State ---
|
|
374
|
-
_section "State"
|
|
375
|
-
# --exclude=always: a run_before_/run_after_ script (the macOS iCloud hook)
|
|
376
|
-
# is "pending" on every apply by design, so plain `verify` exits 1 forever
|
|
377
|
-
# on a fully synchronised machine. Files, dirs, symlinks and run_once/
|
|
378
|
-
# run_onchange scripts still count.
|
|
379
|
-
if chezmoi verify --exclude=always &>/dev/null; then
|
|
380
|
-
_ok "chezmoi" "synchronized"
|
|
381
|
-
else
|
|
382
|
-
_fail "chezmoi" "drifted (run dot drift)"
|
|
383
|
-
fi
|
|
384
|
-
|
|
385
|
-
if [[ -f "$HOME/.zshrc" ]]; then
|
|
386
|
-
_ok ".zshrc" "present"
|
|
387
|
-
else
|
|
388
|
-
_fail ".zshrc" "missing"
|
|
389
|
-
fi
|
|
390
|
-
|
|
391
|
-
if command -v dot >/dev/null 2>&1; then
|
|
392
|
-
dot_path="$(command -v dot)"
|
|
393
|
-
if [[ "$dot_path" == "$HOME/.local/bin/dot" ]]; then
|
|
394
|
-
_ok "dot" "$(pretty_path "$dot_path")"
|
|
395
|
-
else
|
|
396
|
-
_warn "dot" "$(pretty_path "$dot_path") (expected ~/.local/bin/dot)"
|
|
397
|
-
fi
|
|
398
|
-
else
|
|
399
|
-
_fail "dot" "not found in PATH"
|
|
400
|
-
fi
|
|
401
|
-
|
|
402
|
-
# --- Pre-push audit bypass log (closes #871) ---
|
|
403
|
-
_section "Pre-Push Audit Bypass"
|
|
404
|
-
|
|
405
|
-
bypass_log="${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles/audit-bypass.log"
|
|
406
|
-
if [[ -s "$bypass_log" ]]; then
|
|
407
|
-
# Count entries in the last 7 days (timestamps are ISO-8601 UTC).
|
|
408
|
-
seven_days_ago=$(date -u -v-7d +%Y-%m-%dT00:00:00Z 2>/dev/null ||
|
|
409
|
-
date -u -d '7 days ago' +%Y-%m-%dT00:00:00Z 2>/dev/null ||
|
|
410
|
-
echo "1970-01-01T00:00:00Z")
|
|
411
|
-
recent=$(awk -v cutoff="$seven_days_ago" '$1 >= cutoff' "$bypass_log" | wc -l | tr -d ' ')
|
|
412
|
-
if ((recent > 0)); then
|
|
413
|
-
_warn "audit bypass" "$recent push(es) bypassed in last 7 days — see $(pretty_path "$bypass_log")"
|
|
414
|
-
else
|
|
415
|
-
_ok "audit bypass" "log exists; no recent entries (last 7d)"
|
|
416
|
-
fi
|
|
417
|
-
else
|
|
418
|
-
_ok "audit bypass" "clean — no pre-push audits bypassed"
|
|
419
|
-
fi
|
|
420
|
-
|
|
421
|
-
# --- Atuin history-filter (closes #872) ---
|
|
422
|
-
_section "Atuin History Filter"
|
|
423
|
-
|
|
424
|
-
atuin_cfg="$HOME/.config/atuin/config.toml"
|
|
425
|
-
if [[ ! -f "$atuin_cfg" ]]; then
|
|
426
|
-
_warn "atuin config" "not found at $(pretty_path "$atuin_cfg")"
|
|
427
|
-
else
|
|
428
|
-
if grep -Eq "^history_filter[[:space:]]*=" "$atuin_cfg"; then
|
|
429
|
-
pattern_count=$(awk '
|
|
430
|
-
/^history_filter[[:space:]]*=[[:space:]]*\[/ { inside = 1; next }
|
|
431
|
-
inside && /^\]/ { inside = 0 }
|
|
432
|
-
inside && /^[[:space:]]*"/ { count++ }
|
|
433
|
-
END { print count + 0 }
|
|
434
|
-
' "$atuin_cfg")
|
|
435
|
-
if ((pattern_count >= 10)); then
|
|
436
|
-
_ok "history_filter" "$pattern_count patterns (chezmoi-managed)"
|
|
437
|
-
else
|
|
438
|
-
_warn "history_filter" "$pattern_count patterns (expected ≥10 from secrets-patterns.toml)"
|
|
439
|
-
fi
|
|
440
|
-
else
|
|
441
|
-
_fail "history_filter" "no history_filter block in $(pretty_path "$atuin_cfg") — secrets may leak into shell history"
|
|
442
|
-
fi
|
|
443
|
-
fi
|
|
444
|
-
|
|
445
|
-
# --- Topgrade Integration ---
|
|
446
|
-
_section "Topgrade Integration"
|
|
447
|
-
|
|
448
|
-
if command -v antigravity >/dev/null 2>&1; then
|
|
449
|
-
ag_path="$(command -v antigravity)"
|
|
450
|
-
if [[ "$ag_path" == "$HOME/.local/bin/antigravity" ]]; then
|
|
451
|
-
_ok "antigravity wrapper" "$(pretty_path "$ag_path")"
|
|
452
|
-
else
|
|
453
|
-
_warn "antigravity wrapper" "$(pretty_path "$ag_path") (expected ~/.local/bin/antigravity)"
|
|
454
|
-
fi
|
|
455
|
-
else
|
|
456
|
-
_warn "antigravity" "optional"
|
|
457
|
-
fi
|
|
458
|
-
|
|
459
|
-
if [[ -f "$HOME/.config/fish/fish_plugins" ]]; then
|
|
460
|
-
if grep -qx "jorgebucaran/fisher" "$HOME/.config/fish/fish_plugins"; then
|
|
461
|
-
_ok "fish_plugins" "contains jorgebucaran/fisher"
|
|
462
|
-
else
|
|
463
|
-
_warn "fish_plugins" "present but missing jorgebucaran/fisher"
|
|
464
|
-
fi
|
|
465
|
-
else
|
|
466
|
-
_fail "fish_plugins" "missing (~/.config/fish/fish_plugins)"
|
|
467
|
-
fi
|
|
468
|
-
|
|
469
|
-
# Only meaningful when there is a cargo for topgrade to run it through; a
|
|
470
|
-
# machine without a Rust toolchain has nothing to update, so warning about
|
|
471
|
-
# the missing helper there is noise that never goes away.
|
|
472
|
-
if command -v cargo-install-update >/dev/null 2>&1; then
|
|
473
|
-
_ok "cargo-install-update" "$(pretty_path "$(command -v cargo-install-update)")"
|
|
474
|
-
elif command -v cargo >/dev/null 2>&1; then
|
|
475
|
-
_warn "cargo-install-update" "missing (install: cargo install cargo-update)"
|
|
476
|
-
else
|
|
477
|
-
_ok "cargo-install-update" "not needed (cargo not installed)"
|
|
478
|
-
fi
|
|
479
|
-
|
|
480
|
-
# --- Symlinks ---
|
|
481
|
-
broken_links=0
|
|
482
|
-
broken_list=""
|
|
483
|
-
for root in "$HOME/.config" "$HOME/.local/bin" "$HOME/.local/share" "$HOME/.ssh"; do
|
|
484
|
-
[[ -d "$root" ]] || continue
|
|
485
|
-
while IFS= read -r -d '' link; do
|
|
486
|
-
link_name="$(basename "$link")"
|
|
487
|
-
[[ "$link" == *"google-chrome-backup"* ]] && continue
|
|
488
|
-
[[ "$link_name" == SingletonLock || "$link_name" == SingletonCookie || "$link_name" == SingletonSocket ]] && continue
|
|
489
|
-
[[ -e "$link" ]] && continue
|
|
490
|
-
# A link into ~/Library/Caches dangling is macOS working as designed, not a
|
|
491
|
-
# health problem: the OS purges that directory whenever it wants the space,
|
|
492
|
-
# and the owning tool recreates its cache on next use. ~/.config/swiftpm/cache
|
|
493
|
-
# -> ~/Library/Caches/org.swift.swiftpm is the usual one; it was "fixed" by
|
|
494
|
-
# recreating the target earlier the same day and had broken again by evening,
|
|
495
|
-
# which is the tell that it is not fixable, only re-reported.
|
|
496
|
-
case "$(readlink "$link" 2>/dev/null)" in
|
|
497
|
-
"$HOME/Library/Caches/"*) continue ;;
|
|
498
|
-
esac
|
|
499
|
-
broken_links=$((broken_links + 1))
|
|
500
|
-
broken_list="${broken_list:+$broken_list, }$(pretty_path "$link")"
|
|
501
|
-
done < <(find "$root" -maxdepth 3 -type l -print0 2>/dev/null)
|
|
502
|
-
done
|
|
503
|
-
|
|
504
|
-
if [[ $broken_links -eq 0 ]]; then
|
|
505
|
-
_ok "symlinks" "none broken"
|
|
506
|
-
else
|
|
507
|
-
# Name them. "1 broken" with no path meant hunting for it by hand, twice.
|
|
508
|
-
_warn "symlinks" "$broken_links broken: $broken_list"
|
|
509
|
-
fi
|
|
510
|
-
|
|
511
|
-
# --- Portability ---
|
|
512
|
-
ghost_paths=0
|
|
513
|
-
if command -v chezmoi >/dev/null 2>&1; then
|
|
514
|
-
while IFS= read -r managed_path; do
|
|
515
|
-
[[ "$managed_path" == "$HOME/.config/"* ]] || continue
|
|
516
|
-
[[ -f "$managed_path" ]] || continue
|
|
517
|
-
grep -Iq . "$managed_path" 2>/dev/null || continue
|
|
518
|
-
|
|
519
|
-
match_count=$(
|
|
520
|
-
grep -nE '"/home/(linuxbrew)?[^$]|/Users/[^$]' "$managed_path" 2>/dev/null |
|
|
521
|
-
grep -v "linuxbrew" |
|
|
522
|
-
grep -v "/mozilla/firefox" |
|
|
523
|
-
grep -v "/google-chrome" |
|
|
524
|
-
grep -v "/chromium" |
|
|
525
|
-
grep -v "/chezmoi/chezmoi.toml" |
|
|
526
|
-
grep -v "/bun/" |
|
|
527
|
-
grep -v "/.bun/" |
|
|
528
|
-
grep -v "/noctalia/" |
|
|
529
|
-
grep -c -v -- "-backup/" ||
|
|
530
|
-
true
|
|
531
|
-
)
|
|
532
|
-
|
|
533
|
-
ghost_paths=$((ghost_paths + ${match_count:-0}))
|
|
534
|
-
done < <(chezmoi managed --path-style=absolute 2>/dev/null || true)
|
|
535
|
-
|
|
536
|
-
if [[ $ghost_paths -gt 0 ]]; then
|
|
537
|
-
_warn "portability" "$ghost_paths hardcoded paths in managed ~/.config files"
|
|
538
|
-
else
|
|
539
|
-
_ok "portability" "no hardcoded paths in managed ~/.config files"
|
|
540
|
-
fi
|
|
541
|
-
else
|
|
542
|
-
_warn "portability" "chezmoi not found (scan skipped)"
|
|
543
|
-
fi
|
|
544
|
-
|
|
545
|
-
# --- Performance ---
|
|
546
|
-
_section "Performance"
|
|
547
|
-
|
|
548
|
-
cache_base="${XDG_CACHE_HOME:-$HOME/.cache}"
|
|
549
|
-
|
|
550
|
-
# 1. Shell cache freshness for tools the project already wraps in _cached_eval.
|
|
551
|
-
# Stale caches force runtime regeneration on next shell start.
|
|
552
|
-
stale_caches=0
|
|
553
|
-
stale_tools=""
|
|
554
|
-
for tool in mise starship zoxide atuin fzf direnv; do
|
|
555
|
-
tool_bin="$(command -v "$tool" 2>/dev/null || true)"
|
|
556
|
-
[[ -n "$tool_bin" ]] || continue
|
|
557
|
-
for shell_dir in zsh bash fish; do
|
|
558
|
-
case "$shell_dir" in
|
|
559
|
-
fish) cache_file="$cache_base/$shell_dir/${tool}-init.fish" ;;
|
|
560
|
-
*) cache_file="$cache_base/$shell_dir/${tool}-init.$shell_dir" ;;
|
|
561
|
-
esac
|
|
562
|
-
if [[ ! -f "$cache_file" ]] || [[ "$tool_bin" -nt "$cache_file" ]]; then
|
|
563
|
-
stale_caches=$((stale_caches + 1))
|
|
564
|
-
case "$stale_tools" in
|
|
565
|
-
*"$tool"*) ;;
|
|
566
|
-
*) stale_tools="${stale_tools:+$stale_tools, }$tool" ;;
|
|
567
|
-
esac
|
|
568
|
-
break
|
|
569
|
-
fi
|
|
570
|
-
done
|
|
571
|
-
done
|
|
572
|
-
if [[ $stale_caches -eq 0 ]]; then
|
|
573
|
-
caches_fresh="fresh"
|
|
574
|
-
_ok "shell caches" "fresh"
|
|
575
|
-
else
|
|
576
|
-
caches_fresh="stale"
|
|
577
|
-
_warn "shell caches" "stale ($stale_tools) — run dot prewarm"
|
|
578
|
-
fi
|
|
579
|
-
|
|
580
|
-
# 2. Slow-init tools that are present but NOT wrapped in _cached_eval.
|
|
581
|
-
# Each of these runs uncached on every shell start; common offenders eat
|
|
582
|
-
# 100-500ms apiece on a populated dev machine.
|
|
583
|
-
#
|
|
584
|
-
# Only flag tools that actually emit shell init via `<tool> init <shell>`
|
|
585
|
-
# (or equivalent) and would benefit from caching that output. Plain CLIs
|
|
586
|
-
# like gh/cargo/pnpm/yarn don't have init eval; their completions are
|
|
587
|
-
# cached separately under $ZSH_COMPLETIONS_DIR.
|
|
588
|
-
unwrapped=""
|
|
589
|
-
for tool in nvm fnm pyenv rbenv jenv asdf sdkman conda kubectl helm thefuck broot mcfly direnv; do
|
|
590
|
-
command -v "$tool" >/dev/null 2>&1 || continue
|
|
591
|
-
# If the tool is installed but no shell config sources or evals its
|
|
592
|
-
# init (no `$tool env`, `$tool init`, `$tool.sh`, lazy-load stub), it
|
|
593
|
-
# isn't adding startup cost — skip the warning.
|
|
594
|
-
init_referenced=0
|
|
595
|
-
if grep -rIlqE "\\b${tool}([[:space:]]+(env|init|hook)|\\.sh|_lazy_load_${tool}|_dot_lazy[[:space:]]+${tool})" \
|
|
596
|
-
"$HOME/.config/zsh" "$HOME/.config/fish" "$HOME/.config/shell" 2>/dev/null; then
|
|
597
|
-
init_referenced=1
|
|
598
|
-
fi
|
|
599
|
-
((init_referenced == 0)) && continue
|
|
600
|
-
# Tools we lazy-load via shell stubs don't need init-eval cache files.
|
|
601
|
-
case "$tool" in
|
|
602
|
-
fnm | nvm | sdkman)
|
|
603
|
-
grep -rIlqE "_lazy_load_${tool}|_dot_lazy[[:space:]]+${tool}" \
|
|
604
|
-
"$HOME/.config/zsh" "$HOME/.config/fish" 2>/dev/null && continue
|
|
605
|
-
;;
|
|
606
|
-
esac
|
|
607
|
-
found=0
|
|
608
|
-
for shell_dir in zsh bash fish; do
|
|
609
|
-
case "$shell_dir" in
|
|
610
|
-
fish) [[ -f "$cache_base/$shell_dir/${tool}-init.fish" ]] && found=1 ;;
|
|
611
|
-
*) [[ -f "$cache_base/$shell_dir/${tool}-init.$shell_dir" ]] && found=1 ;;
|
|
612
|
-
esac
|
|
613
|
-
((found == 1)) && break
|
|
614
|
-
done
|
|
615
|
-
((found == 0)) && unwrapped="${unwrapped:+$unwrapped, }$tool"
|
|
616
|
-
done
|
|
617
|
-
if [[ -z "$unwrapped" ]]; then
|
|
618
|
-
_ok "uncached slow-init tools" "none detected"
|
|
619
|
-
else
|
|
620
|
-
_warn "uncached slow-init tools" "$unwrapped — consider wrapping in _cached_eval"
|
|
621
|
-
fi
|
|
622
|
-
|
|
623
|
-
# 3. Zsh completion dump health. compinit is usually the single biggest
|
|
624
|
-
# cost on a zsh startup; a stale or uncompiled .zcompdump compounds it.
|
|
625
|
-
if command -v zsh >/dev/null 2>&1; then
|
|
626
|
-
zcompdump="${HOME}/.zcompdump"
|
|
627
|
-
if [[ -f "$zcompdump" ]]; then
|
|
628
|
-
dump_mtime=$(stat -c %Y "$zcompdump" 2>/dev/null || stat -f %m "$zcompdump" 2>/dev/null || echo 0)
|
|
629
|
-
age_days=$((($(date +%s) - dump_mtime) / 86400))
|
|
630
|
-
if ((age_days > 7)); then
|
|
631
|
-
_warn ".zcompdump" "${age_days}d old — refresh: rm ~/.zcompdump* && zsh -ic exit"
|
|
632
|
-
else
|
|
633
|
-
_ok ".zcompdump" "fresh (${age_days}d)"
|
|
634
|
-
fi
|
|
635
|
-
if [[ ! -f "${zcompdump}.zwc" ]]; then
|
|
636
|
-
_warn ".zcompdump.zwc" "missing — completion init slower than necessary"
|
|
637
|
-
fi
|
|
638
|
-
fi
|
|
639
|
-
fi
|
|
640
|
-
|
|
641
|
-
# 4. PATH length. Each entry is searched on every command resolution.
|
|
642
|
-
# A mise-managed 2026 dev machine routinely adds 60-90 entries (one per
|
|
643
|
-
# installed tool version + per-shim path), so the warn/fail thresholds
|
|
644
|
-
# reflect that baseline rather than a lean default (~40).
|
|
645
|
-
#
|
|
646
|
-
# The OK ceiling was 75, which contradicted the 60-90 baseline stated right
|
|
647
|
-
# above it and warned on every healthy mise machine. Worse, the implied remedy
|
|
648
|
-
# was backwards: those per-tool entries are what let a command resolve to the
|
|
649
|
-
# real binary instead of falling through to a mise shim. Measured 2026-08-20,
|
|
650
|
-
# 20 invocations of ripgrep --version:
|
|
651
|
-
#
|
|
652
|
-
# via shim 2741ms (137ms per call)
|
|
653
|
-
# direct install dir 62ms (3.1ms per call)
|
|
654
|
-
#
|
|
655
|
-
# ~44x. "Pruning" those entries to satisfy a length check would trade
|
|
656
|
-
# microseconds of PATH scan for ~134ms on every single tool invocation. The
|
|
657
|
-
# ceiling now matches the documented baseline; >90 still warns, because past
|
|
658
|
-
# that the entries are worth auditing for tools you no longer use.
|
|
659
|
-
path_count=$(printf '%s' "${PATH:-}" | tr ':' '\n' | grep -c . || true)
|
|
660
|
-
if [[ "$path_count" -le 90 ]]; then
|
|
661
|
-
_ok "PATH length" "$path_count entries"
|
|
662
|
-
elif [[ "$path_count" -le 120 ]]; then
|
|
663
|
-
_warn "PATH length" "$path_count entries — consider pruning"
|
|
664
|
-
else
|
|
665
|
-
_fail "PATH length" "$path_count entries — likely slowing every command"
|
|
666
|
-
fi
|
|
667
|
-
|
|
668
|
-
# 5. Shell coverage. Surface installed shells that the project's caching
|
|
669
|
-
# infrastructure doesn't currently maintain caches for.
|
|
670
|
-
shells_unmanaged=""
|
|
671
|
-
for sh in nu pwsh; do
|
|
672
|
-
command -v "$sh" >/dev/null 2>&1 || continue
|
|
673
|
-
case "$sh" in
|
|
674
|
-
nu)
|
|
675
|
-
[[ -f "$HOME/.config/nushell/cached_eval.nu" ]] && continue
|
|
676
|
-
;;
|
|
677
|
-
pwsh)
|
|
678
|
-
pwsh -NoLogo -NoProfile -NonInteractive -Command 'exit 0' >/dev/null 2>&1 || continue
|
|
679
|
-
pwsh_profile="$HOME/.config/powershell/Microsoft.PowerShell_profile.ps1"
|
|
680
|
-
[[ -f "$pwsh_profile" ]] && grep -q 'Get-DotfilesCachedInit' "$pwsh_profile" && continue
|
|
681
|
-
;;
|
|
682
|
-
esac
|
|
683
|
-
shells_unmanaged="${shells_unmanaged:+$shells_unmanaged, }$sh"
|
|
684
|
-
done
|
|
685
|
-
if [[ -z "$shells_unmanaged" ]]; then
|
|
686
|
-
_ok "shell coverage" "all installed shells have _cached_eval support"
|
|
687
|
-
else
|
|
688
|
-
_warn "shell coverage" "$shells_unmanaged installed — no _cached_eval helper"
|
|
689
|
-
fi
|
|
690
|
-
|
|
691
|
-
# 6. Zsh hook count. Heavy precmd/preexec functions compound per-prompt.
|
|
692
|
-
# Probe an interactive zsh with a hard timeout so a broken zshrc doesn't
|
|
693
|
-
# stall doctor; skip cleanly if the probe fails.
|
|
694
|
-
if command -v zsh >/dev/null 2>&1 && command -v timeout >/dev/null 2>&1; then
|
|
695
|
-
hook_counts=$(timeout 5 zsh -i -c 'echo "$#precmd_functions $#preexec_functions"' 2>/dev/null || echo "")
|
|
696
|
-
if [[ -n "$hook_counts" ]]; then
|
|
697
|
-
read -r precmd_n preexec_n <<<"$hook_counts"
|
|
698
|
-
if [[ "${precmd_n:-0}" -le 5 && "${preexec_n:-0}" -le 5 ]]; then
|
|
699
|
-
_ok "zsh hooks" "precmd=$precmd_n preexec=$preexec_n"
|
|
700
|
-
else
|
|
701
|
-
_warn "zsh hooks" "precmd=$precmd_n preexec=$preexec_n — heavy per-prompt work"
|
|
702
|
-
fi
|
|
703
|
-
fi
|
|
704
|
-
fi
|
|
705
|
-
|
|
706
|
-
if command -v hyperfine >/dev/null 2>&1; then
|
|
707
|
-
if bash "$SCRIPT_DIR/../../benches/bench.sh" 2>/dev/null; then
|
|
708
|
-
_ok "startup latency" "within target thresholds"
|
|
709
|
-
else
|
|
710
|
-
# Only suggest prewarm when it could actually help. The caches were
|
|
711
|
-
# already reported fresh above in the common case, and telling someone to
|
|
712
|
-
# re-run a no-op sends them in a circle — as it did on 2026-08-20, where
|
|
713
|
-
# prewarm changed nothing because nothing was cold.
|
|
714
|
-
if [[ "${caches_fresh:-unknown}" == "fresh" ]]; then
|
|
715
|
-
_warn "startup latency" "threshold exceeded (caches already fresh — profile with 'dot benchmark')"
|
|
716
|
-
else
|
|
717
|
-
_warn "startup latency" "threshold exceeded (run dot prewarm)"
|
|
718
|
-
fi
|
|
719
|
-
fi
|
|
720
|
-
else
|
|
721
|
-
_warn "hyperfine" "missing (benchmark skipped)"
|
|
722
|
-
fi
|
|
723
|
-
|
|
724
|
-
# 7. Baseline check + top-3 slowest tools from EVALCACHE_TIMING.
|
|
725
|
-
# Closes part of #863. Reads the same baseline file `dot perf` writes,
|
|
726
|
-
# and the same eval-timings.jsonl _cached_eval populates. Skipped
|
|
727
|
-
# silently when either file is absent (first-run state).
|
|
728
|
-
baseline_file="$cache_base/dotfiles/perf-baseline.json"
|
|
729
|
-
if [[ -s "$baseline_file" ]] && command -v python3 >/dev/null 2>&1; then
|
|
730
|
-
baseline_age_days=$(python3 -c '
|
|
731
|
-
import json, sys, datetime
|
|
732
|
-
try:
|
|
733
|
-
d = json.load(open(sys.argv[1]))
|
|
734
|
-
rec = d.get("recorded_at", "")
|
|
735
|
-
if not rec: print(-1); sys.exit(0)
|
|
736
|
-
rec = rec.replace("Z", "+00:00")
|
|
737
|
-
age = (datetime.datetime.now(datetime.timezone.utc) - datetime.datetime.fromisoformat(rec)).days
|
|
738
|
-
print(age)
|
|
739
|
-
except Exception:
|
|
740
|
-
print(-1)
|
|
741
|
-
' "$baseline_file" 2>/dev/null)
|
|
742
|
-
if [[ "$baseline_age_days" -ge 0 ]]; then
|
|
743
|
-
_ok "perf baseline" "recorded ${baseline_age_days}d ago — run \`dot perf\` to compare"
|
|
744
|
-
fi
|
|
745
|
-
fi
|
|
746
|
-
|
|
747
|
-
timings_file="${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles/eval-timings.jsonl"
|
|
748
|
-
if [[ -s "$timings_file" ]] && command -v python3 >/dev/null 2>&1; then
|
|
749
|
-
top_tools=$(python3 -c '
|
|
750
|
-
import json, sys
|
|
751
|
-
from collections import defaultdict
|
|
752
|
-
samples = defaultdict(list)
|
|
753
|
-
try:
|
|
754
|
-
for line in open(sys.argv[1]):
|
|
755
|
-
try:
|
|
756
|
-
ev = json.loads(line)
|
|
757
|
-
ms = int(ev.get("ms", 0) or 0)
|
|
758
|
-
samples[ev.get("label", "?")].append(ms)
|
|
759
|
-
except Exception:
|
|
760
|
-
pass
|
|
761
|
-
rows = sorted(samples.items(), key=lambda kv: sum(kv[1]) // max(len(kv[1]), 1), reverse=True)[:3]
|
|
762
|
-
print(", ".join(f"{lbl}({sum(v)//max(len(v),1)}ms)" for lbl, v in rows))
|
|
763
|
-
except Exception:
|
|
764
|
-
pass
|
|
765
|
-
' "$timings_file" 2>/dev/null)
|
|
766
|
-
if [[ -n "$top_tools" ]]; then
|
|
767
|
-
_ok "perf top-tools" "$top_tools"
|
|
768
|
-
fi
|
|
769
|
-
fi
|
|
770
|
-
|
|
771
|
-
# --- Summary ---
|
|
772
|
-
dot_log info "doctor_complete" "errors=$Errors" "warnings=$Warnings"
|
|
773
|
-
dot_metric "doctor_errors" "$Errors" "count"
|
|
774
|
-
dot_metric "doctor_warnings" "$Warnings" "count"
|
|
775
|
-
echo ""
|
|
776
|
-
if [[ $Errors -eq 0 ]]; then
|
|
777
|
-
if [[ $Warnings -eq 0 ]]; then
|
|
778
|
-
ui_ok "Healthy" "All checks passed."
|
|
779
|
-
else
|
|
780
|
-
ui_ok "Healthy" "$Warnings warning(s)."
|
|
781
|
-
fi
|
|
782
|
-
else
|
|
783
|
-
ui_err "$Errors error(s)" "$Warnings warning(s). Run 'dot heal' to repair."
|
|
784
|
-
|
|
785
|
-
if [[ $AI_DEBUG -eq 1 ]]; then
|
|
786
|
-
_section "AI Problem Analysis"
|
|
787
|
-
doctor_report=$(~/.local/bin/dot doctor | grep -E "(✗|⚠)")
|
|
788
|
-
ai_prompt="The dotfiles diagnostic 'dot doctor' found the following issues:
|
|
789
|
-
---
|
|
790
|
-
$doctor_report
|
|
791
|
-
---
|
|
792
|
-
Suggest specific shell commands to fix these issues according to our architectural standards."
|
|
793
|
-
dot ai claude --style hardener "$ai_prompt"
|
|
794
|
-
fi
|
|
795
|
-
exit 1
|
|
796
|
-
fi
|
|
797
|
-
echo ""
|