configs-all 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/.claude/settings.local.json +3 -0
  2. package/CLAUDE.md +94 -0
  3. package/README.md +424 -0
  4. package/TERMINAL_SHORTCUTS.md +96 -0
  5. package/WINDOWS_COMPATIBILITY.md +85 -0
  6. package/WINDOWS_MCP_SETUP.md +133 -0
  7. package/apps/RectangleConfig.plist +0 -0
  8. package/apps/Synergy +84 -0
  9. package/apps/iStat Menus Settings.ismp7 +0 -0
  10. package/claude/CLAUDE.md +228 -0
  11. package/claude/commands/changelog.md +36 -0
  12. package/claude/commands/commit.md +29 -0
  13. package/claude/commands/context.md +112 -0
  14. package/claude/commands/dash.md +37 -0
  15. package/claude/commands/deploy-check.md +37 -0
  16. package/claude/commands/deps.md +26 -0
  17. package/claude/commands/duplo.md +56 -0
  18. package/claude/commands/explain.md +43 -0
  19. package/claude/commands/fix-and-test.md +46 -0
  20. package/claude/commands/game-debug.md +66 -0
  21. package/claude/commands/games.md +53 -0
  22. package/claude/commands/go.md +147 -0
  23. package/claude/commands/guard.md +102 -0
  24. package/claude/commands/handoff.md +66 -0
  25. package/claude/commands/incident.md +144 -0
  26. package/claude/commands/init.md +78 -0
  27. package/claude/commands/k8s-debug.md +31 -0
  28. package/claude/commands/lint.md +27 -0
  29. package/claude/commands/merge-all.md +115 -0
  30. package/claude/commands/merge.md +129 -0
  31. package/claude/commands/mikpc.md +54 -0
  32. package/claude/commands/morning.md +72 -0
  33. package/claude/commands/partymode.md +105 -0
  34. package/claude/commands/plans.md +88 -0
  35. package/claude/commands/pr.md +41 -0
  36. package/claude/commands/prep.md +132 -0
  37. package/claude/commands/push-sync.md +82 -0
  38. package/claude/commands/push.md +34 -0
  39. package/claude/commands/research.md +73 -0
  40. package/claude/commands/retro.md +95 -0
  41. package/claude/commands/review-pr.md +96 -0
  42. package/claude/commands/review.md +41 -0
  43. package/claude/commands/scaffold-agent.md +45 -0
  44. package/claude/commands/setup.md +92 -0
  45. package/claude/commands/ship-prod.md +97 -0
  46. package/claude/commands/ship.md +82 -0
  47. package/claude/commands/simplify.md +42 -0
  48. package/claude/commands/spike.md +110 -0
  49. package/claude/commands/status.md +37 -0
  50. package/claude/commands/sync.md +72 -0
  51. package/claude/commands/test.md +29 -0
  52. package/claude/commands/triage.md +72 -0
  53. package/claude/desktop/claude_desktop_config.json +9 -0
  54. package/claude/hooks.json +15 -0
  55. package/claude/mcp-servers.duplo.json +8 -0
  56. package/claude/mcp-servers.json +62 -0
  57. package/claude/scripts/psdebug.ps1 +7 -0
  58. package/claude/settings.json +38 -0
  59. package/claude/settings.local.json +4 -0
  60. package/claude/statusline-command.sh +94 -0
  61. package/claude/templates/CLAUDE-k8s-devops.md +58 -0
  62. package/claude/templates/CLAUDE-python-agent.md +47 -0
  63. package/claude/templates/CLAUDE-typescript-frontend.md +50 -0
  64. package/docker/ai-stack/docker-compose.yml +76 -0
  65. package/docker/ai-stack/searxng/limiter.toml +3 -0
  66. package/docker/ai-stack/searxng/settings.yml +39 -0
  67. package/docker/cli/config.json.template +15 -0
  68. package/docker/cli/daemon.json +9 -0
  69. package/docker/cli/features.json +3 -0
  70. package/docker/mcp/catalog.json +9 -0
  71. package/docker/mcp/catalogs/docker-mcp.yaml +15107 -0
  72. package/docker/mcp/config.yaml +0 -0
  73. package/docker/mcp/registry.yaml +37 -0
  74. package/docker/mcp/tools.yaml +0 -0
  75. package/docs/context/.gitkeep +0 -0
  76. package/docs/context/2026-03-02-configs.md +142 -0
  77. package/docs/handoff/.gitkeep +0 -0
  78. package/docs/incidents/.gitkeep +0 -0
  79. package/docs/plans/2026-02-28-autonomous-command-suite-design.md +250 -0
  80. package/docs/plans/2026-02-28-autonomous-command-suite.md +682 -0
  81. package/docs/plans/2026-03-01-ai-stack-split-architecture.md +72 -0
  82. package/docs/plans/2026-03-02-ai-stack-expansion.md +33 -0
  83. package/docs/plans/2026-03-02-merge-commands-design.md +58 -0
  84. package/docs/plans/2026-03-02-merge-commands.md +354 -0
  85. package/docs/research/.gitkeep +0 -0
  86. package/docs/research/2026-03-02-configs-repo-architecture.md +152 -0
  87. package/docs/retros/.gitkeep +0 -0
  88. package/docs/retros/2026-03-01-ai-stack-split-architecture.md +38 -0
  89. package/docs/spikes/.gitkeep +0 -0
  90. package/gh/config.yml +16 -0
  91. package/gh/hosts.yml +5 -0
  92. package/gh/main.json +103 -0
  93. package/ghostty/config +90 -0
  94. package/git/config/base.gitconfig +46 -0
  95. package/git/config/chiefmikey.gitconfig +11 -0
  96. package/git/config/personal.gitconfig +10 -0
  97. package/git/config/work.gitconfig +14 -0
  98. package/ide/cursor/extensions.txt +111 -0
  99. package/ide/cursor/keybindings.json +307 -0
  100. package/ide/cursor/mcp.json +92 -0
  101. package/ide/cursor/settings.json +544 -0
  102. package/ide/vscode/extensions.txt +120 -0
  103. package/ide/vscode/insiders/extensions.txt +119 -0
  104. package/ide/vscode/insiders/keybindings.json +294 -0
  105. package/ide/vscode/insiders/settings.json +518 -0
  106. package/ide/vscode/keybindings.json +294 -0
  107. package/ide/vscode/settings.json +526 -0
  108. package/ide/vscode/vscode/extensions.txt +43 -0
  109. package/iterm/Mikey Pro.json +951 -0
  110. package/iterm/com.googlecode.iterm2.plist +5549 -0
  111. package/iterm/font/MesloLGS NF Bold Italic.ttf +0 -0
  112. package/iterm/font/MesloLGS NF Bold.ttf +0 -0
  113. package/iterm/font/MesloLGS NF Italic.ttf +0 -0
  114. package/iterm/font/MesloLGS NF Regular.ttf +0 -0
  115. package/package.json +15 -0
  116. package/scripts/ai/deploy-ai-stack.sh +119 -0
  117. package/scripts/ai/fix-ai-proxy.service +12 -0
  118. package/scripts/ai/fix-ai-proxy.sh +25 -0
  119. package/scripts/brew/search/brew-search-results.sh +19 -0
  120. package/scripts/brew/search/brew-search.sh +34 -0
  121. package/scripts/brew/upgrade/brew-upgrade-autoupdate.sh +5 -0
  122. package/scripts/brew/upgrade/brew-upgrade-full-auto.sh +89 -0
  123. package/scripts/brew/upgrade/brew-upgrade-full.sh +159 -0
  124. package/scripts/docker/cleanup/docker-cleanup-manage.sh +163 -0
  125. package/scripts/docker/cleanup/docker-cleanup.cron +12 -0
  126. package/scripts/docker/cleanup/docker-cleanup.sh +280 -0
  127. package/scripts/docker/install/README.md +23 -0
  128. package/scripts/docker/install/docker-al2.sh +7 -0
  129. package/scripts/docker/install/docker-compose-al2.sh +15 -0
  130. package/scripts/gh/auth/auth.sh +12 -0
  131. package/scripts/gh/config/gh-config.sh +3 -0
  132. package/scripts/gh/gist/gh-gist-create.sh +29 -0
  133. package/scripts/gh/gist/gh-gist-delete.sh +1 -0
  134. package/scripts/gh/gist/gh-gist-edit.sh +8 -0
  135. package/scripts/gh/gpg-key/gh-gpg-key-add.sh +3 -0
  136. package/scripts/gh/install/install.sh +7 -0
  137. package/scripts/gh/label/gh-label-clone.sh +0 -0
  138. package/scripts/gh/label/gh-label-create.sh +0 -0
  139. package/scripts/gh/label/gh-label-delete.sh +0 -0
  140. package/scripts/gh/label/gh-label-edit.sh +0 -0
  141. package/scripts/gh/label/gh-label-list.sh +0 -0
  142. package/scripts/gh/secret/gh-secret-delete.sh +24 -0
  143. package/scripts/gh/secret/gh-secret-set.sh +70 -0
  144. package/scripts/gh/ssh-key/gh-ssh-key-add.sh +8 -0
  145. package/scripts/git/add/git-add.sh +3 -0
  146. package/scripts/git/auth/README.md +11 -0
  147. package/scripts/git/auth/https.sh +20 -0
  148. package/scripts/git/auth/ssh-mac.sh +41 -0
  149. package/scripts/git/branch-delete/git-branch-delete.sh +16 -0
  150. package/scripts/git/checkout/git-checkout-stash.sh +32 -0
  151. package/scripts/git/temp/git-temp-pull.sh +6 -0
  152. package/scripts/git/temp/git-temp-push.sh +5 -0
  153. package/scripts/install/fresh.zsh +34 -0
  154. package/scripts/install/full-install.zsh +193 -0
  155. package/scripts/linux/codedeploy/README.md +19 -0
  156. package/scripts/linux/codedeploy/linux-codedeploy-al2.sh +13 -0
  157. package/scripts/linux/codedeploy/linux-codedeploy-index.sh +16 -0
  158. package/scripts/linux/codedeploy/linux-codedeploy-ubuntu.sh +14 -0
  159. package/scripts/linux/coredns/README.md +17 -0
  160. package/scripts/linux/coredns/linux-coredns-al2.sh +29 -0
  161. package/scripts/linux/wifi/01-netconf.yaml +21 -0
  162. package/scripts/linux/wifi/wifi-ubuntu.sh +17 -0
  163. package/scripts/mac/dock-sort/mac-dock-sort.sh +87 -0
  164. package/scripts/mac/dropbox-ignore/mac-dropbox-ignore.sh +12 -0
  165. package/scripts/mac/sudo-askpass/mac-sudo-askpass.sh +50 -0
  166. package/scripts/mac/sudo-askpass/setup-sudo-password.sh +49 -0
  167. package/scripts/mac/upgrade/mac-upgrade.sh +21 -0
  168. package/scripts/mac/vpn/mac-vpn.sh +4 -0
  169. package/scripts/mcp/aws-mcp-wrapper.ps1 +97 -0
  170. package/scripts/mcp/aws-mcp-wrapper.sh +53 -0
  171. package/scripts/mcp/duplo-mcp-wrapper.sh +31 -0
  172. package/scripts/mcp/filesystem-mcp-wrapper.ps1 +43 -0
  173. package/scripts/mcp/filesystem-mcp-wrapper.sh +34 -0
  174. package/scripts/mcp/git-mcp-wrapper.ps1 +42 -0
  175. package/scripts/mcp/git-mcp-wrapper.sh +33 -0
  176. package/scripts/mcp/github-mcp-wrapper.ps1 +43 -0
  177. package/scripts/mcp/github-mcp-wrapper.sh +19 -0
  178. package/scripts/mcp/kubernetes-mcp-wrapper.ps1 +22 -0
  179. package/scripts/mcp/kubernetes-mcp-wrapper.sh +16 -0
  180. package/scripts/mcp/mcp-launcher.ps1 +56 -0
  181. package/scripts/mcp/mcp-launcher.sh +71 -0
  182. package/scripts/mcp/mongodb-mcp-wrapper.ps1 +26 -0
  183. package/scripts/mcp/mongodb-mcp-wrapper.sh +17 -0
  184. package/scripts/mcp/notion-mcp-wrapper.ps1 +23 -0
  185. package/scripts/mcp/notion-mcp-wrapper.sh +14 -0
  186. package/scripts/mcp/postgres-mcp-wrapper.ps1 +23 -0
  187. package/scripts/mcp/postgres-mcp-wrapper.sh +16 -0
  188. package/scripts/npm/ncu/npm-ncu.sh +24 -0
  189. package/scripts/npm/upgrade/npm-upgrade.sh +51 -0
  190. package/scripts/qmk/build_reviung41.sh +28 -0
  191. package/scripts/qmk/sync_unicorne.sh +44 -0
  192. package/scripts/sync/README.md +64 -0
  193. package/scripts/sync/config-common.zsh +882 -0
  194. package/scripts/sync/pull-configs.ps1 +33 -0
  195. package/scripts/sync/pull-configs.zsh +278 -0
  196. package/scripts/sync/push-configs.ps1 +91 -0
  197. package/scripts/sync/push-configs.zsh +384 -0
  198. package/shell/alias/alias-d.zsh +333 -0
  199. package/shell/alias/alias.zsh +36 -0
  200. package/shell/alias/categories/development.zsh +157 -0
  201. package/shell/alias/categories/environment.zsh +13 -0
  202. package/shell/alias/categories/git.zsh +40 -0
  203. package/shell/alias/categories/github-functions.zsh +459 -0
  204. package/shell/alias/categories/network.zsh +46 -0
  205. package/shell/alias/categories/path.zsh +46 -0
  206. package/shell/alias/categories/system.zsh +78 -0
  207. package/shell/alias/categories/wolfe-server.zsh +11 -0
  208. package/shell/powershell/Microsoft.PowerShell_profile.ps1 +208 -0
  209. package/shell/zsh/.p10k.zsh +1832 -0
  210. package/shell/zsh/.zshrc +87 -0
  211. package/shell/zsh/config/completion.zsh +31 -0
  212. package/shell/zsh/config/functions.zsh +31 -0
  213. package/shell/zsh/config/keybindings.zsh +13 -0
  214. package/shell/zsh/config/options.zsh +56 -0
  215. package/shell/zsh/config/plugins.zsh +83 -0
  216. package/shell/zsh/config/variables.zsh +191 -0
  217. package/shell/zsh/powerlevel10k.zsh-theme +83 -0
  218. package/shell/zsh/zsh-autosuggestions.zsh +871 -0
  219. package/ssh/config +46 -0
  220. package/ssh/config.duplo +21 -0
  221. package/ssh/config.mikpc +35 -0
  222. package/ssh/personal_signing.pub +1 -0
  223. package/templates/.envrc.example +34 -0
  224. package/templates/.envrc.quickstart +17 -0
  225. package/wezterm/wezterm.lua +249 -0
  226. package/wsl/.wslconfig +3 -0
  227. package/wsl/wsl.conf +18 -0
