install.ps1 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. <#
  2. .SYNOPSIS
  3. Install claude-mods extensions to ~/.claude/
  4. .DESCRIPTION
  5. Copies commands, skills, agents, and rules to the global Claude Code config.
  6. Handles cleanup of deprecated items and command-to-skill migrations.
  7. .NOTES
  8. Run from the claude-mods directory:
  9. .\scripts\install.ps1
  10. #>
  11. $ErrorActionPreference = "Stop"
  12. Write-Host "================================================================" -ForegroundColor Cyan
  13. Write-Host " claude-mods Installer (Windows) " -ForegroundColor Cyan
  14. Write-Host "================================================================" -ForegroundColor Cyan
  15. Write-Host ""
  16. $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
  17. $projectRoot = Split-Path -Parent $scriptDir
  18. if ($env:CLAUDE_DIR) {
  19. $claudeDir = $env:CLAUDE_DIR
  20. } else {
  21. $claudeDir = "$env:USERPROFILE\.claude"
  22. }
  23. # Ensure ~/.claude directories exist
  24. $dirs = @("commands", "skills", "agents", "rules", "output-styles", "hooks")
  25. foreach ($dir in $dirs) {
  26. $path = Join-Path $claudeDir $dir
  27. if (-not (Test-Path $path)) {
  28. New-Item -ItemType Directory -Path $path -Force | Out-Null
  29. Write-Host " Created $path" -ForegroundColor Green
  30. }
  31. }
  32. # =============================================================================
  33. # DEPRECATED ITEMS - Remove these from user config
  34. # =============================================================================
  35. $deprecated = @(
  36. "$claudeDir\commands\review.md",
  37. "$claudeDir\commands\testgen.md",
  38. "$claudeDir\commands\conclave.md",
  39. "$claudeDir\commands\pulse.md",
  40. "$claudeDir\skills\conclave",
  41. "$claudeDir\skills\claude-code-templates", # Replaced by skill-creator
  42. "$claudeDir\skills\agentmail", # Renamed to pigeon (v2.3.0)
  43. "$claudeDir\skills\claude-code-debug", # Merged into claude-code-ops (v3.0)
  44. "$claudeDir\skills\claude-code-headless", # Merged into claude-code-ops (v3.0)
  45. "$claudeDir\skills\claude-code-hooks", # Merged into claude-code-ops (v3.0)
  46. "$claudeDir\skills\dsp-launch", # Superseded by fleet-worker + native background agents (2026-07)
  47. # Deprecated agents (v3.0): folded into their -ops skill twins
  48. "$claudeDir\agents\python-expert.md",
  49. "$claudeDir\agents\typescript-expert.md",
  50. "$claudeDir\agents\javascript-expert.md",
  51. "$claudeDir\agents\go-expert.md",
  52. "$claudeDir\agents\rust-expert.md",
  53. "$claudeDir\agents\react-expert.md",
  54. "$claudeDir\agents\vue-expert.md",
  55. "$claudeDir\agents\astro-expert.md",
  56. "$claudeDir\agents\laravel-expert.md",
  57. "$claudeDir\agents\sql-expert.md",
  58. "$claudeDir\agents\postgres-expert.md",
  59. "$claudeDir\agents\cypress-expert.md", # -> skills/cypress-ops
  60. "$claudeDir\agents\cloudflare-expert.md", # -> skills/cloudflare-ops
  61. "$claudeDir\agents\wrangler-expert.md", # -> skills/cloudflare-ops
  62. "$claudeDir\agents\bash-expert.md", # -> skills/bash-ops
  63. "$claudeDir\agents\claude-architect.md", # -> skills/claude-code-ops
  64. "$claudeDir\agents\aws-fargate-ecs-expert.md", # -> skills/container-orchestration
  65. "$claudeDir\agents\craftcms-expert.md", # -> skills/craftcms-ops
  66. "$claudeDir\agents\payloadcms-expert.md", # -> skills/payloadcms-ops
  67. "$claudeDir\agents\asus-router-expert.md" # -> skills/asus-router-ops
  68. )
  69. # Renamed skills: -patterns -> -ops (March 2026)
  70. $renamedSkills = @(
  71. "cli-patterns",
  72. "mcp-patterns",
  73. "python-async-patterns",
  74. "python-cli-patterns",
  75. "python-database-patterns",
  76. "python-fastapi-patterns",
  77. "python-observability-patterns",
  78. "python-pytest-patterns",
  79. "python-typing-patterns",
  80. "rest-patterns",
  81. "security-patterns",
  82. "sql-patterns",
  83. "tailwind-patterns",
  84. "testing-patterns"
  85. )
  86. foreach ($oldSkill in $renamedSkills) {
  87. $oldPath = "$claudeDir\skills\$oldSkill"
  88. if (Test-Path $oldPath) {
  89. Remove-Item -Path $oldPath -Recurse -Force
  90. $newName = $oldSkill -replace '-patterns$', '-ops'
  91. Write-Host " Removed renamed: $oldSkill (now $newName)" -ForegroundColor Red
  92. }
  93. }
  94. Write-Host "Cleaning up deprecated items..." -ForegroundColor Yellow
  95. foreach ($item in $deprecated) {
  96. if (Test-Path $item) {
  97. Remove-Item -Path $item -Recurse -Force
  98. Write-Host " Removed: $item" -ForegroundColor Red
  99. }
  100. }
  101. Write-Host ""
  102. # =============================================================================
  103. # COMMANDS - Only copy commands that have not been migrated to skills
  104. # =============================================================================
  105. Write-Host "Installing commands..." -ForegroundColor Cyan
  106. $skipCommands = @("review.md", "testgen.md")
  107. $commandsDir = Join-Path $projectRoot "commands"
  108. Get-ChildItem -Path $commandsDir -Filter "*.md" | ForEach-Object {
  109. if ($_.Name -notin $skipCommands -and $_.Name -notlike "archive*") {
  110. Copy-Item $_.FullName -Destination "$claudeDir\commands\" -Force
  111. Write-Host " $($_.Name)" -ForegroundColor Green
  112. }
  113. }
  114. Write-Host ""
  115. # =============================================================================
  116. # SKILLS - Copy all skill directories
  117. # =============================================================================
  118. Write-Host "Installing skills..." -ForegroundColor Cyan
  119. $skillsDir = Join-Path $projectRoot "skills"
  120. Get-ChildItem -Path $skillsDir -Directory | ForEach-Object {
  121. $dest = "$claudeDir\skills\$($_.Name)"
  122. if (Test-Path $dest) {
  123. Remove-Item -Path $dest -Recurse -Force
  124. }
  125. Copy-Item $_.FullName -Destination $dest -Recurse -Force
  126. Write-Host " $($_.Name)/" -ForegroundColor Green
  127. }
  128. Write-Host ""
  129. # =============================================================================
  130. # AGENTS - Copy all agent files
  131. # =============================================================================
  132. Write-Host "Installing agents..." -ForegroundColor Cyan
  133. $agentsDir = Join-Path $projectRoot "agents"
  134. Get-ChildItem -Path $agentsDir -Filter "*.md" | ForEach-Object {
  135. Copy-Item $_.FullName -Destination "$claudeDir\agents\" -Force
  136. Write-Host " $($_.Name)" -ForegroundColor Green
  137. }
  138. Write-Host ""
  139. # =============================================================================
  140. # RULES - Copy all rule files
  141. # =============================================================================
  142. Write-Host "Installing rules..." -ForegroundColor Cyan
  143. $rulesDir = Join-Path $projectRoot "rules"
  144. Get-ChildItem -Path $rulesDir -Filter "*.md" | ForEach-Object {
  145. Copy-Item $_.FullName -Destination "$claudeDir\rules\" -Force
  146. Write-Host " $($_.Name)" -ForegroundColor Green
  147. }
  148. Write-Host ""
  149. # =============================================================================
  150. # OUTPUT STYLES - Copy all output style files
  151. # =============================================================================
  152. Write-Host "Installing output styles..." -ForegroundColor Cyan
  153. $stylesDir = Join-Path $projectRoot "output-styles"
  154. if (Test-Path $stylesDir) {
  155. Get-ChildItem -Path $stylesDir -Filter "*.md" | ForEach-Object {
  156. Copy-Item $_.FullName -Destination "$claudeDir\output-styles\" -Force
  157. Write-Host " $($_.Name)" -ForegroundColor Green
  158. }
  159. }
  160. Write-Host ""
  161. # =============================================================================
  162. # HOOKS - Copy scripts and merge plugin-equivalent wiring into settings.json
  163. # =============================================================================
  164. Write-Host "Installing hooks..." -ForegroundColor Cyan
  165. $hooksDir = Join-Path $projectRoot "hooks"
  166. Get-ChildItem -Path $hooksDir -Filter "*.sh" | ForEach-Object {
  167. Copy-Item $_.FullName -Destination "$claudeDir\hooks\" -Force
  168. }
  169. $settingsPath = Join-Path $claudeDir "settings.json"
  170. if (Test-Path $settingsPath) {
  171. $settings = Get-Content $settingsPath -Raw | ConvertFrom-Json
  172. } else {
  173. $settings = [PSCustomObject]@{}
  174. }
  175. if (-not $settings.PSObject.Properties["hooks"]) {
  176. $settings | Add-Member -MemberType NoteProperty -Name hooks -Value ([PSCustomObject]@{})
  177. }
  178. $desired = Get-Content (Join-Path $hooksDir "hooks.json") -Raw | ConvertFrom-Json
  179. foreach ($eventProperty in $desired.hooks.PSObject.Properties) {
  180. $eventName = $eventProperty.Name
  181. if (-not $settings.hooks.PSObject.Properties[$eventName]) {
  182. $settings.hooks | Add-Member -MemberType NoteProperty -Name $eventName -Value @()
  183. }
  184. $eventGroups = @($settings.hooks.$eventName)
  185. foreach ($group in @($eventProperty.Value)) {
  186. $missingHooks = @()
  187. foreach ($hook in @($group.hooks)) {
  188. $command = $hook.command.Replace('${CLAUDE_PLUGIN_ROOT}/hooks', (Join-Path $claudeDir "hooks"))
  189. # Dedup on the script NAME under hooks/, not the resolved path: a
  190. # hook wired by a plugin install carries the
  191. # ${CLAUDE_PLUGIN_ROOT}/hooks/ form and must still count as
  192. # already-wired (mixed-method double-fire). Separator-tolerant:
  193. # Join-Path yields \hooks while plugin form uses /hooks.
  194. $hookName = ($command -replace '^.*hooks[/\\]', '') -replace '"$', ''
  195. $alreadyWired = $false
  196. foreach ($existingGroup in $eventGroups) {
  197. foreach ($existingHook in @($existingGroup.hooks)) {
  198. if ($existingHook.command -and ($existingHook.command.Contains("hooks/$hookName") -or $existingHook.command.Contains("hooks\$hookName"))) {
  199. $alreadyWired = $true
  200. }
  201. }
  202. }
  203. if (-not $alreadyWired) {
  204. $copy = $hook.PSObject.Copy()
  205. $copy.command = $command
  206. $missingHooks += $copy
  207. }
  208. }
  209. if ($missingHooks.Count -gt 0) {
  210. $newGroup = $group.PSObject.Copy()
  211. $newGroup.hooks = $missingHooks
  212. $eventGroups += $newGroup
  213. }
  214. }
  215. $settings.hooks.$eventName = $eventGroups
  216. }
  217. $settings | ConvertTo-Json -Depth 20 | Set-Content $settingsPath -Encoding UTF8
  218. Write-Host " Security and peer-guard hooks wired in settings.json" -ForegroundColor Green
  219. Write-Host ""
  220. # =============================================================================
  221. # PIGEON - Global install (scripts + hook config hint)
  222. # =============================================================================
  223. Write-Host "Installing pigeon (pmail)..." -ForegroundColor Cyan
  224. # Clean up old agentmail install if present
  225. $oldAgentmailDir = Join-Path $claudeDir "agentmail"
  226. if (Test-Path $oldAgentmailDir) {
  227. Remove-Item -Path $oldAgentmailDir -Recurse -Force
  228. Write-Host " Removed old agentmail/ (renamed to pigeon/)" -ForegroundColor Red
  229. }
  230. $pigeonDir = Join-Path $claudeDir "pigeon"
  231. New-Item -ItemType Directory -Force -Path $pigeonDir | Out-Null
  232. $mailDbSrc = Join-Path $projectRoot "skills\pigeon\scripts\mail-db.sh"
  233. $checkMailSrc = Join-Path $projectRoot "hooks\check-mail.sh"
  234. if (Test-Path $mailDbSrc) {
  235. Copy-Item $mailDbSrc -Destination "$pigeonDir\" -Force
  236. Write-Host " mail-db.sh" -ForegroundColor Green
  237. }
  238. if (Test-Path $checkMailSrc) {
  239. Copy-Item $checkMailSrc -Destination "$pigeonDir\" -Force
  240. Write-Host " check-mail.sh" -ForegroundColor Green
  241. }
  242. $settingsPath = Join-Path $claudeDir "settings.json"
  243. # Migrate stale agentmail hook path -> pigeon
  244. if ((Test-Path $settingsPath) -and (Select-String -Path $settingsPath -Pattern "agentmail/check-mail\.sh" -Quiet)) {
  245. $content = Get-Content $settingsPath -Raw
  246. $content = $content -replace 'agentmail/check-mail\.sh', 'pigeon/check-mail.sh'
  247. Set-Content $settingsPath -Value $content -NoNewline
  248. Write-Host " Migrated agentmail hook -> pigeon in settings.json" -ForegroundColor Green
  249. }
  250. # Check if hook is already configured (pigeon path)
  251. if ((Test-Path $settingsPath) -and (Select-String -Path $settingsPath -Pattern "pigeon/check-mail\.sh" -Quiet)) {
  252. Write-Host " Hook already configured in settings.json" -ForegroundColor Green
  253. } else {
  254. Write-Host ""
  255. Write-Host ' To enable automatic pmail notifications, add this to ~/.claude/settings.json:' -ForegroundColor Yellow
  256. Write-Host ""
  257. Write-Host ' "hooks": {'
  258. Write-Host ' "PreToolUse": [{'
  259. Write-Host ' "matcher": "*",'
  260. Write-Host ' "hooks": [{'
  261. Write-Host ' "type": "command",'
  262. Write-Host ' "command": "bash \"$HOME/.claude/pigeon/check-mail.sh\"",'
  263. Write-Host ' "timeout": 5'
  264. Write-Host ' }]'
  265. Write-Host ' }]'
  266. Write-Host ' }'
  267. Write-Host ""
  268. Write-Host " Without this, pigeon works but you must check manually (pigeon read)." -ForegroundColor Yellow
  269. }
  270. Write-Host ""
  271. # =============================================================================
  272. # AUTO-SKILL - Global install (tracking + evaluation hooks)
  273. # =============================================================================
  274. Write-Host "Installing auto-skill..." -ForegroundColor Cyan
  275. $autoSkillDir = Join-Path $claudeDir "auto-skill"
  276. New-Item -ItemType Directory -Force -Path $autoSkillDir | Out-Null
  277. $scripts = @("track-tools.sh", "evaluate.sh")
  278. foreach ($script in $scripts) {
  279. $src = Join-Path $projectRoot "skills\auto-skill\scripts\$script"
  280. if (Test-Path $src) {
  281. Copy-Item $src -Destination "$autoSkillDir\" -Force
  282. Write-Host " $script" -ForegroundColor Green
  283. }
  284. }
  285. $settingsPath = Join-Path $claudeDir "settings.json"
  286. if ((Test-Path $settingsPath) -and (Select-String -Path $settingsPath -Pattern "auto-skill" -Quiet)) {
  287. Write-Host " Hooks already configured in settings.json" -ForegroundColor Green
  288. } else {
  289. Write-Host ""
  290. Write-Host ' To enable automatic skill suggestions, add these hooks to ~/.claude/settings.json:' -ForegroundColor Yellow
  291. Write-Host ""
  292. Write-Host ' "PostToolUse": [{ "matcher": "*", "hooks": [{'
  293. Write-Host ' "type": "command",'
  294. Write-Host ' "command": "bash \"$HOME/.claude/auto-skill/track-tools.sh\"", "timeout": 2'
  295. Write-Host ' }] }],'
  296. Write-Host ' "Stop": [{ "hooks": [{'
  297. Write-Host ' "type": "command",'
  298. Write-Host ' "command": "bash \"$HOME/.claude/auto-skill/evaluate.sh\"", "timeout": 5'
  299. Write-Host ' }] }]'
  300. Write-Host ""
  301. Write-Host " Without this, /auto-skill still works but won't suggest automatically." -ForegroundColor Yellow
  302. }
  303. Write-Host ""
  304. # =============================================================================
  305. # SUMMARY
  306. # =============================================================================
  307. Write-Host "================================================================" -ForegroundColor Cyan
  308. Write-Host " Installation complete!" -ForegroundColor Green
  309. Write-Host "================================================================" -ForegroundColor Cyan
  310. Write-Host ""
  311. Write-Host "Restart Claude Code to load the new extensions." -ForegroundColor Yellow
  312. Write-Host ""