@@ -0,0 +1,882 @@
1
+ #!/bin/zsh
2
+
3
+ # Common configuration management functions
4
+ # Used by both push-configs.zsh and pull-configs.zsh
5
+
6
+ set -uo pipefail
7
+
8
+ # Detect platform and set HOME appropriately
9
+ if [[ "$OSTYPE" == "darwin"* ]]; then
10
+ HOME="${HOME:-$(cd ~ && pwd)}"
11
+ IS_MAC=true
12
+ IS_WINDOWS=false
13
+ elif [[ -d "/mnt/c" ]]; then
14
+ # WSL or Windows environment - HOME should already be set correctly
15
+ HOME="${HOME:-$(cd ~ && pwd)}"
16
+ IS_MAC=false
17
+ IS_WINDOWS=true
18
+ # Get Windows username from WSL
19
+ WINDOWS_USER="wolfe"
20
+ else
21
+ # Linux or other Unix - use standard HOME
22
+ HOME="${HOME:-$(cd ~ && pwd)}"
23
+ IS_MAC=false
24
+ IS_WINDOWS=false
25
+ fi
26
+
27
+ # Resolve repo root if CONFIG_DIR isn't provided by the environment
28
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
29
+
30
+ # Detect CONFIG_DIR dynamically based on hostname
31
+ # On Windows/PowerShell, hostname might return incorrect value, so try alternatives
32
+ if [[ "$IS_WINDOWS" == "true" ]]; then
33
+ # Try to detect based on current directory
34
+ if [[ "$PWD" == *"mikpc"* ]]; then
35
+ HOST="mikpc"
36
+ elif [[ "$PWD" == *"duplo"* ]]; then
37
+ HOST="duplo"
38
+ elif [[ "$PWD" == *"mikbook"* ]]; then
39
+ HOST="mikbook"
40
+ elif [[ "$PWD" == *"mikmac"* ]]; then
41
+ HOST="mikmac"
42
+ else
43
+ HOST="mikpc" # Default fallback for Windows
44
+ fi
45
+ else
46
+ HOST="$(hostname 2>/dev/null || echo 'unknown')"
47
+ fi
48
+ case "${HOST}" in
49
+ "mikmac")
50
+ CONFIG_DIR="${HOME}/Dropbox/dev/configs"
51
+ ;;
52
+ "mikbook")
53
+ CONFIG_DIR="${HOME}/Dropbox/mikbook/dev/configs"
54
+ ;;
55
+ "duplo")
56
+ CONFIG_DIR="${HOME}/Dropbox/duplo/dev/chiefmikey/configs"
57
+ ;;
58
+ "mikpc")
59
+ # Windows PC - handle both WSL and native paths
60
+ if [[ -n "${WSL_DISTRO_NAME:-}" ]] || [[ -f /proc/version ]] && grep -qi microsoft /proc/version 2>/dev/null; then
61
+ # WSL path - check if it exists, otherwise use SCRIPT_DIR fallback
62
+ if [[ -d "${HOME}/Dropbox/mikpc/dev/configs" ]]; then
63
+ CONFIG_DIR="${HOME}/Dropbox/mikpc/dev/configs"
64
+ elif [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}/Dropbox/mikpc/dev/configs" ]]; then
65
+ CONFIG_DIR="/mnt/c/Users/${WINDOWS_USER:-$USER}/Dropbox/mikpc/dev/configs"
66
+ else
67
+ # Fallback to relative path resolution
68
+ CONFIG_DIR="${CONFIG_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
69
+ fi
70
+ else
71
+ # Fallback to relative path resolution
72
+ CONFIG_DIR="${CONFIG_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
73
+ fi
74
+ ;;
75
+ *)
76
+ # Fallback to relative path resolution
77
+ CONFIG_DIR="${CONFIG_DIR:-$(cd "${SCRIPT_DIR}/.." && pwd)}"
78
+ ;;
79
+ esac
80
+
81
+ # Backup configuration
82
+ # On Windows/WSL, use Linux filesystem for backups (NTFS has permission issues)
83
+ if [[ "$IS_WINDOWS" == "true" ]]; then
84
+ BACKUP_DIR="${HOME}/.config/config-backups"
85
+ else
86
+ BACKUP_DIR="${CONFIG_DIR}/backups"
87
+ fi
88
+ MAX_BACKUPS=5 # Keep only 5 most recent backups per file
89
+
90
+ # Load variable definitions if available
91
+ if [ -f "${CONFIG_DIR}/shell/zsh/config/variables.zsh" ]; then
92
+ # Source the variables
93
+ source "${CONFIG_DIR}/shell/zsh/config/variables.zsh"
94
+ fi
95
+
96
+ # Color output functions
97
+ info() { echo -e "\033[0;34m[INFO]\033[0m $1"; }
98
+ success() { echo -e "\033[0;32m[SUCCESS]\033[0m $1"; }
99
+ warn() { echo -e "\033[0;33m[WARN]\033[0m $1"; }
100
+ error() { echo -e "\033[0;31m[ERROR]\033[0m $1"; }
101
+
102
+ # Ensure directory exists
103
+ ensure_dir() {
104
+ local dir="$1"
105
+ if [[ ! -d "$dir" ]]; then
106
+ mkdir -p "$dir"
107
+ info "Created directory: $dir"
108
+ fi
109
+ }
110
+
111
+ # Create backup directory structure for a file
112
+ create_backup_dir() {
113
+ local file_path="$1"
114
+ # Convert absolute path to relative path for backup structure
115
+ local relative_path="${file_path#"${HOME}"}"
116
+ local backup_subdir="${BACKUP_DIR}${relative_path}"
117
+ local backup_dir
118
+ backup_dir="$(dirname "$backup_subdir")"
119
+ ensure_dir "$backup_dir" >/dev/null
120
+ echo "$backup_dir"
121
+ }
122
+
123
+ # Clean up old backups for a specific file
124
+ cleanup_old_backups() {
125
+ local backup_dir="$1"
126
+ local file_basename="$2"
127
+
128
+ # Find all backup files for this specific file and sort by modification time (newest first)
129
+ local backup_files
130
+ backup_files="$(find "$backup_dir" -name "${file_basename}.backup.*" -type f -printf '%T@ %p\n' 2>/dev/null | sort -nr | cut -d' ' -f2- || true)"
131
+
132
+ if [[ -n "$backup_files" ]]; then
133
+ local count=0
134
+ while IFS= read -r backup_file; do
135
+ count=$((count + 1))
136
+ if [[ $count -gt $MAX_BACKUPS ]]; then
137
+ rm -f "$backup_file"
138
+ info "Removed old backup: $(basename "$backup_file")"
139
+ fi
140
+ done <<< "$backup_files"
141
+ fi
142
+ }
143
+
144
+ # Validation functions
145
+ validate_json() {
146
+ local file="$1"
147
+ if command -v jq >/dev/null 2>&1; then
148
+ if ! jq empty "$file" 2>/dev/null; then
149
+ error "Invalid JSON in $file"
150
+ return 1
151
+ fi
152
+ else
153
+ warn "jq not available, skipping JSON validation for $file"
154
+ fi
155
+ }
156
+
157
+ validate_xml() {
158
+ local file="$1"
159
+ if command -v xmllint >/dev/null 2>&1; then
160
+ if ! xmllint --noout "$file" 2>/dev/null; then
161
+ error "Invalid XML in $file"
162
+ return 1
163
+ fi
164
+ else
165
+ warn "xmllint not available, skipping XML validation for $file"
166
+ fi
167
+ }
168
+
169
+ validate_plist() {
170
+ local file="$1"
171
+ if command -v plutil >/dev/null 2>&1; then
172
+ if ! plutil -lint "$file" >/dev/null 2>&1; then
173
+ error "Invalid plist in $file"
174
+ return 1
175
+ fi
176
+ else
177
+ warn "plutil not available, skipping plist validation for $file"
178
+ fi
179
+ }
180
+
181
+ validate_yaml() {
182
+ local file="$1"
183
+ if command -v yq >/dev/null 2>&1; then
184
+ if ! yq '.' "$file" >/dev/null 2>&1; then
185
+ error "Invalid YAML in $file"
186
+ return 1
187
+ fi
188
+ else
189
+ warn "yq not available, skipping YAML validation for $file"
190
+ fi
191
+ }
192
+
193
+ validate_shell() {
194
+ local file="$1"
195
+ local shell_type="$2"
196
+ if [[ "$shell_type" == "zsh" ]]; then
197
+ if ! zsh -n "$file" 2>/dev/null; then
198
+ error "Invalid zsh syntax in $file"
199
+ return 1
200
+ fi
201
+ fi
202
+ }
203
+
204
+ # Safe copy with validation and backup
205
+ safe_copy() {
206
+ local src="$1"
207
+ local dest="$2"
208
+ local validate_func="$3"
209
+ local backup_suffix
210
+ backup_suffix=".backup.$(date +%Y%m%d_%H%M%S)"
211
+
212
+ if [[ ! -f "$src" ]]; then
213
+ warn "Source file not found: $src"
214
+ return 0 # Return success to allow script to continue processing other files
215
+ fi
216
+
217
+ # If destination is a dangling symlink, remove it so we can write the real file.
218
+ if [[ -L "$dest" && ! -e "$dest" ]]; then
219
+ rm -f "$dest"
220
+ warn "Removed dangling symlink: $dest"
221
+ fi
222
+
223
+ # Validate source file if validation function provided
224
+ if [[ -n "$validate_func" ]]; then
225
+ if ! "$validate_func" "$src"; then
226
+ error "Validation failed for $src, skipping copy"
227
+ return 0 # Return success to allow script to continue processing other files
228
+ fi
229
+ fi
230
+
231
+ # Create backup if destination exists
232
+ if [[ -f "$dest" ]]; then
233
+ local backup_dir
234
+ backup_dir="$(create_backup_dir "$dest")"
235
+ local file_basename
236
+ file_basename="$(basename "$dest")"
237
+ local backup_file="${backup_dir}/${file_basename}${backup_suffix}"
238
+
239
+ cp "$dest" "$backup_file" 2>/dev/null || warn "Could not backup: $dest"
240
+ info "Backed up existing file: $backup_file"
241
+
242
+ # Clean up old backups for this file
243
+ cleanup_old_backups "$backup_dir" "$file_basename"
244
+ fi
245
+
246
+ ensure_dir "$(dirname "$dest")"
247
+ if cp "$src" "$dest" 2>/dev/null; then
248
+ success "Copied: $src → $dest"
249
+ else
250
+ error "Failed to copy: $src → $dest (permission denied?)"
251
+ return 0 # Continue processing other files
252
+ fi
253
+ }
254
+
255
+ # Copy if source exists with validation
256
+ copy_if_exists() {
257
+ local src="$1"
258
+ local dest="$2"
259
+ local validate_func="$3"
260
+
261
+ if [[ -f "$src" ]]; then
262
+ # Validate source file if validation function provided
263
+ if [[ -n "$validate_func" ]]; then
264
+ if ! "$validate_func" "$src"; then
265
+ error "Validation failed for $src, skipping copy"
266
+ return 1
267
+ fi
268
+ fi
269
+
270
+ ensure_dir "$(dirname "$dest")"
271
+
272
+ # Special handling for Rectangle config (use defaults command)
273
+ if [[ "$src" == *"RectangleConfig.plist" ]]; then
274
+ # Use defaults command to load Rectangle preferences
275
+ if defaults import com.knollsoft.Rectangle "$src" 2>/dev/null; then
276
+ success "Imported Rectangle preferences: $src → Rectangle defaults"
277
+ else
278
+ error "Failed to import Rectangle preferences"
279
+ return 1
280
+ fi
281
+ else
282
+ if cp "$src" "$dest" 2>/dev/null; then
283
+ success "Copied: $src → $dest"
284
+ else
285
+ error "Failed to copy: $src → $dest (permission denied?)"
286
+ fi
287
+ fi
288
+ else
289
+ warn "Source file not found: $src"
290
+ fi
291
+ }
292
+
293
+ # Determine git config based on hostname
294
+ get_git_config() {
295
+ case "${HOST}" in
296
+ "mikbook") echo "personal.gitconfig" ;;
297
+ "duplo") echo "work.gitconfig" ;;
298
+ "mikpc") echo "personal.gitconfig" ;; # Windows PC - default to personal
299
+ *) echo "personal.gitconfig" ;; # Default fallback
300
+ esac
301
+ }
302
+
303
+ # Determine SSH config based on hostname (allows host-specific SSH settings)
304
+ get_ssh_config() {
305
+ case "${HOST}" in
306
+ "duplo") echo "config.duplo" ;;
307
+ "mikpc") echo "config.mikpc" ;;
308
+ *) echo "config" ;;
309
+ esac
310
+ }
311
+
312
+ # Configuration file mappings (using functions instead of associative arrays for compatibility)
313
+ get_config_dest() {
314
+ local config_file="$1"
315
+ case "$config_file" in
316
+ "shell/zsh/.zshrc") echo "${HOME}/.zshrc" ;;
317
+ "git/config/base.gitconfig") echo "${HOME}/.config/git/base.gitconfig" ;;
318
+ "git/config/chiefmikey.gitconfig") echo "${HOME}/.config/git/chiefmikey.gitconfig" ;;
319
+ "git/config/$(get_git_config)") echo "${HOME}/.gitconfig" ;;
320
+ "ide/vscode/settings.json")
321
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming" ]]; then
322
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming/Code/User/settings.json"
323
+ elif [[ "$IS_MAC" == "true" ]]; then
324
+ echo "${HOME}/Library/Application Support/Code/User/settings.json"
325
+ else
326
+ echo "${HOME}/.config/Code/User/settings.json"
327
+ fi
328
+ ;;
329
+ "ide/vscode/keybindings.json")
330
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming" ]]; then
331
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming/Code/User/keybindings.json"
332
+ elif [[ "$IS_MAC" == "true" ]]; then
333
+ echo "${HOME}/Library/Application Support/Code/User/keybindings.json"
334
+ else
335
+ echo "${HOME}/.config/Code/User/keybindings.json"
336
+ fi
337
+ ;;
338
+ "ide/vscode/insiders/settings.json")
339
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming" ]]; then
340
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming/Code - Insiders/User/settings.json"
341
+ elif [[ "$IS_MAC" == "true" ]]; then
342
+ echo "${HOME}/Library/Application Support/Code - Insiders/User/settings.json"
343
+ else
344
+ echo "${HOME}/.config/Code - Insiders/User/settings.json"
345
+ fi
346
+ ;;
347
+ "ide/vscode/insiders/keybindings.json")
348
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming" ]]; then
349
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming/Code - Insiders/User/keybindings.json"
350
+ elif [[ "$IS_MAC" == "true" ]]; then
351
+ echo "${HOME}/Library/Application Support/Code - Insiders/User/keybindings.json"
352
+ else
353
+ echo "${HOME}/.config/Code - Insiders/User/keybindings.json"
354
+ fi
355
+ ;;
356
+ "ide/cursor/settings.json")
357
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming" ]]; then
358
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming/Cursor/User/settings.json"
359
+ elif [[ "$IS_MAC" == "true" ]]; then
360
+ echo "${HOME}/Library/Application Support/Cursor/User/settings.json"
361
+ else
362
+ echo "${HOME}/.config/Cursor/User/settings.json"
363
+ fi
364
+ ;;
365
+ "ide/cursor/keybindings.json")
366
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming" ]]; then
367
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming/Cursor/User/keybindings.json"
368
+ elif [[ "$IS_MAC" == "true" ]]; then
369
+ echo "${HOME}/Library/Application Support/Cursor/User/keybindings.json"
370
+ else
371
+ echo "${HOME}/.config/Cursor/User/keybindings.json"
372
+ fi
373
+ ;;
374
+ "ide/cursor/mcp.json")
375
+ # Mac uses mcp.json directly, Windows transforms it
376
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming" ]]; then
377
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/.cursor/mcp.json"
378
+ else
379
+ echo "${HOME}/.cursor/mcp.json"
380
+ fi
381
+ ;;
382
+ "scripts/mcp/mcp-launcher.sh")
383
+ # Install launcher into a stable per-user location (Mac/Unix)
384
+ if [[ "$IS_WINDOWS" == "true" ]]; then
385
+ echo ""
386
+ else
387
+ echo "${HOME}/.local/bin/mcp-launcher.sh"
388
+ fi
389
+ ;;
390
+ "scripts/mcp/mcp-launcher.ps1")
391
+ # Install launcher into a stable per-user location (Windows filesystem via WSL)
392
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}" ]]; then
393
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/.local/bin/mcp-launcher.ps1"
394
+ else
395
+ echo ""
396
+ fi
397
+ ;;
398
+ "gh/config.yml") echo "${HOME}/.config/gh/config.yml" ;;
399
+ "gh/hosts.yml") echo "${HOME}/.config/gh/hosts.yml" ;;
400
+ "ssh/$(get_ssh_config)") echo "${HOME}/.ssh/config" ;;
401
+ "ssh/personal_signing.pub")
402
+ # Only deploy on non-duplo hosts
403
+ if [[ "${HOST}" != "duplo" ]]; then
404
+ echo "${HOME}/.ssh/personal_signing.pub"
405
+ else
406
+ echo ""
407
+ fi
408
+ ;;
409
+ "iterm/com.googlecode.iterm2.plist")
410
+ # Mac only
411
+ if [[ "$IS_MAC" == "true" ]]; then
412
+ echo "${HOME}/Library/Preferences/com.googlecode.iterm2.plist"
413
+ else
414
+ echo ""
415
+ fi
416
+ ;;
417
+ "wezterm/wezterm.lua")
418
+ # Cross-platform (primarily Windows/Linux, but works on Mac too)
419
+ echo "${HOME}/.config/wezterm/wezterm.lua"
420
+ ;;
421
+ "ghostty/config")
422
+ # Cross-platform (Mac/Linux — Ghostty not available on Windows yet)
423
+ if [[ "$IS_WINDOWS" == "true" ]]; then
424
+ echo ""
425
+ else
426
+ echo "${HOME}/.config/ghostty/config"
427
+ fi
428
+ ;;
429
+ "shell/powershell/Microsoft.PowerShell_profile.ps1")
430
+ # Windows only — PowerShell profile with debug helpers
431
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}" ]]; then
432
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1"
433
+ else
434
+ echo ""
435
+ fi
436
+ ;;
437
+ "ide/vscode/vscode/extensions.txt")
438
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming" ]]; then
439
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming/Code/User/extensions.txt"
440
+ elif [[ "$IS_MAC" == "true" ]]; then
441
+ echo "${HOME}/Library/Application Support/Code/User/extensions.txt"
442
+ else
443
+ echo "${HOME}/.config/Code/User/extensions.txt"
444
+ fi
445
+ ;;
446
+ "apps/RectangleConfig.plist")
447
+ # Mac only
448
+ if [[ "$IS_MAC" == "true" ]]; then
449
+ echo "${HOME}/Library/Preferences/com.knollsoft.Rectangle.plist"
450
+ else
451
+ echo ""
452
+ fi
453
+ ;;
454
+ "apps/iStat Menus Settings.ismp7")
455
+ # Mac only
456
+ if [[ "$IS_MAC" == "true" ]]; then
457
+ echo "${HOME}/Library/Preferences/com.bjango.istatmenus.plist"
458
+ else
459
+ echo ""
460
+ fi
461
+ ;;
462
+ "apps/Synergy")
463
+ # Can work on both, but path differs
464
+ if [[ "$IS_MAC" == "true" ]]; then
465
+ echo "${HOME}/Library/Preferences/Synergy/synergy.conf"
466
+ elif [[ "$IS_WINDOWS" == "true" ]]; then
467
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/AppData/Roaming/Synergy/synergy.conf"
468
+ else
469
+ echo "${HOME}/.synergy.conf"
470
+ fi
471
+ ;;
472
+ "docker/mcp/registry.yaml")
473
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}" ]]; then
474
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/.docker/mcp/registry.yaml"
475
+ else
476
+ echo "${HOME}/.docker/mcp/registry.yaml"
477
+ fi
478
+ ;;
479
+ "docker/mcp/catalog.json")
480
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}" ]]; then
481
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/.docker/mcp/catalog.json"
482
+ else
483
+ echo "${HOME}/.docker/mcp/catalog.json"
484
+ fi
485
+ ;;
486
+ "docker/mcp/config.yaml")
487
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}" ]]; then
488
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/.docker/mcp/config.yaml"
489
+ else
490
+ echo "${HOME}/.docker/mcp/config.yaml"
491
+ fi
492
+ ;;
493
+ "docker/mcp/tools.yaml")
494
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}" ]]; then
495
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/.docker/mcp/tools.yaml"
496
+ else
497
+ echo "${HOME}/.docker/mcp/tools.yaml"
498
+ fi
499
+ ;;
500
+ "docker/mcp/catalogs/docker-mcp.yaml")
501
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}" ]]; then
502
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/.docker/mcp/catalogs/docker-mcp.yaml"
503
+ else
504
+ echo "${HOME}/.docker/mcp/catalogs/docker-mcp.yaml"
505
+ fi
506
+ ;;
507
+ "docker/cli/daemon.json") echo "${HOME}/.docker/daemon.json" ;;
508
+ "docker/cli/features.json") echo "${HOME}/.docker/features.json" ;;
509
+ # Claude Code configs
510
+ "claude/settings.json") echo "${HOME}/.claude/settings.json" ;;
511
+ "claude/settings.local.json") echo "${HOME}/.claude/settings.local.json" ;;
512
+ "claude/hooks.json") echo "${HOME}/.claude/hooks.json" ;;
513
+ "claude/statusline-command.sh") echo "${HOME}/.claude/statusline-command.sh" ;;
514
+ "claude/CLAUDE.md") echo "${HOME}/CLAUDE.md" ;;
515
+ "claude/desktop/claude_desktop_config.json")
516
+ # Mac only - Claude Desktop app
517
+ if [[ "$IS_MAC" == "true" ]]; then
518
+ echo "${HOME}/Library/Application Support/Claude/claude_desktop_config.json"
519
+ else
520
+ echo ""
521
+ fi
522
+ ;;
523
+ # WSL configs (Windows only)
524
+ "wsl/.wslconfig")
525
+ if [[ "$IS_WINDOWS" == "true" ]] && [[ -d "/mnt/c/Users/${WINDOWS_USER:-$USER}" ]]; then
526
+ echo "/mnt/c/Users/${WINDOWS_USER:-$USER}/.wslconfig"
527
+ else
528
+ echo ""
529
+ fi
530
+ ;;
531
+ "wsl/wsl.conf")
532
+ if [[ "$IS_WINDOWS" == "true" ]]; then
533
+ echo "/etc/wsl.conf"
534
+ else
535
+ echo ""
536
+ fi
537
+ ;;
538
+ *) echo "" ;;
539
+ esac
540
+ }
541
+
542
+ get_validation_func() {
543
+ local config_file="$1"
544
+ case "$config_file" in
545
+ "shell/zsh/.zshrc") echo "validate_shell_zsh" ;;
546
+ "ide/vscode/settings.json") echo "validate_json" ;;
547
+ "ide/vscode/keybindings.json") echo "validate_json" ;;
548
+ "ide/vscode/insiders/settings.json") echo "validate_json" ;;
549
+ "ide/vscode/insiders/keybindings.json") echo "validate_json" ;;
550
+ "ide/cursor/settings.json") echo "validate_json" ;;
551
+ "ide/cursor/keybindings.json") echo "validate_json" ;;
552
+ "ide/cursor/mcp.json") echo "validate_json" ;;
553
+ "gh/config.yml") echo "validate_yaml" ;;
554
+ "gh/hosts.yml") echo "validate_yaml" ;;
555
+ "iterm/com.googlecode.iterm2.plist") echo "validate_plist" ;;
556
+ "wezterm/wezterm.lua") echo "" ;;
557
+ "ghostty/config") echo "" ;;
558
+ "shell/powershell/Microsoft.PowerShell_profile.ps1") echo "" ;;
559
+ "ide/vscode/vscode/extensions.txt") echo "" ;;
560
+ "apps/RectangleConfig.plist") echo "validate_plist" ;;
561
+ "apps/iStat Menus Settings.ismp7") echo "validate_plist" ;;
562
+ "apps/Synergy") echo "" ;;
563
+ "docker/mcp/registry.yaml") echo "validate_yaml" ;;
564
+ "docker/mcp/catalog.json") echo "validate_json" ;;
565
+ "docker/mcp/config.yaml") echo "validate_yaml" ;;
566
+ "docker/mcp/tools.yaml") echo "validate_yaml" ;;
567
+ "docker/mcp/catalogs/docker-mcp.yaml") echo "validate_yaml" ;;
568
+ "docker/cli/daemon.json") echo "validate_json" ;;
569
+ "docker/cli/features.json") echo "validate_json" ;;
570
+ "scripts/mcp/mcp-launcher.sh") echo "validate_shell_zsh" ;;
571
+ "scripts/mcp/mcp-launcher.ps1") echo "" ;;
572
+ # Claude Code configs
573
+ "claude/settings.json") echo "validate_json" ;;
574
+ "claude/settings.local.json") echo "validate_json" ;;
575
+ "claude/hooks.json") echo "validate_json" ;;
576
+ "claude/statusline-command.sh") echo "validate_shell_zsh" ;;
577
+ "claude/CLAUDE.md") echo "" ;;
578
+ "claude/desktop/claude_desktop_config.json") echo "validate_json" ;;
579
+ "wsl/.wslconfig") echo "" ;;
580
+ "wsl/wsl.conf") echo "" ;;
581
+ *) echo "" ;;
582
+ esac
583
+ }
584
+
585
+ # Wrapper functions for shell validation
586
+ validate_shell_zsh() { validate_shell "$1" "zsh"; }
587
+
588
+ # Transform MCP config from Mac to Windows format
589
+ transform_mcp_mac_to_windows() {
590
+ local input_file="$1"
591
+ local output_file="$2"
592
+ local windows_user="${WINDOWS_USER:-$USER}"
593
+ local windows_config_dir="C:\\\\Users\\\\${windows_user}\\\\Dropbox\\\\mikpc\\\\dev\\\\configs"
594
+ local windows_home="C:\\\\Users\\\\${windows_user}"
595
+ local windows_npx_cmd=""
596
+
597
+ # On Windows, Cursor spawns MCP servers directly (not via PowerShell),
598
+ # so relying on PATH/PATHEXT for `npx` can be flaky. Prefer an absolute npx.cmd.
599
+ if [[ -f "/mnt/c/Program Files/nodejs/npx.cmd" ]]; then
600
+ windows_npx_cmd="C:\\Program Files\\nodejs\\npx.cmd"
601
+ elif [[ -f "/mnt/c/Users/${windows_user}/AppData/Roaming/npm/npx.cmd" ]]; then
602
+ windows_npx_cmd="C:\\Users\\${windows_user}\\AppData\\Roaming\\npm\\npx.cmd"
603
+ fi
604
+
605
+ # Use jq to transform the JSON
606
+ if command -v jq >/dev/null 2>&1; then
607
+ jq --arg win_dir "$windows_config_dir" --arg win_user "$windows_user" --arg win_home "$windows_home" --arg win_npx "$windows_npx_cmd" '
608
+ .mcpServers |= with_entries(
609
+ .value |= (
610
+ # Transform filesystem and git paths
611
+ if .args then
612
+ .args = (.args | map(
613
+ if type == "string" then
614
+ if . == "/Users/mikl/Dropbox/dev" or (test("^\\$\\{userHome\\}/Dropbox/dev$")) then
615
+ "C:\\\\Users\\\\\($win_user)\\\\Dropbox\\\\mikpc\\\\dev"
616
+ elif . == "/Users/mikl/Dropbox/dev/configs" or (test("^\\$\\{userHome\\}/Dropbox/dev/configs$")) then
617
+ $win_dir
618
+ elif startswith("/Users/mikl/Dropbox/dev/configs/scripts/mcp/") then
619
+ $win_dir + "\\\\scripts\\\\mcp\\\\" + (. | split("/") | .[-1] | sub("\\.sh$"; ".ps1"))
620
+ elif test("^\\$\\{userHome\\}/Dropbox/dev/configs/scripts/mcp/") then
621
+ $win_dir + "\\\\scripts\\\\mcp\\\\" + (. | split("/") | .[-1] | sub("\\.sh$"; ".ps1"))
622
+ elif contains("${userHome}") then
623
+ gsub("\\$\\{userHome\\}"; $win_home) | gsub("/"; "\\\\")
624
+ else
625
+ .
626
+ end
627
+ else
628
+ .
629
+ end
630
+ ))
631
+ else
632
+ .
633
+ end |
634
+ # Transform wrapper script commands
635
+ if .command then
636
+ if (.command | endswith("mcp-launcher.sh")) then
637
+ # Launcher: preserve wrapper arg, translate launcher + wrapper to ps1
638
+ .command as $orig_cmd
639
+ | (.args // []) as $orig_args
640
+ | .command = "powershell"
641
+ | .args = (
642
+ [
643
+ "-ExecutionPolicy",
644
+ "Bypass",
645
+ "-File",
646
+ (
647
+ $orig_cmd
648
+ | sub("\\.sh$"; ".ps1")
649
+ | sub("/Users/mikl/Dropbox/dev/configs"; $win_dir)
650
+ | gsub("^\\$\\{userHome\\}/Dropbox/dev/configs"; $win_dir)
651
+ | gsub("\\$\\{userHome\\}"; $win_home)
652
+ | gsub("/"; "\\\\")
653
+ )
654
+ ]
655
+ + ($orig_args | map(
656
+ if type == "string" then
657
+ sub("\\.sh$"; ".ps1")
658
+ else
659
+ .
660
+ end
661
+ ))
662
+ )
663
+ elif .command | test("\\.sh$") then
664
+ .command as $orig_cmd |
665
+ .command = "powershell" |
666
+ .args = ["-ExecutionPolicy", "Bypass", "-File", ($orig_cmd | sub("\\.sh$"; ".ps1") | sub("/Users/mikl/Dropbox/dev/configs"; $win_dir) | gsub("/"; "\\\\"))]
667
+ elif .command | startswith("/Users/mikl/Dropbox/dev/configs/scripts/mcp/") then
668
+ .command as $orig_cmd |
669
+ .command = "powershell" |
670
+ .args = ["-ExecutionPolicy", "Bypass", "-File", ($orig_cmd | sub("/Users/mikl/Dropbox/dev/configs"; $win_dir) | gsub("/"; "\\\\") | sub("\\.sh$"; ".ps1"))]
671
+ elif ($win_npx != "" and .command == "npx") then
672
+ .command = $win_npx
673
+ else
674
+ .
675
+ end
676
+ else
677
+ .
678
+ end
679
+ )
680
+ )
681
+ ' "$input_file" > "$output_file"
682
+ else
683
+ # Fallback: simple sed-based transformation (less reliable)
684
+ warn "jq not available, using sed for MCP transformation (may be less accurate)"
685
+ sed -e "s|/Users/mikl/Dropbox/dev|C:\\\\\\\\Users\\\\\\\\${windows_user}\\\\\\\\Dropbox\\\\\\\\mikpc\\\\\\\\dev|g" \
686
+ -e "s|/Users/mikl/Dropbox/dev/configs|${windows_config_dir}|g" \
687
+ -e "s|\\.sh|.ps1|g" \
688
+ -e "s|\"command\": \"/[^\"]*mcp/[^\"]*\\.ps1\"|\"command\": \"powershell\", \"args\": [\"-ExecutionPolicy\", \"Bypass\", \"-File\", \"&\"|g" \
689
+ "$input_file" > "$output_file"
690
+ fi
691
+ }
692
+
693
+ # Transform Cursor settings from Mac to Windows format
694
+ transform_cursor_settings_mac_to_windows() {
695
+ local input_file="$1"
696
+ local output_file="$2"
697
+ local windows_user="${WINDOWS_USER:-$USER}"
698
+
699
+ # Best-practice approach:
700
+ # - Keep the repo settings as Mac-first source of truth.
701
+ # - On Windows, strip Mac-only keys/values to avoid breaking Cursor UI/theme.
702
+ #
703
+ # We intentionally avoid "translating" most paths, because Windows layouts and
704
+ # installed locations vary. Cursor/VS Code will usually pick correct defaults.
705
+ if command -v jq >/dev/null 2>&1; then
706
+ jq '
707
+ # Remove obviously Mac-only keys (keep theme/editor behavior intact)
708
+ del(
709
+ .["terminal.integrated.env.osx"],
710
+ .["terminal.external.osxExec"],
711
+ .["terminal.integrated.defaultProfile.osx"],
712
+ .["git.path"],
713
+ .["sonarlint.pathToNodeExecutable"]
714
+ )
715
+ |
716
+ # Drop Parallels-specific settings (Mac-only)
717
+ with_entries(select(.key | test("^parallels-desktop\\.") | not))
718
+ |
719
+ # Drop any remaining macOS-scoped keys (e.g. *.osx)
720
+ with_entries(select(.key | test("(\\.|^)osx($|\\.)") | not))
721
+ |
722
+ # Drop string values that are Mac paths (avoid breaking Windows UI/features)
723
+ with_entries(
724
+ select(
725
+ (.value | type) != "string"
726
+ or
727
+ ((.value | test("^(\\/Users\\/|\\/opt\\/homebrew\\/|\\/Library\\/|\\$HOME\\/Library\\/)") ) | not)
728
+ )
729
+ )
730
+ ' "$input_file" > "$output_file"
731
+ else
732
+ warn "jq not available; copying Cursor settings without transformation"
733
+ cp "$input_file" "$output_file"
734
+ fi
735
+ }
736
+
737
+ # Transform MCP config from Windows to Mac format
738
+ transform_mcp_windows_to_mac() {
739
+ local input_file="$1"
740
+ local output_file="$2"
741
+ local mac_user="${USER}"
742
+ local mac_config_dir="/Users/${mac_user}/Dropbox/dev/configs"
743
+
744
+ # Use jq to transform the JSON
745
+ if command -v jq >/dev/null 2>&1; then
746
+ jq --arg mac_dir "$mac_config_dir" --arg mac_user "$mac_user" '
747
+ .mcpServers |= with_entries(
748
+ .value |= (
749
+ # Transform PowerShell commands back to shell scripts
750
+ if .command == "powershell" and (.args | length > 0) and (.args[-1] | test("\\.ps1$")) then
751
+ .command = (.args[-1] | sub("\\.ps1$"; ".sh") | sub("C:\\\\\\\\Users\\\\\\\\[^\\\\\\\\]+\\\\\\\\Dropbox\\\\\\\\mikpc\\\\\\\\dev\\\\\\\\configs"; $mac_dir) | gsub("\\\\"; "/"))
752
+ | .args = []
753
+ else
754
+ .
755
+ end |
756
+ # Transform Windows paths back to Mac paths
757
+ if .args then
758
+ .args = (.args | map(
759
+ if type == "string" then
760
+ if test("C:\\\\\\\\Users\\\\\\\\[^\\\\\\\\]+\\\\\\\\Dropbox\\\\\\\\mikpc\\\\\\\\dev$") then
761
+ "/Users/\($mac_user)/Dropbox/dev"
762
+ elif test("C:\\\\\\\\Users\\\\\\\\[^\\\\\\\\]+\\\\\\\\Dropbox\\\\\\\\mikpc\\\\\\\\dev\\\\\\\\configs") then
763
+ $mac_dir
764
+ elif test(".*\\\\\\\\scripts\\\\\\\\mcp\\\\\\\\.*\\.ps1$") then
765
+ (. | gsub("\\\\"; "/") | sub("C:/Users/[^/]+/Dropbox/mikpc/dev/configs"; $mac_dir) | sub("\\.ps1$"; ".sh"))
766
+ else
767
+ .
768
+ end
769
+ else
770
+ .
771
+ end
772
+ ))
773
+ else
774
+ .
775
+ end
776
+ )
777
+ )
778
+ ' "$input_file" > "$output_file"
779
+ else
780
+ # Fallback: simple sed-based transformation
781
+ warn "jq not available, using sed for MCP transformation (may be less accurate)"
782
+ sed -e "s|C:\\\\\\\\Users\\\\\\\\[^\\\\\\\\]*\\\\\\\\Dropbox\\\\\\\\mikpc\\\\\\\\dev|/Users/${mac_user}/Dropbox/dev|g" \
783
+ -e "s|C:\\\\\\\\Users\\\\\\\\[^\\\\\\\\]*\\\\\\\\Dropbox\\\\\\\\mikpc\\\\\\\\dev\\\\\\\\configs|${mac_config_dir}|g" \
784
+ -e "s|\\.ps1|.sh|g" \
785
+ -e "s|\"command\": \"powershell\",|\"command\":|g" \
786
+ -e "s|\"args\": \[\"-ExecutionPolicy\", \"Bypass\", \"-File\", \"\([^\"]*\)\"\]|\"args\": []|g" \
787
+ -e "s|\"command\": \"\([^\"]*\)\"|&|g" \
788
+ "$input_file" > "$output_file"
789
+ fi
790
+ }
791
+
792
+
793
+ # Get all configuration files (filtering Mac-only on Windows)
794
+ get_all_configs() {
795
+ # Use zsh array for better performance and maintainability
796
+ local configs=(
797
+ "shell/zsh/.zshrc"
798
+ "git/config/base.gitconfig"
799
+ "git/config/chiefmikey.gitconfig"
800
+ "git/config/$(get_git_config)"
801
+ "ide/vscode/settings.json"
802
+ "ide/vscode/keybindings.json"
803
+ "ide/vscode/insiders/settings.json"
804
+ "ide/vscode/insiders/keybindings.json"
805
+ "ide/cursor/settings.json"
806
+ "ide/cursor/keybindings.json"
807
+ "gh/config.yml"
808
+ "gh/hosts.yml"
809
+ "ssh/$(get_ssh_config)"
810
+ "ssh/personal_signing.pub"
811
+ "ide/vscode/vscode/extensions.txt"
812
+ )
813
+
814
+ # MCP config (transformed on Windows)
815
+ configs+=("ide/cursor/mcp.json")
816
+
817
+ # MCP launcher (installed to stable per-user path)
818
+ configs+=("scripts/mcp/mcp-launcher.sh")
819
+ configs+=("scripts/mcp/mcp-launcher.ps1")
820
+
821
+ # Docker configs (cross-platform)
822
+ configs+=(
823
+ "docker/mcp/registry.yaml"
824
+ "docker/mcp/catalog.json"
825
+ "docker/mcp/config.yaml"
826
+ "docker/mcp/tools.yaml"
827
+ "docker/mcp/catalogs/docker-mcp.yaml"
828
+ "docker/cli/daemon.json"
829
+ "docker/cli/features.json"
830
+ )
831
+
832
+ # Mac-only configs
833
+ if [[ "$IS_MAC" == "true" ]]; then
834
+ configs+=(
835
+ "iterm/com.googlecode.iterm2.plist"
836
+ "apps/RectangleConfig.plist"
837
+ "apps/iStat Menus Settings.ismp7"
838
+ )
839
+ fi
840
+
841
+ # WezTerm (cross-platform terminal)
842
+ configs+=("wezterm/wezterm.lua")
843
+
844
+ # Ghostty (Mac/Linux terminal)
845
+ configs+=("ghostty/config")
846
+
847
+ # PowerShell profile (Windows only — debug helpers)
848
+ configs+=("shell/powershell/Microsoft.PowerShell_profile.ps1")
849
+
850
+ # Synergy works on both platforms
851
+ configs+=("apps/Synergy")
852
+
853
+ # Claude Code configs (cross-platform)
854
+ configs+=(
855
+ "claude/settings.json"
856
+ "claude/settings.local.json"
857
+ "claude/hooks.json"
858
+ "claude/statusline-command.sh"
859
+ "claude/CLAUDE.md"
860
+ )
861
+
862
+ # Claude Desktop (Mac only)
863
+ if [[ "$IS_MAC" == "true" ]]; then
864
+ configs+=("claude/desktop/claude_desktop_config.json")
865
+ fi
866
+
867
+ # WSL configs (Windows only)
868
+ if [[ "$IS_WINDOWS" == "true" ]]; then
869
+ configs+=(
870
+ "wsl/.wslconfig"
871
+ "wsl/wsl.conf"
872
+ )
873
+ fi
874
+
875
+ # Print array elements (zsh automatically handles this)
876
+ print -l $configs
877
+ }
878
+
879
+ # Get destination path for a config file
880
+ get_dest_path() {
881
+ get_config_dest "$1"
882
+ }