@arthai/agents 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 (390) hide show
  1. package/README.md +123 -0
  2. package/VERSION +1 -0
  3. package/agents/ai-consultant.md +999 -0
  4. package/agents/architect.md +174 -0
  5. package/agents/code-reviewer.md +115 -0
  6. package/agents/competitive-analyst.md +688 -0
  7. package/agents/content-strategist.md +607 -0
  8. package/agents/design-studio-create.md +304 -0
  9. package/agents/design-studio-critique.md +258 -0
  10. package/agents/design-studio-think.md +79 -0
  11. package/agents/domain-hunter.md +519 -0
  12. package/agents/explore-light.md +52 -0
  13. package/agents/frontend.md +261 -0
  14. package/agents/gtm-expert.md +811 -0
  15. package/agents/meeting-prep.md +318 -0
  16. package/agents/ops.md +149 -0
  17. package/agents/product-manager.md +563 -0
  18. package/agents/python-backend.md +286 -0
  19. package/agents/qa-baseline-updater.md +45 -0
  20. package/agents/qa-challenger.md +97 -0
  21. package/agents/qa-domain.md +145 -0
  22. package/agents/qa-e2e.md +184 -0
  23. package/agents/qa-test-promoter.md +97 -0
  24. package/agents/qa.md +226 -0
  25. package/agents/setup.md +134 -0
  26. package/agents/sre.md +165 -0
  27. package/agents/stakeholder-reporter.md +94 -0
  28. package/agents/user-researcher.md +602 -0
  29. package/bin/cli.js +322 -0
  30. package/bundles/canvas.json +16 -0
  31. package/bundles/compass.json +16 -0
  32. package/bundles/counsel.json +31 -0
  33. package/bundles/cruise.json +11 -0
  34. package/bundles/forge.json +26 -0
  35. package/bundles/prime.json +10 -0
  36. package/bundles/prism.json +23 -0
  37. package/bundles/scalpel.json +17 -0
  38. package/bundles/sentinel.json +19 -0
  39. package/bundles/shield.json +14 -0
  40. package/bundles/spark.json +19 -0
  41. package/compiler.sh +305 -0
  42. package/dist/plugins/canvas/.claude-plugin/plugin.json +6 -0
  43. package/dist/plugins/canvas/agents/design-studio-create.md +304 -0
  44. package/dist/plugins/canvas/agents/design-studio-critique.md +258 -0
  45. package/dist/plugins/canvas/agents/design-studio-think.md +79 -0
  46. package/dist/plugins/canvas/agents/frontend.md +261 -0
  47. package/dist/plugins/canvas/skills/planning/SKILL.md +436 -0
  48. package/dist/plugins/compass/.claude-plugin/plugin.json +6 -0
  49. package/dist/plugins/compass/agents/content-strategist.md +607 -0
  50. package/dist/plugins/compass/agents/gtm-expert.md +811 -0
  51. package/dist/plugins/compass/agents/product-manager.md +563 -0
  52. package/dist/plugins/compass/agents/user-researcher.md +602 -0
  53. package/dist/plugins/compass/skills/planning/SKILL.md +436 -0
  54. package/dist/plugins/counsel/.claude-plugin/plugin.json +6 -0
  55. package/dist/plugins/counsel/agents/ai-consultant.md +999 -0
  56. package/dist/plugins/counsel/agents/competitive-analyst.md +688 -0
  57. package/dist/plugins/counsel/agents/meeting-prep.md +318 -0
  58. package/dist/plugins/counsel/agents/stakeholder-reporter.md +94 -0
  59. package/dist/plugins/counsel/hooks/check-deliverable.sh +65 -0
  60. package/dist/plugins/counsel/hooks/ensure-client-dir.sh +59 -0
  61. package/dist/plugins/counsel/hooks/hooks.json +28 -0
  62. package/dist/plugins/counsel/skills/client-discovery/SKILL.md +266 -0
  63. package/dist/plugins/counsel/skills/consulting/SKILL.md +282 -0
  64. package/dist/plugins/counsel/skills/deliverable-builder/SKILL.md +928 -0
  65. package/dist/plugins/counsel/skills/engagement-tracker/SKILL.md +380 -0
  66. package/dist/plugins/counsel/skills/market-research/SKILL.md +300 -0
  67. package/dist/plugins/counsel/skills/opportunity-map/SKILL.md +307 -0
  68. package/dist/plugins/counsel/skills/pitch-generator/SKILL.md +378 -0
  69. package/dist/plugins/counsel/skills/roi-calculator/SKILL.md +469 -0
  70. package/dist/plugins/counsel/skills/share/SKILL.md +211 -0
  71. package/dist/plugins/counsel/skills/solution-architect/SKILL.md +566 -0
  72. package/dist/plugins/counsel/skills/templates/SKILL.md +194 -0
  73. package/dist/plugins/counsel/skills/welcome/SKILL.md +136 -0
  74. package/dist/plugins/counsel/skills/wizard/SKILL.md +411 -0
  75. package/dist/plugins/cruise/.claude-plugin/plugin.json +6 -0
  76. package/dist/plugins/cruise/skills/autopilot/SKILL.md +425 -0
  77. package/dist/plugins/forge/.claude-plugin/plugin.json +6 -0
  78. package/dist/plugins/forge/agents/architect.md +174 -0
  79. package/dist/plugins/forge/agents/code-reviewer.md +115 -0
  80. package/dist/plugins/forge/agents/frontend.md +261 -0
  81. package/dist/plugins/forge/agents/product-manager.md +563 -0
  82. package/dist/plugins/forge/agents/python-backend.md +286 -0
  83. package/dist/plugins/forge/agents/qa.md +226 -0
  84. package/dist/plugins/forge/hooks/hooks.json +28 -0
  85. package/dist/plugins/forge/hooks/post-test-summary.sh +115 -0
  86. package/dist/plugins/forge/hooks/triage-router.sh +740 -0
  87. package/dist/plugins/forge/skills/implement/SKILL.md +532 -0
  88. package/dist/plugins/forge/skills/planning/SKILL.md +436 -0
  89. package/dist/plugins/forge/skills/pr/SKILL.md +275 -0
  90. package/dist/plugins/forge/skills/precheck/SKILL.md +159 -0
  91. package/dist/plugins/forge/skills/qa/SKILL.md +127 -0
  92. package/dist/plugins/forge/skills/review-pr/SKILL.md +367 -0
  93. package/dist/plugins/prime/.claude-plugin/plugin.json +6 -0
  94. package/dist/plugins/prime/agents/ai-consultant.md +999 -0
  95. package/dist/plugins/prime/agents/architect.md +174 -0
  96. package/dist/plugins/prime/agents/code-reviewer.md +115 -0
  97. package/dist/plugins/prime/agents/competitive-analyst.md +688 -0
  98. package/dist/plugins/prime/agents/content-strategist.md +607 -0
  99. package/dist/plugins/prime/agents/design-studio-create.md +304 -0
  100. package/dist/plugins/prime/agents/design-studio-critique.md +258 -0
  101. package/dist/plugins/prime/agents/design-studio-think.md +79 -0
  102. package/dist/plugins/prime/agents/explore-light.md +52 -0
  103. package/dist/plugins/prime/agents/frontend.md +261 -0
  104. package/dist/plugins/prime/agents/gtm-expert.md +811 -0
  105. package/dist/plugins/prime/agents/meeting-prep.md +318 -0
  106. package/dist/plugins/prime/agents/ops.md +149 -0
  107. package/dist/plugins/prime/agents/product-manager.md +563 -0
  108. package/dist/plugins/prime/agents/python-backend.md +286 -0
  109. package/dist/plugins/prime/agents/qa-baseline-updater.md +45 -0
  110. package/dist/plugins/prime/agents/qa-challenger.md +97 -0
  111. package/dist/plugins/prime/agents/qa-domain.md +145 -0
  112. package/dist/plugins/prime/agents/qa-e2e.md +184 -0
  113. package/dist/plugins/prime/agents/qa-test-promoter.md +97 -0
  114. package/dist/plugins/prime/agents/qa.md +226 -0
  115. package/dist/plugins/prime/agents/setup.md +134 -0
  116. package/dist/plugins/prime/agents/sre.md +165 -0
  117. package/dist/plugins/prime/agents/stakeholder-reporter.md +94 -0
  118. package/dist/plugins/prime/agents/user-researcher.md +602 -0
  119. package/dist/plugins/prime/hooks/check-deliverable.sh +65 -0
  120. package/dist/plugins/prime/hooks/ensure-client-dir.sh +59 -0
  121. package/dist/plugins/prime/hooks/hooks.json +184 -0
  122. package/dist/plugins/prime/hooks/post-deploy-health.sh +83 -0
  123. package/dist/plugins/prime/hooks/post-diff-test-compare.sh +125 -0
  124. package/dist/plugins/prime/hooks/post-edit-lint.sh +92 -0
  125. package/dist/plugins/prime/hooks/post-git-state.sh +54 -0
  126. package/dist/plugins/prime/hooks/post-merge-cleanup.sh +101 -0
  127. package/dist/plugins/prime/hooks/post-test-summary.sh +115 -0
  128. package/dist/plugins/prime/hooks/pre-bash-guard.sh +142 -0
  129. package/dist/plugins/prime/hooks/pre-edit-guard.sh +121 -0
  130. package/dist/plugins/prime/hooks/pre-task-context.sh +113 -0
  131. package/dist/plugins/prime/hooks/session-bootstrap.sh +379 -0
  132. package/dist/plugins/prime/hooks/session-end.sh +107 -0
  133. package/dist/plugins/prime/hooks/session-summary.sh +97 -0
  134. package/dist/plugins/prime/hooks/sync-agents.sh +269 -0
  135. package/dist/plugins/prime/hooks/triage-router.sh +740 -0
  136. package/dist/plugins/prime/skills/arth/SKILL.md +165 -0
  137. package/dist/plugins/prime/skills/autopilot/SKILL.md +425 -0
  138. package/dist/plugins/prime/skills/calibrate/SKILL.md +1807 -0
  139. package/dist/plugins/prime/skills/ci-fix/SKILL.md +293 -0
  140. package/dist/plugins/prime/skills/client-discovery/SKILL.md +266 -0
  141. package/dist/plugins/prime/skills/consulting/SKILL.md +282 -0
  142. package/dist/plugins/prime/skills/custom-domain/SKILL.md +261 -0
  143. package/dist/plugins/prime/skills/deliverable-builder/SKILL.md +928 -0
  144. package/dist/plugins/prime/skills/discord-ops/SKILL.md +125 -0
  145. package/dist/plugins/prime/skills/engagement-tracker/SKILL.md +380 -0
  146. package/dist/plugins/prime/skills/explore.md +43 -0
  147. package/dist/plugins/prime/skills/fix/SKILL.md +1058 -0
  148. package/dist/plugins/prime/skills/implement/SKILL.md +532 -0
  149. package/dist/plugins/prime/skills/incident/SKILL.md +910 -0
  150. package/dist/plugins/prime/skills/issue/SKILL.md +134 -0
  151. package/dist/plugins/prime/skills/market-research/SKILL.md +300 -0
  152. package/dist/plugins/prime/skills/onboard/SKILL.md +344 -0
  153. package/dist/plugins/prime/skills/opportunity-map/SKILL.md +307 -0
  154. package/dist/plugins/prime/skills/pitch-generator/SKILL.md +378 -0
  155. package/dist/plugins/prime/skills/planning/SKILL.md +436 -0
  156. package/dist/plugins/prime/skills/pr/SKILL.md +275 -0
  157. package/dist/plugins/prime/skills/precheck/SKILL.md +159 -0
  158. package/dist/plugins/prime/skills/qa/SKILL.md +127 -0
  159. package/dist/plugins/prime/skills/qa-incident/SKILL.md +54 -0
  160. package/dist/plugins/prime/skills/qa-learn/SKILL.md +47 -0
  161. package/dist/plugins/prime/skills/restart/SKILL.md +70 -0
  162. package/dist/plugins/prime/skills/review-pr/SKILL.md +367 -0
  163. package/dist/plugins/prime/skills/roi-calculator/SKILL.md +469 -0
  164. package/dist/plugins/prime/skills/scan/SKILL.md +232 -0
  165. package/dist/plugins/prime/skills/setup/SKILL.md +691 -0
  166. package/dist/plugins/prime/skills/share/SKILL.md +211 -0
  167. package/dist/plugins/prime/skills/solution-architect/SKILL.md +566 -0
  168. package/dist/plugins/prime/skills/sre/SKILL.md +362 -0
  169. package/dist/plugins/prime/skills/sync/SKILL.md +188 -0
  170. package/dist/plugins/prime/skills/templates/SKILL.md +194 -0
  171. package/dist/plugins/prime/skills/welcome/SKILL.md +136 -0
  172. package/dist/plugins/prime/skills/wizard/SKILL.md +411 -0
  173. package/dist/plugins/prism/.claude-plugin/plugin.json +6 -0
  174. package/dist/plugins/prism/agents/qa-baseline-updater.md +45 -0
  175. package/dist/plugins/prism/agents/qa-challenger.md +97 -0
  176. package/dist/plugins/prism/agents/qa-domain.md +145 -0
  177. package/dist/plugins/prism/agents/qa-e2e.md +184 -0
  178. package/dist/plugins/prism/agents/qa-test-promoter.md +97 -0
  179. package/dist/plugins/prism/agents/qa.md +226 -0
  180. package/dist/plugins/prism/hooks/hooks.json +26 -0
  181. package/dist/plugins/prism/hooks/post-diff-test-compare.sh +125 -0
  182. package/dist/plugins/prism/hooks/post-test-summary.sh +115 -0
  183. package/dist/plugins/prism/skills/qa/SKILL.md +127 -0
  184. package/dist/plugins/prism/skills/qa-incident/SKILL.md +54 -0
  185. package/dist/plugins/prism/skills/qa-learn/SKILL.md +47 -0
  186. package/dist/plugins/scalpel/.claude-plugin/plugin.json +6 -0
  187. package/dist/plugins/scalpel/agents/code-reviewer.md +115 -0
  188. package/dist/plugins/scalpel/hooks/hooks.json +26 -0
  189. package/dist/plugins/scalpel/hooks/pre-edit-guard.sh +121 -0
  190. package/dist/plugins/scalpel/skills/ci-fix/SKILL.md +293 -0
  191. package/dist/plugins/scalpel/skills/fix/SKILL.md +1058 -0
  192. package/dist/plugins/scalpel/skills/issue/SKILL.md +134 -0
  193. package/dist/plugins/sentinel/.claude-plugin/plugin.json +6 -0
  194. package/dist/plugins/sentinel/agents/ops.md +149 -0
  195. package/dist/plugins/sentinel/agents/sre.md +165 -0
  196. package/dist/plugins/sentinel/hooks/hooks.json +26 -0
  197. package/dist/plugins/sentinel/hooks/post-deploy-health.sh +83 -0
  198. package/dist/plugins/sentinel/hooks/post-git-state.sh +54 -0
  199. package/dist/plugins/sentinel/skills/incident/SKILL.md +910 -0
  200. package/dist/plugins/sentinel/skills/restart/SKILL.md +70 -0
  201. package/dist/plugins/sentinel/skills/sre/SKILL.md +362 -0
  202. package/dist/plugins/shield/.claude-plugin/plugin.json +6 -0
  203. package/dist/plugins/shield/hooks/hooks.json +60 -0
  204. package/dist/plugins/shield/hooks/pre-bash-guard.sh +142 -0
  205. package/dist/plugins/shield/hooks/pre-edit-guard.sh +121 -0
  206. package/dist/plugins/shield/hooks/session-bootstrap.sh +379 -0
  207. package/dist/plugins/shield/hooks/triage-router.sh +740 -0
  208. package/dist/plugins/spark/.claude-plugin/plugin.json +6 -0
  209. package/dist/plugins/spark/agents/explore-light.md +52 -0
  210. package/dist/plugins/spark/agents/setup.md +134 -0
  211. package/dist/plugins/spark/hooks/hooks.json +16 -0
  212. package/dist/plugins/spark/hooks/session-bootstrap.sh +379 -0
  213. package/dist/plugins/spark/skills/calibrate/SKILL.md +1807 -0
  214. package/dist/plugins/spark/skills/onboard/SKILL.md +344 -0
  215. package/dist/plugins/spark/skills/scan/SKILL.md +232 -0
  216. package/dist/plugins/spark/skills/setup/SKILL.md +691 -0
  217. package/hook-defs.json +104 -0
  218. package/hooks/check-deliverable.sh +65 -0
  219. package/hooks/ensure-client-dir.sh +59 -0
  220. package/hooks/hooks.json +16 -0
  221. package/hooks/post-deploy-health.sh +83 -0
  222. package/hooks/post-diff-test-compare.sh +125 -0
  223. package/hooks/post-edit-lint.sh +92 -0
  224. package/hooks/post-git-state.sh +54 -0
  225. package/hooks/post-merge-cleanup.sh +101 -0
  226. package/hooks/post-test-summary.sh +115 -0
  227. package/hooks/pre-bash-guard.sh +142 -0
  228. package/hooks/pre-edit-guard.sh +121 -0
  229. package/hooks/pre-task-context.sh +113 -0
  230. package/hooks/session-bootstrap.sh +379 -0
  231. package/hooks/session-end.sh +107 -0
  232. package/hooks/session-start.sh +46 -0
  233. package/hooks/session-summary.sh +97 -0
  234. package/hooks/sync-agents.sh +269 -0
  235. package/hooks/triage-router.sh +740 -0
  236. package/install.sh +3185 -0
  237. package/package.json +40 -0
  238. package/portable.manifest +112 -0
  239. package/skills/arth/SKILL.md +165 -0
  240. package/skills/autopilot/SKILL.md +425 -0
  241. package/skills/calibrate/SKILL.md +1807 -0
  242. package/skills/ci-fix/SKILL.md +293 -0
  243. package/skills/client-discovery/SKILL.md +266 -0
  244. package/skills/consulting/SKILL.md +282 -0
  245. package/skills/continue/SKILL.md +174 -0
  246. package/skills/custom-domain/SKILL.md +261 -0
  247. package/skills/deliverable-builder/SKILL.md +928 -0
  248. package/skills/discord-ops/SKILL.md +125 -0
  249. package/skills/engagement-tracker/SKILL.md +380 -0
  250. package/skills/explore.md +43 -0
  251. package/skills/fix/SKILL.md +1058 -0
  252. package/skills/implement/SKILL.md +532 -0
  253. package/skills/incident/SKILL.md +910 -0
  254. package/skills/issue/SKILL.md +134 -0
  255. package/skills/market-research/SKILL.md +300 -0
  256. package/skills/onboard/SKILL.md +344 -0
  257. package/skills/opportunity-map/SKILL.md +307 -0
  258. package/skills/pitch-generator/SKILL.md +378 -0
  259. package/skills/planning/SKILL.md +436 -0
  260. package/skills/pr/SKILL.md +275 -0
  261. package/skills/precheck/SKILL.md +159 -0
  262. package/skills/qa/SKILL.md +127 -0
  263. package/skills/qa-incident/SKILL.md +54 -0
  264. package/skills/qa-learn/SKILL.md +47 -0
  265. package/skills/railway/central-station/SKILL.md +226 -0
  266. package/skills/railway/central-station/references/environment-config.md +183 -0
  267. package/skills/railway/central-station/references/monorepo.md +216 -0
  268. package/skills/railway/central-station/references/railpack.md +257 -0
  269. package/skills/railway/central-station/references/variables.md +170 -0
  270. package/skills/railway/database/SKILL.md +284 -0
  271. package/skills/railway/database/references/environment-config.md +183 -0
  272. package/skills/railway/database/references/monorepo.md +216 -0
  273. package/skills/railway/database/references/railpack.md +257 -0
  274. package/skills/railway/database/references/variables.md +170 -0
  275. package/skills/railway/database/scripts/railway-api.sh +41 -0
  276. package/skills/railway/deploy/SKILL.md +128 -0
  277. package/skills/railway/deploy/references/environment-config.md +183 -0
  278. package/skills/railway/deploy/references/monorepo.md +216 -0
  279. package/skills/railway/deploy/references/railpack.md +257 -0
  280. package/skills/railway/deploy/references/variables.md +170 -0
  281. package/skills/railway/deployment/SKILL.md +222 -0
  282. package/skills/railway/deployment/references/environment-config.md +183 -0
  283. package/skills/railway/deployment/references/monorepo.md +216 -0
  284. package/skills/railway/deployment/references/railpack.md +257 -0
  285. package/skills/railway/deployment/references/variables.md +170 -0
  286. package/skills/railway/domain/SKILL.md +137 -0
  287. package/skills/railway/domain/references/environment-config.md +183 -0
  288. package/skills/railway/domain/references/monorepo.md +216 -0
  289. package/skills/railway/domain/references/railpack.md +257 -0
  290. package/skills/railway/domain/references/variables.md +170 -0
  291. package/skills/railway/environment/SKILL.md +266 -0
  292. package/skills/railway/environment/references/environment-config.md +183 -0
  293. package/skills/railway/environment/references/monorepo.md +216 -0
  294. package/skills/railway/environment/references/railpack.md +257 -0
  295. package/skills/railway/environment/references/variables.md +170 -0
  296. package/skills/railway/metrics/SKILL.md +211 -0
  297. package/skills/railway/metrics/references/environment-config.md +183 -0
  298. package/skills/railway/metrics/references/monorepo.md +216 -0
  299. package/skills/railway/metrics/references/railpack.md +257 -0
  300. package/skills/railway/metrics/references/variables.md +170 -0
  301. package/skills/railway/metrics/scripts/railway-api.sh +41 -0
  302. package/skills/railway/new/SKILL.md +489 -0
  303. package/skills/railway/new/references/environment-config.md +183 -0
  304. package/skills/railway/new/references/monorepo.md +216 -0
  305. package/skills/railway/new/references/railpack.md +257 -0
  306. package/skills/railway/new/references/variables.md +170 -0
  307. package/skills/railway/projects/SKILL.md +142 -0
  308. package/skills/railway/projects/references/environment-config.md +183 -0
  309. package/skills/railway/projects/references/monorepo.md +216 -0
  310. package/skills/railway/projects/references/railpack.md +257 -0
  311. package/skills/railway/projects/references/variables.md +170 -0
  312. package/skills/railway/projects/scripts/railway-api.sh +41 -0
  313. package/skills/railway/railway-docs/SKILL.md +47 -0
  314. package/skills/railway/railway-docs/references/environment-config.md +183 -0
  315. package/skills/railway/railway-docs/references/monorepo.md +216 -0
  316. package/skills/railway/railway-docs/references/railpack.md +257 -0
  317. package/skills/railway/railway-docs/references/variables.md +170 -0
  318. package/skills/railway/service/SKILL.md +249 -0
  319. package/skills/railway/service/references/environment-config.md +183 -0
  320. package/skills/railway/service/references/monorepo.md +216 -0
  321. package/skills/railway/service/references/railpack.md +257 -0
  322. package/skills/railway/service/references/variables.md +170 -0
  323. package/skills/railway/service/scripts/railway-api.sh +41 -0
  324. package/skills/railway/status/SKILL.md +91 -0
  325. package/skills/railway/status/references/environment-config.md +183 -0
  326. package/skills/railway/status/references/monorepo.md +216 -0
  327. package/skills/railway/status/references/railpack.md +257 -0
  328. package/skills/railway/status/references/variables.md +170 -0
  329. package/skills/railway/templates/SKILL.md +275 -0
  330. package/skills/railway/templates/references/environment-config.md +183 -0
  331. package/skills/railway/templates/references/monorepo.md +216 -0
  332. package/skills/railway/templates/references/railpack.md +257 -0
  333. package/skills/railway/templates/references/variables.md +170 -0
  334. package/skills/railway/templates/scripts/railway-api.sh +41 -0
  335. package/skills/restart/SKILL.md +70 -0
  336. package/skills/review-pr/SKILL.md +367 -0
  337. package/skills/roi-calculator/SKILL.md +469 -0
  338. package/skills/scan/SKILL.md +232 -0
  339. package/skills/setup/SKILL.md +691 -0
  340. package/skills/share/SKILL.md +211 -0
  341. package/skills/solution-architect/SKILL.md +566 -0
  342. package/skills/sre/SKILL.md +362 -0
  343. package/skills/superpowers/brainstorming/SKILL.md +96 -0
  344. package/skills/superpowers/dispatching-parallel-agents/SKILL.md +180 -0
  345. package/skills/superpowers/executing-plans/SKILL.md +84 -0
  346. package/skills/superpowers/finishing-a-development-branch/SKILL.md +200 -0
  347. package/skills/superpowers/receiving-code-review/SKILL.md +213 -0
  348. package/skills/superpowers/requesting-code-review/SKILL.md +105 -0
  349. package/skills/superpowers/requesting-code-review/code-reviewer.md +146 -0
  350. package/skills/superpowers/subagent-driven-development/SKILL.md +242 -0
  351. package/skills/superpowers/subagent-driven-development/code-quality-reviewer-prompt.md +20 -0
  352. package/skills/superpowers/subagent-driven-development/implementer-prompt.md +78 -0
  353. package/skills/superpowers/subagent-driven-development/spec-reviewer-prompt.md +61 -0
  354. package/skills/superpowers/systematic-debugging/CREATION-LOG.md +119 -0
  355. package/skills/superpowers/systematic-debugging/SKILL.md +296 -0
  356. package/skills/superpowers/systematic-debugging/condition-based-waiting-example.ts +158 -0
  357. package/skills/superpowers/systematic-debugging/condition-based-waiting.md +115 -0
  358. package/skills/superpowers/systematic-debugging/defense-in-depth.md +122 -0
  359. package/skills/superpowers/systematic-debugging/find-polluter.sh +63 -0
  360. package/skills/superpowers/systematic-debugging/root-cause-tracing.md +169 -0
  361. package/skills/superpowers/systematic-debugging/test-academic.md +14 -0
  362. package/skills/superpowers/systematic-debugging/test-pressure-1.md +58 -0
  363. package/skills/superpowers/systematic-debugging/test-pressure-2.md +68 -0
  364. package/skills/superpowers/systematic-debugging/test-pressure-3.md +69 -0
  365. package/skills/superpowers/test-driven-development/SKILL.md +371 -0
  366. package/skills/superpowers/test-driven-development/testing-anti-patterns.md +299 -0
  367. package/skills/superpowers/using-git-worktrees/SKILL.md +218 -0
  368. package/skills/superpowers/using-superpowers/SKILL.md +95 -0
  369. package/skills/superpowers/verification-before-completion/SKILL.md +139 -0
  370. package/skills/superpowers/writing-plans/SKILL.md +116 -0
  371. package/skills/superpowers/writing-skills/SKILL.md +655 -0
  372. package/skills/superpowers/writing-skills/anthropic-best-practices.md +1150 -0
  373. package/skills/superpowers/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  374. package/skills/superpowers/writing-skills/graphviz-conventions.dot +172 -0
  375. package/skills/superpowers/writing-skills/persuasion-principles.md +187 -0
  376. package/skills/superpowers/writing-skills/render-graphs.js +168 -0
  377. package/skills/superpowers/writing-skills/testing-skills-with-subagents.md +384 -0
  378. package/skills/sync/SKILL.md +188 -0
  379. package/skills/templates/SKILL.md +194 -0
  380. package/skills/welcome/SKILL.md +136 -0
  381. package/skills/wizard/SKILL.md +411 -0
  382. package/templates/CLAUDE.md.managed-block +123 -0
  383. package/templates/CLAUDE.md.template +111 -0
  384. package/templates/consulting/engagement-tracker-template.md +181 -0
  385. package/templates/consulting/executive-summary-template.md +83 -0
  386. package/templates/consulting/maturity-assessment-template.md +182 -0
  387. package/templates/consulting/proposal-template.md +209 -0
  388. package/templates/consulting/roi-model-template.md +139 -0
  389. package/templates/consulting/solution-architecture-template.md +313 -0
  390. package/templates/settings.json +130 -0
package/install.sh ADDED
@@ -0,0 +1,3185 @@
1
+ #!/bin/bash
2
+ # Claude Agents — Symlink Install System
3
+ #
4
+ # Installs portable agents, skills, and hooks into any project's .claude/
5
+ # directory using symlinks. Changes to the repo propagate automatically.
6
+ #
7
+ # Usage:
8
+ # install.sh [path] Sync symlinks into project (auto-detects setup needs)
9
+ # install.sh --setup [path] Interactive setup (pick categories, configure hooks)
10
+ # install.sh --uninstall [path] Clean uninstall (restore everything)
11
+ # install.sh --sync-from-config [path] Sync only configured categories (used by hook)
12
+ # install.sh --init [path] Scaffold + sync (greenfield)
13
+ # install.sh --convert [path] One-time migration: replace matching copies with symlinks
14
+ # install.sh --assess [path] Brownfield assessment (read-only, no license needed)
15
+ # install.sh --upgrade [path] Brownfield upgrade (converts stale/identical to symlinks)
16
+ # install.sh --generate-config [path] Generate config from existing .claude/ contents
17
+ # install.sh --status [path] Show symlink status report
18
+ # install.sh --key KEY Store license key
19
+ # install.sh --railway Include Railway sub-skills
20
+ # install.sh --categories cat1,cat2 Install specific categories (non-interactive)
21
+ # install.sh --json-output Emit JSON summary on completion
22
+ # install.sh --check-license-only Validate license and exit (used by sync hook)
23
+ # install.sh --help Show help
24
+
25
+ set -euo pipefail
26
+
27
+ # CLAUDE_AGENTS_TOOLKIT_DIR can override SCRIPT_DIR (used by test harness in CI
28
+ # where ~/.claude-agents is a symlink and BASH_SOURCE resolves to physical path)
29
+ SCRIPT_DIR="${CLAUDE_AGENTS_TOOLKIT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)}"
30
+ MANIFEST="$SCRIPT_DIR/portable.manifest"
31
+ LICENSE_FILE="$SCRIPT_DIR/.license"
32
+ AUTHORIZED_KEYS="$SCRIPT_DIR/authorized-keys.txt"
33
+
34
+ # Config & backup constants
35
+ CONFIG_NAME=".claude-agents.conf"
36
+ BACKUP_DIR_NAME=".claude-agents-backup"
37
+ GITIGNORE_MARKER_START="# >>> claude-agents managed (DO NOT EDIT THIS BLOCK) >>>"
38
+ GITIGNORE_MARKER_END="# <<< claude-agents managed <<<"
39
+ CLAUDEMD_MARKER_START="<!-- >>> claude-agents toolkit (DO NOT EDIT THIS BLOCK) >>> -->"
40
+ CLAUDEMD_MARKER_END="<!-- <<< claude-agents toolkit <<< -->"
41
+ CLAUDEMD_MANAGED_TEMPLATE="$SCRIPT_DIR/templates/CLAUDE.md.managed-block"
42
+
43
+ # Colors
44
+ RED='\033[0;31m'
45
+ GREEN='\033[0;32m'
46
+ YELLOW='\033[0;33m'
47
+ CYAN='\033[0;36m'
48
+ BOLD='\033[1m'
49
+ DIM='\033[2m'
50
+ NC='\033[0m' # No Color
51
+
52
+ # Counters
53
+ CREATED=0
54
+ UPDATED=0
55
+ SKIPPED=0
56
+ CONVERTED=0
57
+ UPGRADED=0
58
+ ERRORS=0
59
+ REMOVED=0
60
+ KEPT=0
61
+ STALE_DEFERRED=()
62
+ SKIP_ITEMS=""
63
+ FORCE_YES=false
64
+
65
+ # ============================================================================
66
+ # License Validation
67
+ # ============================================================================
68
+
69
+ check_license() {
70
+ if [ ! -f "$LICENSE_FILE" ]; then
71
+ echo -e "${RED}No license key found.${NC}"
72
+ echo "Run: $0 --key YOUR_KEY"
73
+ echo ""
74
+ echo "Get a license key at https://github.com/ArthTech-AI/claude-agents or contact support."
75
+ exit 1
76
+ fi
77
+
78
+ local key
79
+ key=$(cat "$LICENSE_FILE")
80
+
81
+ # Validate key format: ARTH-XXXX-XXXX-XXXX-XXXX
82
+ if ! echo "$key" | grep -qE '^ARTH-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'; then
83
+ echo -e "${RED}Invalid license key format.${NC} Expected: ARTH-XXXX-XXXX-XXXX-XXXX"
84
+ exit 1
85
+ fi
86
+
87
+ # Phase 1: Check against local hashed allowlist
88
+ if [ ! -f "$AUTHORIZED_KEYS" ]; then
89
+ echo -e "${RED}Authorization file missing.${NC} Re-clone the repo."
90
+ exit 1
91
+ fi
92
+
93
+ local key_hash
94
+ key_hash=$(echo -n "$key" | shasum -a 256 | awk '{print $1}')
95
+
96
+ if ! grep -v '^#' "$AUTHORIZED_KEYS" | grep -q "$key_hash"; then
97
+ echo -e "${RED}Invalid license key.${NC} Contact support."
98
+ exit 1
99
+ fi
100
+ }
101
+
102
+ # ============================================================================
103
+ # Category Definitions
104
+ # ============================================================================
105
+ # Maps categories to their manifest entry paths.
106
+ # Categories: core, agents, skills, hooks, railway
107
+
108
+ get_category_items() {
109
+ case "$1" in
110
+ core)
111
+ echo "agents/explore-light.md skills/sync skills/scan skills/calibrate hooks/sync-agents.sh"
112
+ ;;
113
+ strategy)
114
+ echo "agents/architect.md agents/code-reviewer.md agents/design-studio-create.md agents/design-studio-critique.md agents/design-studio-think.md agents/gtm-expert.md agents/product-manager.md agents/stakeholder-reporter.md agents/meeting-prep.md agents/content-strategist.md agents/user-researcher.md agents/competitive-analyst.md"
115
+ ;;
116
+ development)
117
+ echo "agents/frontend.md agents/python-backend.md agents/ops.md skills/planning skills/implement skills/fix skills/pr skills/precheck skills/review-pr skills/issue"
118
+ ;;
119
+ quality)
120
+ echo "agents/qa.md agents/qa-e2e.md agents/qa-challenger.md agents/qa-test-promoter.md agents/qa-baseline-updater.md agents/qa-domain.md skills/qa skills/ci-fix skills/qa-incident skills/qa-learn"
121
+ ;;
122
+ operations)
123
+ echo "agents/sre.md agents/setup.md skills/sre skills/incident skills/setup skills/restart skills/discord-ops skills/arth skills/custom-domain"
124
+ ;;
125
+ hooks)
126
+ echo "hooks/triage-router.sh hooks/session-end.sh"
127
+ ;;
128
+ guardrails)
129
+ echo "hooks/session-bootstrap.sh hooks/pre-bash-guard.sh hooks/pre-task-context.sh hooks/pre-edit-guard.sh hooks/post-test-summary.sh hooks/post-edit-lint.sh hooks/post-deploy-health.sh hooks/post-diff-test-compare.sh hooks/post-git-state.sh hooks/post-merge-cleanup.sh skills/onboard skills/welcome skills/share skills/templates skills/wizard skills/autopilot skills/continue"
130
+ ;;
131
+ railway)
132
+ echo "skills/railway/central-station skills/railway/database skills/railway/deploy skills/railway/deployment skills/railway/domain skills/railway/environment skills/railway/metrics skills/railway/new skills/railway/projects skills/railway/railway-docs skills/railway/service skills/railway/status skills/railway/templates"
133
+ ;;
134
+ superpowers)
135
+ echo "skills/superpowers skills/explore.md"
136
+ ;;
137
+ consulting)
138
+ echo "agents/ai-consultant.md skills/consulting skills/client-discovery skills/opportunity-map skills/pitch-generator skills/roi-calculator skills/market-research skills/solution-architect skills/deliverable-builder skills/engagement-tracker hooks/ensure-client-dir.sh hooks/check-deliverable.sh hooks/session-summary.sh"
139
+ ;;
140
+ esac
141
+ }
142
+
143
+ get_category_label() {
144
+ case "$1" in
145
+ core) echo "Core (required)" ;;
146
+ strategy) echo "Strategy & Design (12 agents)" ;;
147
+ development) echo "Development (3 agents, 6 skills)" ;;
148
+ quality) echo "Quality Assurance (6 agents, 4 skills)" ;;
149
+ operations) echo "Operations (2 agents, 5 skills)" ;;
150
+ hooks) echo "Hooks (3)" ;;
151
+ guardrails) echo "Guardrails (8 hooks, 7 skills)" ;;
152
+ railway) echo "Railway (13 skills, 1 hook)" ;;
153
+ superpowers) echo "Superpowers (2 skills)" ;;
154
+ consulting) echo "Consulting (1 agent, 9 skills, 3 hooks)" ;;
155
+ esac
156
+ }
157
+
158
+ get_category_desc() {
159
+ case "$1" in
160
+ core) echo "Sync, codebase scanner, exploration, auto-update hook" ;;
161
+ strategy) echo "Architect, code-review, design-studio, PM, GTM, competitive-analyst, stakeholder-reporter, meeting-prep, content-strategist, user-researcher agents" ;;
162
+ development) echo "Frontend, backend, ops agents + planning, implement, fix, PR, review-pr, issue skills" ;;
163
+ quality) echo "QA orchestrator, E2E, challenger, promoter, baseline, domain + QA skills" ;;
164
+ operations) echo "SRE + setup agents, SRE, setup, restart, discord-ops, arth, custom-domain skills" ;;
165
+ hooks) echo "Triage router (cost delegation) + git worktree sync + session-end memory" ;;
166
+ guardrails) echo "Safety guards, auto-lint, test summaries, deploy checks, onboard, welcome, share, templates, wizard, autopilot, continue" ;;
167
+ railway) echo "Railway deployment skills (deploy, database, domain, etc.)" ;;
168
+ superpowers) echo "Superpowers + deep explore skills" ;;
169
+ consulting) echo "AI consulting agent, client discovery, opportunity mapping, pitch generation, ROI calculator" ;;
170
+ esac
171
+ }
172
+
173
+ get_category_default() {
174
+ case "$1" in
175
+ guardrails) echo "1" ;;
176
+ railway) echo "0" ;;
177
+ superpowers) echo "0" ;;
178
+ consulting) echo "0" ;;
179
+ *) echo "1" ;;
180
+ esac
181
+ }
182
+
183
+ # Returns the settings.json hook names that correspond to a category
184
+ get_category_hooks() {
185
+ case "$1" in
186
+ core) echo "sync-agents" ;;
187
+ hooks) echo "triage-router session-end" ;;
188
+ guardrails) echo "session-bootstrap pre-bash-guard pre-task-context pre-edit-guard pre-write-guard post-test-summary post-edit-lint post-deploy-health post-diff-test-compare post-merge-cleanup" ;;
189
+ *) echo "" ;;
190
+ esac
191
+ }
192
+
193
+ # Build combined item list for a space-separated list of categories
194
+ items_for_categories() {
195
+ local categories="$1"
196
+ local all_items=""
197
+ for cat in $categories; do
198
+ local items
199
+ items=$(get_category_items "$cat")
200
+ all_items="$all_items $items"
201
+ done
202
+ echo "$all_items" | sed 's/^ *//' | sed 's/ *$//'
203
+ }
204
+
205
+ # Build combined settings hook list for categories
206
+ hooks_for_categories() {
207
+ local categories="$1"
208
+ local all_hooks=""
209
+ for cat in $categories; do
210
+ local h
211
+ h=$(get_category_hooks "$cat")
212
+ if [ -n "$h" ]; then
213
+ all_hooks="$all_hooks $h"
214
+ fi
215
+ done
216
+ echo "$all_hooks" | sed 's/^ *//' | sed 's/ *$//'
217
+ }
218
+
219
+ # ============================================================================
220
+ # Config System
221
+ # ============================================================================
222
+
223
+ config_path() {
224
+ echo "$1/.claude/$CONFIG_NAME"
225
+ }
226
+
227
+ config_exists() {
228
+ [ -f "$(config_path "$1")" ]
229
+ }
230
+
231
+ # Sources config into CONF_* variables
232
+ load_config() {
233
+ local conf_file
234
+ conf_file=$(config_path "$1")
235
+ if [ ! -f "$conf_file" ]; then
236
+ return 1
237
+ fi
238
+ # Source the file — sets INSTALLED_CATEGORIES, INSTALLED_AGENTS, etc.
239
+ # shellcheck disable=SC1090
240
+ . "$conf_file"
241
+ # Export as CONF_ prefixed for clarity
242
+ CONF_INSTALLED_CATEGORIES="${INSTALLED_CATEGORIES:-}"
243
+ CONF_INSTALLED_AGENTS="${INSTALLED_AGENTS:-}"
244
+ CONF_INSTALLED_SKILLS="${INSTALLED_SKILLS:-}"
245
+ CONF_INSTALLED_HOOKS="${INSTALLED_HOOKS:-}"
246
+ CONF_INSTALLED_RAILWAY="${INSTALLED_RAILWAY:-}"
247
+ CONF_SETTINGS_HOOKS_ADDED="${SETTINGS_HOOKS_ADDED:-}"
248
+ CONF_BACKUP_EXISTS="${BACKUP_EXISTS:-false}"
249
+ }
250
+
251
+ write_config() {
252
+ local target_dir="$1"
253
+ local categories="$2" # space-separated
254
+ local conf_file
255
+ conf_file=$(config_path "$target_dir")
256
+
257
+ # Derive individual lists from categories
258
+ local agents="" skills="" hooks_list="" railway=""
259
+ for cat in $categories; do
260
+ local items
261
+ items=$(get_category_items "$cat")
262
+ for item in $items; do
263
+ local bname
264
+ bname=$(basename "$item")
265
+ bname="${bname%.md}"
266
+ bname="${bname%.sh}"
267
+ case "$item" in
268
+ agents/*)
269
+ agents="$agents $bname"
270
+ ;;
271
+ skills/railway/*)
272
+ railway="$railway $bname"
273
+ ;;
274
+ skills/*)
275
+ skills="$skills $bname"
276
+ ;;
277
+ hooks/*)
278
+ hooks_list="$hooks_list $bname"
279
+ ;;
280
+ esac
281
+ done
282
+ done
283
+
284
+ # Trim leading spaces
285
+ agents="${agents# }"
286
+ skills="${skills# }"
287
+ hooks_list="${hooks_list# }"
288
+ railway="${railway# }"
289
+
290
+ # Settings hooks we manage
291
+ local settings_hooks
292
+ settings_hooks=$(hooks_for_categories "$categories")
293
+
294
+ # Backup status
295
+ local backup_exists="false"
296
+ if [ -d "$target_dir/.claude/$BACKUP_DIR_NAME" ]; then
297
+ backup_exists="true"
298
+ fi
299
+
300
+ cat > "$conf_file" << CONFEOF
301
+ # claude-agents project config — generated by install.sh
302
+ # Re-run: install.sh --setup $(basename "$target_dir")
303
+ INSTALLED_CATEGORIES="$categories"
304
+ INSTALLED_AGENTS="$agents"
305
+ INSTALLED_SKILLS="$skills"
306
+ INSTALLED_HOOKS="$hooks_list"
307
+ INSTALLED_RAILWAY="$railway"
308
+ INSTALLED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
309
+ SETTINGS_HOOKS_ADDED="$settings_hooks"
310
+ BACKUP_EXISTS=$backup_exists
311
+ CONFEOF
312
+ }
313
+
314
+ # ============================================================================
315
+ # JSON Output
316
+ # ============================================================================
317
+
318
+ emit_json_summary() {
319
+ local mode="${1:-sync}"
320
+ local target="${2:-}"
321
+ local conf_file
322
+ conf_file=$(config_path "$target")
323
+ local categories=""
324
+ if config_exists "$target"; then
325
+ load_config "$target"
326
+ categories="${CONF_INSTALLED_CATEGORIES:-}"
327
+ fi
328
+ printf '{"status":"success","mode":"%s","target":"%s","stats":{"created":%d,"updated":%d,"skipped":%d,"errors":%d},"categories":"%s","configPath":"%s"}\n' \
329
+ "$mode" "$target" "$CREATED" "$UPDATED" "$SKIPPED" "$ERRORS" "$categories" "$conf_file"
330
+ }
331
+
332
+ # ============================================================================
333
+ # Backup System
334
+ # ============================================================================
335
+
336
+ backup_dir() {
337
+ echo "$1/.claude/$BACKUP_DIR_NAME"
338
+ }
339
+
340
+ # Create backup snapshot — only on FIRST install (or if backup dir is missing)
341
+ snapshot_pre_install() {
342
+ local target_dir="$1"
343
+ local bdir
344
+ bdir=$(backup_dir "$target_dir")
345
+
346
+ if [ -d "$bdir" ]; then
347
+ return 0 # Backup already exists, don't overwrite
348
+ fi
349
+
350
+ mkdir -p "$bdir"
351
+
352
+ # Back up settings.json if it exists
353
+ if [ -f "$target_dir/.claude/settings.json" ]; then
354
+ cp "$target_dir/.claude/settings.json" "$bdir/settings.json.bak"
355
+ fi
356
+
357
+ # Back up .gitignore if it exists
358
+ if [ -f "$target_dir/.claude/.gitignore" ]; then
359
+ cp "$target_dir/.claude/.gitignore" "$bdir/gitignore.bak"
360
+ fi
361
+
362
+ echo -e " ${DIM}Backup created at .claude/$BACKUP_DIR_NAME/${NC}" >&2
363
+ }
364
+
365
+ # Record all symlinks we created (written AFTER install completes)
366
+ post_install_record() {
367
+ local target_dir="$1"
368
+ local bdir
369
+ bdir=$(backup_dir "$target_dir")
370
+ mkdir -p "$bdir"
371
+
372
+ local manifest_file="$bdir/manifest.txt"
373
+ : > "$manifest_file" # Truncate
374
+
375
+ # Walk .claude/ for symlinks pointing to our repo
376
+ for subdir in agents skills hooks; do
377
+ local search_dir="$target_dir/.claude/$subdir"
378
+ [ ! -d "$search_dir" ] && continue
379
+
380
+ # Find symlinks (non-recursive for agents/hooks, handle skill subdirs)
381
+ for item in "$search_dir"/*; do
382
+ [ ! -e "$item" ] && [ ! -L "$item" ] && continue
383
+ if [ -L "$item" ]; then
384
+ local link_target
385
+ link_target=$(readlink "$item" 2>/dev/null || true)
386
+ if echo "$link_target" | grep -q "/.claude-agents/"; then
387
+ echo "$item" >> "$manifest_file"
388
+ fi
389
+ fi
390
+ done
391
+ done
392
+ }
393
+
394
+ # ============================================================================
395
+ # Settings.json Merge (Phase 3)
396
+ # ============================================================================
397
+ # Uses embedded Python3 for safe JSON manipulation.
398
+ # Adds/removes hook entries without touching other settings.
399
+
400
+ merge_settings_hooks() {
401
+ local settings_file="$1"
402
+ local action="$2" # "add" or "remove"
403
+ local hooks_str="$3" # space-separated hook names: "sync-agents triage-router"
404
+
405
+ if [ -z "$hooks_str" ]; then
406
+ return 0
407
+ fi
408
+
409
+ if ! command -v python3 &>/dev/null; then
410
+ echo -e "${YELLOW}Warning: python3 not found. Cannot auto-merge hooks into settings.json.${NC}" >&2
411
+ echo -e "Please manually add/remove hooks in $settings_file" >&2
412
+ return 1
413
+ fi
414
+
415
+ python3 - "$settings_file" "$action" "$hooks_str" "$SCRIPT_DIR" <<'PYEOF'
416
+ import json, sys, os
417
+
418
+ settings_file = sys.argv[1]
419
+ action = sys.argv[2]
420
+ hooks_to_process = sys.argv[3].split()
421
+ script_dir = sys.argv[4] if len(sys.argv) > 4 else os.path.dirname(os.path.abspath(__file__))
422
+
423
+ # Load hook definitions from hook-defs.json (single source of truth)
424
+ # Translates entries-array format into the flat per-matcher HOOK_DEFS dict
425
+ # that install.sh's settings merge logic expects.
426
+ hook_defs_path = os.path.join(script_dir, "hook-defs.json")
427
+ _raw_defs = {}
428
+ if os.path.exists(hook_defs_path):
429
+ with open(hook_defs_path) as _f:
430
+ _raw_defs = json.load(_f)
431
+
432
+ HOOK_DEFS = {}
433
+ for _name, _defn in _raw_defs.items():
434
+ _script = _defn["script"]
435
+ _entries = _defn["entries"]
436
+ if _name == "sync-agents":
437
+ # sync-agents uses a special command_override (not $CLAUDE_PROJECT_DIR path)
438
+ _entry0 = _entries[0]
439
+ _cmd = _entry0.get("command_override",
440
+ "bash -c 'export HOME=$(eval echo ~$(id -un)); [ -x \"$HOME/.claude-agents/hooks/sync-agents.sh\" ] && \"$HOME/.claude-agents/hooks/sync-agents.sh\" || true'")
441
+ HOOK_DEFS["sync-agents"] = {
442
+ "event": _entry0["event"],
443
+ "entry": {"matcher": _entry0["matcher"], "hooks": [{"type": "command", "command": _cmd, "timeout": _entry0["timeout"]}]},
444
+ "fingerprint": _script,
445
+ }
446
+ elif len(_entries) == 1:
447
+ _e = _entries[0]
448
+ HOOK_DEFS[_name] = {
449
+ "event": _e["event"],
450
+ "entry": {"matcher": _e["matcher"], "hooks": [{"type": "command", "command": f"$CLAUDE_PROJECT_DIR/.claude/hooks/{_script}", "timeout": _e["timeout"]}]},
451
+ "fingerprint": _script,
452
+ }
453
+ else:
454
+ # Multi-entry hook (e.g. pre-edit-guard fires on both Edit and Write).
455
+ # Expand into separate HOOK_DEFS keys so each matcher gets its own entry.
456
+ for _i, _e in enumerate(_entries):
457
+ _key = _name if _i == 0 else f"{_name.rsplit('-', 1)[0]}-write-guard" if _e["matcher"] == "Write" else f"{_name}-{_i}"
458
+ HOOK_DEFS[_key] = {
459
+ "event": _e["event"],
460
+ "entry": {"matcher": _e["matcher"], "hooks": [{"type": "command", "command": f"$CLAUDE_PROJECT_DIR/.claude/hooks/{_script}", "timeout": _e["timeout"]}]},
461
+ "fingerprint": _script,
462
+ }
463
+
464
+ # Fallback: if hook-defs.json missing, keep hardcoded sync-agents for safety
465
+ if not HOOK_DEFS:
466
+ HOOK_DEFS = {
467
+ "sync-agents": {
468
+ "event": "SessionStart",
469
+ "entry": {"matcher": "", "hooks": [{"type": "command", "command": "bash -c 'export HOME=$(eval echo ~$(id -un)); [ -x \"$HOME/.claude-agents/hooks/sync-agents.sh\" ] && \"$HOME/.claude-agents/hooks/sync-agents.sh\" || true'", "timeout": 30}]},
470
+ "fingerprint": "sync-agents.sh",
471
+ }
472
+ }
473
+
474
+ # Read or create settings
475
+ if os.path.exists(settings_file):
476
+ with open(settings_file) as f:
477
+ try:
478
+ settings = json.load(f)
479
+ except json.JSONDecodeError:
480
+ settings = {}
481
+ else:
482
+ settings = {}
483
+
484
+ if "hooks" not in settings:
485
+ settings["hooks"] = {}
486
+
487
+ for hook_name in hooks_to_process:
488
+ if hook_name not in HOOK_DEFS:
489
+ continue
490
+ defn = HOOK_DEFS[hook_name]
491
+ event = defn["event"]
492
+ fingerprint = defn["fingerprint"]
493
+
494
+ if action == "add":
495
+ if event not in settings["hooks"]:
496
+ settings["hooks"][event] = []
497
+ # Check if already present (by fingerprint AND matcher to avoid false dedup)
498
+ entry_matcher = defn["entry"].get("matcher", "")
499
+ already_present = False
500
+ for entry in settings["hooks"][event]:
501
+ if entry.get("matcher", "") != entry_matcher:
502
+ continue
503
+ for h in entry.get("hooks", []):
504
+ if fingerprint in h.get("command", ""):
505
+ already_present = True
506
+ break
507
+ if already_present:
508
+ break
509
+ if not already_present:
510
+ settings["hooks"][event].append(defn["entry"])
511
+
512
+ elif action == "remove":
513
+ if event in settings["hooks"]:
514
+ entry_matcher = defn["entry"].get("matcher", "")
515
+ settings["hooks"][event] = [
516
+ entry for entry in settings["hooks"][event]
517
+ if not (
518
+ entry.get("matcher", "") == entry_matcher and
519
+ any(fingerprint in h.get("command", "") for h in entry.get("hooks", []))
520
+ )
521
+ ]
522
+ if not settings["hooks"][event]:
523
+ del settings["hooks"][event]
524
+
525
+ # Clean up empty hooks dict
526
+ if not settings.get("hooks"):
527
+ if "hooks" in settings:
528
+ del settings["hooks"]
529
+
530
+ with open(settings_file, "w") as f:
531
+ json.dump(settings, f, indent=2)
532
+ f.write("\n")
533
+ PYEOF
534
+ }
535
+
536
+ # ============================================================================
537
+ # Gitignore Management
538
+ # ============================================================================
539
+ # Maintains a marker block in .claude/.gitignore for our symlinks.
540
+ # User entries outside the block are never touched.
541
+
542
+ update_gitignore_block() {
543
+ local target_dir="$1"
544
+ local items="$2" # space-separated list of manifest entry paths
545
+ local gitignore="$target_dir/.claude/.gitignore"
546
+
547
+ # Build the block content
548
+ local block=""
549
+ block="$GITIGNORE_MARKER_START"$'\n'
550
+
551
+ for item in $items; do
552
+ local bname
553
+ bname=$(basename "$item")
554
+ case "$item" in
555
+ agents/*) block="${block}agents/$bname"$'\n' ;;
556
+ skills/railway/*) block="${block}skills/$bname"$'\n' ;;
557
+ skills/*) block="${block}skills/$bname"$'\n' ;;
558
+ hooks/*) block="${block}hooks/$bname"$'\n' ;;
559
+ esac
560
+ done
561
+
562
+ # Always gitignore our config and backup
563
+ block="${block}$CONFIG_NAME"$'\n'
564
+ block="${block}$BACKUP_DIR_NAME/"$'\n'
565
+ block="$block$GITIGNORE_MARKER_END"
566
+
567
+ if [ -f "$gitignore" ]; then
568
+ # Remove existing block if present, then append new one
569
+ local tmp
570
+ tmp=$(mktemp)
571
+ local in_block=false
572
+ while IFS= read -r line; do
573
+ if [ "$line" = "$GITIGNORE_MARKER_START" ]; then
574
+ in_block=true
575
+ continue
576
+ fi
577
+ if [ "$line" = "$GITIGNORE_MARKER_END" ]; then
578
+ in_block=false
579
+ continue
580
+ fi
581
+ if ! $in_block; then
582
+ echo "$line" >> "$tmp"
583
+ fi
584
+ done < "$gitignore"
585
+ # Append our block
586
+ echo "" >> "$tmp"
587
+ echo "$block" >> "$tmp"
588
+ mv "$tmp" "$gitignore"
589
+ else
590
+ # Create new file with just our block
591
+ echo "$block" > "$gitignore"
592
+ fi
593
+ }
594
+
595
+ remove_gitignore_block() {
596
+ local target_dir="$1"
597
+ local gitignore="$target_dir/.claude/.gitignore"
598
+
599
+ if [ ! -f "$gitignore" ]; then
600
+ return 0
601
+ fi
602
+
603
+ local tmp
604
+ tmp=$(mktemp)
605
+ local in_block=false
606
+ while IFS= read -r line; do
607
+ if [ "$line" = "$GITIGNORE_MARKER_START" ]; then
608
+ in_block=true
609
+ continue
610
+ fi
611
+ if [ "$line" = "$GITIGNORE_MARKER_END" ]; then
612
+ in_block=false
613
+ continue
614
+ fi
615
+ if ! $in_block; then
616
+ echo "$line" >> "$tmp"
617
+ fi
618
+ done < "$gitignore"
619
+
620
+ # Remove trailing blank lines
621
+ sed -e :a -e '/^[[:space:]]*$/{ $d; N; ba; }' "$tmp" > "$gitignore" 2>/dev/null || mv "$tmp" "$gitignore"
622
+ rm -f "$tmp"
623
+
624
+ # If gitignore is now empty, remove it
625
+ if [ ! -s "$gitignore" ]; then
626
+ rm -f "$gitignore"
627
+ fi
628
+ }
629
+
630
+ # ============================================================================
631
+ # CLAUDE.md Managed Block (toolkit instructions that auto-update)
632
+ # ============================================================================
633
+
634
+ update_claudemd_block() {
635
+ local target_dir="$1"
636
+ local mode="${2:-silent}" # "silent" = update existing block only, "ask" = offer to inject
637
+ local claudemd="$target_dir/CLAUDE.md"
638
+
639
+ # No template? Nothing to do.
640
+ if [ ! -f "$CLAUDEMD_MANAGED_TEMPLATE" ]; then
641
+ return 0
642
+ fi
643
+
644
+ # No CLAUDE.md? Nothing to do. (--init creates it separately)
645
+ if [ ! -f "$claudemd" ]; then
646
+ return 0
647
+ fi
648
+
649
+ # Build the block content (markers + template)
650
+ local block_content
651
+ block_content="$CLAUDEMD_MARKER_START"$'\n'
652
+ block_content+=$(cat "$CLAUDEMD_MANAGED_TEMPLATE")
653
+ block_content+=$'\n'"$CLAUDEMD_MARKER_END"
654
+
655
+ # Check if markers already exist
656
+ if grep -qF "$CLAUDEMD_MARKER_START" "$claudemd" 2>/dev/null; then
657
+ # Markers exist — replace the block in-place
658
+ local tmp
659
+ tmp=$(mktemp)
660
+ local in_block=false
661
+ local replaced=false
662
+ while IFS= read -r line; do
663
+ if [ "$line" = "$CLAUDEMD_MARKER_START" ]; then
664
+ in_block=true
665
+ if ! $replaced; then
666
+ echo "$block_content" >> "$tmp"
667
+ replaced=true
668
+ fi
669
+ continue
670
+ fi
671
+ if $in_block && [ "$line" = "$CLAUDEMD_MARKER_END" ]; then
672
+ in_block=false
673
+ continue
674
+ fi
675
+ if ! $in_block; then
676
+ echo "$line" >> "$tmp"
677
+ fi
678
+ done < "$claudemd"
679
+ mv "$tmp" "$claudemd"
680
+ return 0
681
+ fi
682
+
683
+ # No markers yet — behavior depends on mode
684
+ if [ "$mode" = "silent" ]; then
685
+ # Silent mode (sync): don't inject into unmarked files
686
+ return 0
687
+ fi
688
+
689
+ # Ask mode (upgrade/setup): offer to inject
690
+ echo ""
691
+ echo -e " ${BOLD}CLAUDE.md toolkit instructions:${NC}"
692
+ echo -e " Your CLAUDE.md doesn't have toolkit instructions yet."
693
+ echo -e " These tell Claude how to use the toolkit (routing, session start, delegation)."
694
+ echo -e " They go in a managed block — your content won't be touched."
695
+ echo ""
696
+
697
+ if [ "$FORCE_YES" = "true" ]; then
698
+ echo -e " Adding toolkit instructions to CLAUDE.md (--yes)"
699
+ else
700
+ echo -n " Add toolkit instructions to CLAUDE.md? [y/N] "
701
+ local answer
702
+ read -r answer < /dev/tty 2>/dev/null || read -r answer
703
+ case "$answer" in
704
+ y|Y) ;;
705
+ *)
706
+ echo -e " ${DIM}Skipped — run --upgrade again to add later${NC}"
707
+ return 0
708
+ ;;
709
+ esac
710
+ fi
711
+
712
+ # Inject after the first line (the # heading)
713
+ local tmp
714
+ tmp=$(mktemp)
715
+ local first_line=true
716
+ while IFS= read -r line; do
717
+ echo "$line" >> "$tmp"
718
+ if $first_line; then
719
+ first_line=false
720
+ echo "" >> "$tmp"
721
+ echo "$block_content" >> "$tmp"
722
+ fi
723
+ done < "$claudemd"
724
+ mv "$tmp" "$claudemd"
725
+ echo -e " ${GREEN}✓${NC} Added toolkit instructions to CLAUDE.md"
726
+ }
727
+
728
+ remove_claudemd_block() {
729
+ local target_dir="$1"
730
+ local claudemd="$target_dir/CLAUDE.md"
731
+
732
+ if [ ! -f "$claudemd" ]; then
733
+ return 0
734
+ fi
735
+
736
+ # No markers? Nothing to remove.
737
+ if ! grep -qF "$CLAUDEMD_MARKER_START" "$claudemd" 2>/dev/null; then
738
+ return 0
739
+ fi
740
+
741
+ local tmp
742
+ tmp=$(mktemp)
743
+ local in_block=false
744
+ local prev_blank=false
745
+ while IFS= read -r line; do
746
+ if [ "$line" = "$CLAUDEMD_MARKER_START" ]; then
747
+ in_block=true
748
+ continue
749
+ fi
750
+ if [ "$line" = "$CLAUDEMD_MARKER_END" ]; then
751
+ in_block=false
752
+ # Skip the blank line after the block too
753
+ prev_blank=true
754
+ continue
755
+ fi
756
+ if ! $in_block; then
757
+ # Skip one blank line right after block removal
758
+ if $prev_blank && [ -z "$line" ]; then
759
+ prev_blank=false
760
+ continue
761
+ fi
762
+ prev_blank=false
763
+ echo "$line" >> "$tmp"
764
+ fi
765
+ done < "$claudemd"
766
+ mv "$tmp" "$claudemd"
767
+ echo -e " CLAUDE.md: ${GREEN}removed toolkit instructions block${NC}"
768
+ }
769
+
770
+ # ============================================================================
771
+ # Project State Detection
772
+ # ============================================================================
773
+ # Detects the state of the target project to tailor the setup experience.
774
+ # Returns: "greenfield" | "minimal" | "established"
775
+ #
776
+ # greenfield — no .claude/ dir, or .claude/ exists but is mostly empty
777
+ # minimal — .claude/ exists with settings.json/CLAUDE.md but few agents/skills
778
+ # established — .claude/ exists with significant existing agents/skills/hooks
779
+
780
+ detect_project_state() {
781
+ local target_dir="$1"
782
+
783
+ # No .claude dir at all → greenfield
784
+ if [ ! -d "$target_dir/.claude" ]; then
785
+ echo "greenfield"
786
+ return
787
+ fi
788
+
789
+ # Count existing non-symlink agents, skills, hooks
790
+ local agent_count=0
791
+ local skill_count=0
792
+ local hook_count=0
793
+
794
+ if [ -d "$target_dir/.claude/agents" ]; then
795
+ for f in "$target_dir/.claude/agents"/*.md; do
796
+ [ ! -f "$f" ] && continue
797
+ [ -L "$f" ] && continue
798
+ agent_count=$((agent_count + 1))
799
+ done
800
+ fi
801
+
802
+ if [ -d "$target_dir/.claude/skills" ]; then
803
+ for d in "$target_dir/.claude/skills"/*/; do
804
+ [ ! -d "$d" ] && continue
805
+ [ -L "${d%/}" ] && continue
806
+ skill_count=$((skill_count + 1))
807
+ done
808
+ # Also count single-file skills
809
+ for f in "$target_dir/.claude/skills"/*.md; do
810
+ [ ! -f "$f" ] && continue
811
+ [ -L "$f" ] && continue
812
+ skill_count=$((skill_count + 1))
813
+ done
814
+ fi
815
+
816
+ if [ -d "$target_dir/.claude/hooks" ]; then
817
+ for f in "$target_dir/.claude/hooks"/*.sh; do
818
+ [ ! -f "$f" ] && continue
819
+ [ -L "$f" ] && continue
820
+ hook_count=$((hook_count + 1))
821
+ done
822
+ fi
823
+
824
+ local total=$((agent_count + skill_count + hook_count))
825
+
826
+ # Has settings.json but very few custom tools → minimal
827
+ if [ "$total" -le 2 ]; then
828
+ if [ -f "$target_dir/.claude/settings.json" ] || [ -f "$target_dir/CLAUDE.md" ]; then
829
+ echo "minimal"
830
+ else
831
+ echo "greenfield"
832
+ fi
833
+ return
834
+ fi
835
+
836
+ # Significant existing setup
837
+ echo "established"
838
+ }
839
+
840
+ # ============================================================================
841
+ # Interactive Menu
842
+ # ============================================================================
843
+ # Pure bash interactive category selector.
844
+ # All UI output goes to stderr; result (selected categories) goes to stdout.
845
+
846
+ show_interactive_menu() {
847
+ local target_dir="$1"
848
+ local preselected="$2" # space-separated category names, or "" for defaults
849
+ local project_state="$3" # greenfield | minimal | established
850
+
851
+ local num_categories=9
852
+ local current=0
853
+
854
+ # Category names (indexed array — bash 3+ compatible)
855
+ # Note: 'consulting' is a hidden category — not shown in menu.
856
+ # Install via: INSTALLED_CATEGORIES="... consulting" in .claude-agents.conf
857
+ local cat_names_0="core"
858
+ local cat_names_1="strategy"
859
+ local cat_names_2="development"
860
+ local cat_names_3="quality"
861
+ local cat_names_4="operations"
862
+ local cat_names_5="hooks"
863
+ local cat_names_6="guardrails"
864
+ local cat_names_7="railway"
865
+ local cat_names_8="superpowers"
866
+
867
+ # Initialize selections from defaults
868
+ local sel_0=1 sel_1=1 sel_2=1 sel_3=1 sel_4=1 sel_5=1 sel_6=1 sel_7=0 sel_8=0
869
+
870
+ # Pre-populate from existing config if provided
871
+ if [ -n "$preselected" ]; then
872
+ sel_0=0; sel_1=0; sel_2=0; sel_3=0; sel_4=0; sel_5=0; sel_6=0; sel_7=0; sel_8=0
873
+ # Hidden categories (consulting) are preserved if already in config
874
+ local _hidden_cats=""
875
+ for cat in $preselected; do
876
+ case "$cat" in
877
+ core) sel_0=1 ;;
878
+ strategy) sel_1=1 ;;
879
+ development) sel_2=1 ;;
880
+ quality) sel_3=1 ;;
881
+ operations) sel_4=1 ;;
882
+ hooks) sel_5=1 ;;
883
+ guardrails) sel_6=1 ;;
884
+ railway) sel_7=1 ;;
885
+ superpowers) sel_8=1 ;;
886
+ consulting) _hidden_cats="$_hidden_cats consulting" ;;
887
+ esac
888
+ done
889
+ fi
890
+
891
+ # Hide cursor
892
+ printf '\033[?25l' >&2
893
+
894
+ # Ensure cursor is restored on exit/interrupt
895
+ trap 'printf "\033[?25h" >&2' EXIT INT TERM
896
+
897
+ # Header
898
+ echo "" >&2
899
+ echo -e "${BOLD}Claude Agents — Setup${NC}" >&2
900
+ echo -e "Target: ${CYAN}$target_dir${NC}" >&2
901
+ echo "" >&2
902
+
903
+ # Show project state context
904
+ case "$project_state" in
905
+ greenfield)
906
+ echo -e "${GREEN}Fresh project detected.${NC} We'll set up the full toolkit — agents, skills," >&2
907
+ echo -e "hooks, and workflows — so Claude Code works optimally from day one." >&2
908
+ echo "" >&2
909
+ ;;
910
+ minimal)
911
+ echo -e "${CYAN}Existing project with basic Claude Code setup.${NC} We'll add our agents" >&2
912
+ echo -e "and skills alongside your existing configuration. Your CLAUDE.md and" >&2
913
+ echo -e "settings stay untouched — we only add, never replace." >&2
914
+ echo "" >&2
915
+ # Count and show what they have
916
+ local their_agents=0 their_skills=0 their_hooks=0
917
+ if [ -d "$target_dir/.claude/agents" ]; then
918
+ for f in "$target_dir/.claude/agents"/*.md; do
919
+ [ -f "$f" ] && [ ! -L "$f" ] && their_agents=$((their_agents + 1))
920
+ done
921
+ fi
922
+ if [ -d "$target_dir/.claude/skills" ]; then
923
+ for d in "$target_dir/.claude/skills"/*/; do
924
+ [ -d "$d" ] && [ ! -L "${d%/}" ] && their_skills=$((their_skills + 1))
925
+ done
926
+ fi
927
+ if [ -d "$target_dir/.claude/hooks" ]; then
928
+ for f in "$target_dir/.claude/hooks"/*.sh; do
929
+ [ -f "$f" ] && [ ! -L "$f" ] && their_hooks=$((their_hooks + 1))
930
+ done
931
+ fi
932
+ if [ $((their_agents + their_skills + their_hooks)) -gt 0 ]; then
933
+ echo -e " ${DIM}Your existing tools: ${their_agents} agents, ${their_skills} skills, ${their_hooks} hooks${NC}" >&2
934
+ echo -e " ${DIM}These will be auto-discovered by the triage router and work${NC}" >&2
935
+ echo -e " ${DIM}alongside our portable tools — better together.${NC}" >&2
936
+ echo "" >&2
937
+ fi
938
+ ;;
939
+ established)
940
+ echo -e "${YELLOW}Established Claude Code project detected.${NC} Your existing agents," >&2
941
+ echo -e "skills, and hooks are preserved. We symlink our tools alongside yours —" >&2
942
+ echo -e "the triage router auto-discovers everything and routes optimally." >&2
943
+ echo "" >&2
944
+ ;;
945
+ esac
946
+
947
+ echo "Select categories to install (arrows to move, space to toggle, enter to confirm):" >&2
948
+ echo "" >&2
949
+
950
+ while true; do
951
+ # Render each category line
952
+ local i=0
953
+ while [ "$i" -lt "$num_categories" ]; do
954
+ local prefix=" "
955
+ if [ "$i" -eq "$current" ]; then
956
+ prefix="> "
957
+ fi
958
+
959
+ # Get selection state
960
+ local sel_val=0
961
+ case "$i" in
962
+ 0) sel_val=$sel_0 ;; 1) sel_val=$sel_1 ;; 2) sel_val=$sel_2 ;;
963
+ 3) sel_val=$sel_3 ;; 4) sel_val=$sel_4 ;; 5) sel_val=$sel_5 ;;
964
+ 6) sel_val=$sel_6 ;; 7) sel_val=$sel_7 ;; 8) sel_val=$sel_8 ;;
965
+ esac
966
+
967
+ local checkbox="[ ]"
968
+ if [ "$sel_val" -eq 1 ]; then
969
+ checkbox="[x]"
970
+ fi
971
+ # Core is required — show locked indicator
972
+ if [ "$i" -eq 0 ]; then
973
+ checkbox="[x]" # Always checked, cannot toggle
974
+ fi
975
+
976
+ # Get label and description
977
+ local cat_name=""
978
+ case "$i" in
979
+ 0) cat_name="core" ;; 1) cat_name="strategy" ;; 2) cat_name="development" ;;
980
+ 3) cat_name="quality" ;; 4) cat_name="operations" ;; 5) cat_name="hooks" ;;
981
+ 6) cat_name="guardrails" ;; 7) cat_name="railway" ;; 8) cat_name="superpowers" ;;
982
+ esac
983
+
984
+ local label desc
985
+ label=$(get_category_label "$cat_name")
986
+ desc=$(get_category_desc "$cat_name")
987
+
988
+ # Highlight current row
989
+ if [ "$i" -eq "$current" ]; then
990
+ printf '\033[K' >&2
991
+ printf "${BOLD}%s %s %-22s${NC} %s\n" "$prefix" "$checkbox" "$label" "$desc" >&2
992
+ else
993
+ printf '\033[K' >&2
994
+ printf "%s %s %-22s ${DIM}%s${NC}\n" "$prefix" "$checkbox" "$label" "$desc" >&2
995
+ fi
996
+
997
+ i=$((i + 1))
998
+ done
999
+
1000
+ # Read key
1001
+ IFS= read -rsn1 key < /dev/tty 2>/dev/null || IFS= read -rsn1 key
1002
+
1003
+ if [ "$key" = $'\x1b' ]; then
1004
+ IFS= read -rsn2 key < /dev/tty 2>/dev/null || IFS= read -rsn2 key
1005
+ case "$key" in
1006
+ '[A') # Up
1007
+ current=$(( (current - 1 + num_categories) % num_categories ))
1008
+ ;;
1009
+ '[B') # Down
1010
+ current=$(( (current + 1) % num_categories ))
1011
+ ;;
1012
+ esac
1013
+ elif [ "$key" = " " ]; then
1014
+ # Toggle selection (core is always-on — cannot be deselected)
1015
+ case "$current" in
1016
+ 0) ;; # core is required — no toggle
1017
+ 1) sel_1=$(( 1 - sel_1 )) ;; 2) sel_2=$(( 1 - sel_2 )) ;;
1018
+ 3) sel_3=$(( 1 - sel_3 )) ;; 4) sel_4=$(( 1 - sel_4 )) ;;
1019
+ 5) sel_5=$(( 1 - sel_5 )) ;; 6) sel_6=$(( 1 - sel_6 )) ;;
1020
+ 7) sel_7=$(( 1 - sel_7 )) ;; 8) sel_8=$(( 1 - sel_8 )) ;;
1021
+ esac
1022
+ elif [ "$key" = "" ]; then
1023
+ # Enter — confirm
1024
+ break
1025
+ fi
1026
+
1027
+ # Move cursor back up to reprint menu
1028
+ printf '\033[%dA' "$num_categories" >&2
1029
+ done
1030
+
1031
+ # Restore cursor
1032
+ printf '\033[?25h' >&2
1033
+ trap - EXIT INT TERM
1034
+
1035
+ echo "" >&2
1036
+
1037
+ # Build result string (to stdout)
1038
+ local result=""
1039
+ [ "$sel_0" -eq 1 ] && result="$result core"
1040
+ [ "$sel_1" -eq 1 ] && result="$result strategy"
1041
+ [ "$sel_2" -eq 1 ] && result="$result development"
1042
+ [ "$sel_3" -eq 1 ] && result="$result quality"
1043
+ [ "$sel_4" -eq 1 ] && result="$result operations"
1044
+ [ "$sel_5" -eq 1 ] && result="$result hooks"
1045
+ [ "$sel_6" -eq 1 ] && result="$result guardrails"
1046
+ [ "$sel_7" -eq 1 ] && result="$result railway"
1047
+ [ "$sel_8" -eq 1 ] && result="$result superpowers"
1048
+
1049
+ # Re-append hidden categories that were already in config
1050
+ result="$result$_hidden_cats"
1051
+
1052
+ # Trim leading space and output
1053
+ echo "${result# }"
1054
+ }
1055
+
1056
+ # ============================================================================
1057
+ # Symlink Helpers
1058
+ # ============================================================================
1059
+
1060
+ # safe_symlink: Default mode for daily sync
1061
+ # - Symlink exists → update (it's ours)
1062
+ # - Regular file exists → SKIP (project override)
1063
+ # - Doesn't exist → create symlink
1064
+ safe_symlink() {
1065
+ local source="$1" # Absolute path in repo
1066
+ local target="$2" # Absolute path in project .claude/
1067
+
1068
+ if [ -L "$target" ]; then
1069
+ # It's a symlink — update it (managed by us)
1070
+ rm "$target"
1071
+ ln -s "$source" "$target"
1072
+ UPDATED=$((UPDATED + 1))
1073
+ elif [ -e "$target" ]; then
1074
+ # Regular file/dir exists — project override, skip
1075
+ SKIPPED=$((SKIPPED + 1))
1076
+ else
1077
+ # Doesn't exist — create symlink
1078
+ local target_dir
1079
+ target_dir=$(dirname "$target")
1080
+ mkdir -p "$target_dir"
1081
+ ln -s "$source" "$target"
1082
+ CREATED=$((CREATED + 1))
1083
+ fi
1084
+ }
1085
+
1086
+ # convert_to_symlink: One-time migration mode (--convert)
1087
+ # - Symlink exists → update
1088
+ # - Regular file, matches repo → replace with symlink
1089
+ # - Regular file, differs → SKIP + warn (project override)
1090
+ # - Doesn't exist → create symlink
1091
+ convert_to_symlink() {
1092
+ local source="$1"
1093
+ local target="$2"
1094
+
1095
+ if [ -L "$target" ]; then
1096
+ # Already a symlink — update
1097
+ rm "$target"
1098
+ ln -s "$source" "$target"
1099
+ UPDATED=$((UPDATED + 1))
1100
+ elif [ -d "$target" ] && [ -d "$source" ]; then
1101
+ # Both are directories — compare contents
1102
+ if diff -rq "$source" "$target" >/dev/null 2>&1; then
1103
+ # Contents match — replace with symlink
1104
+ rm -rf "$target"
1105
+ ln -s "$source" "$target"
1106
+ CONVERTED=$((CONVERTED + 1))
1107
+ else
1108
+ # Differs — project override
1109
+ echo -e " ${YELLOW}OVERRIDE${NC} $(basename "$target") (differs from repo)"
1110
+ SKIPPED=$((SKIPPED + 1))
1111
+ fi
1112
+ elif [ -f "$target" ] && [ -f "$source" ]; then
1113
+ # Both are files — compare
1114
+ if diff -q "$source" "$target" >/dev/null 2>&1; then
1115
+ # Matches — replace with symlink
1116
+ rm "$target"
1117
+ ln -s "$source" "$target"
1118
+ CONVERTED=$((CONVERTED + 1))
1119
+ else
1120
+ # Differs — project override
1121
+ echo -e " ${YELLOW}OVERRIDE${NC} $(basename "$target") (differs from repo)"
1122
+ SKIPPED=$((SKIPPED + 1))
1123
+ fi
1124
+ elif [ ! -e "$target" ]; then
1125
+ # Doesn't exist — create
1126
+ local target_dir
1127
+ target_dir=$(dirname "$target")
1128
+ mkdir -p "$target_dir"
1129
+ ln -s "$source" "$target"
1130
+ CREATED=$((CREATED + 1))
1131
+ else
1132
+ echo -e " ${RED}ERROR${NC} $(basename "$target") — unexpected state"
1133
+ ERRORS=$((ERRORS + 1))
1134
+ fi
1135
+ }
1136
+
1137
+ # ============================================================================
1138
+ # Brownfield Assessment Helpers
1139
+ # ============================================================================
1140
+
1141
+ # get_size: Returns total bytes for a file or directory
1142
+ get_size() {
1143
+ local path="$1"
1144
+ if [ -f "$path" ]; then
1145
+ wc -c < "$path" | tr -d ' '
1146
+ elif [ -d "$path" ]; then
1147
+ find "$path" -type f -exec cat {} + 2>/dev/null | wc -c | tr -d ' '
1148
+ else
1149
+ echo "0"
1150
+ fi
1151
+ }
1152
+
1153
+ # classify_file: Compare a regular file/dir at target to its toolkit source
1154
+ # Returns: SYMLINK | MISSING | IDENTICAL | STALE | CUSTOMIZED | MODIFIED
1155
+ classify_file() {
1156
+ local source="$1"
1157
+ local target="$2"
1158
+
1159
+ if [ -L "$target" ]; then
1160
+ echo "SYMLINK"
1161
+ return
1162
+ fi
1163
+
1164
+ if [ ! -e "$target" ]; then
1165
+ echo "MISSING"
1166
+ return
1167
+ fi
1168
+
1169
+ # Compare content
1170
+ if [ -d "$source" ] && [ -d "$target" ]; then
1171
+ if diff -rq "$source" "$target" >/dev/null 2>&1; then
1172
+ echo "IDENTICAL"
1173
+ return
1174
+ fi
1175
+ elif [ -f "$source" ] && [ -f "$target" ]; then
1176
+ if diff -q "$source" "$target" >/dev/null 2>&1; then
1177
+ echo "IDENTICAL"
1178
+ return
1179
+ fi
1180
+ fi
1181
+
1182
+ # Size-based classification
1183
+ local source_size target_size
1184
+ source_size=$(get_size "$source")
1185
+ target_size=$(get_size "$target")
1186
+
1187
+ if [ "$source_size" -eq 0 ]; then
1188
+ echo "MODIFIED"
1189
+ return
1190
+ fi
1191
+
1192
+ local ratio=$((target_size * 100 / source_size))
1193
+
1194
+ if [ "$ratio" -lt 70 ]; then
1195
+ echo "STALE"
1196
+ elif [ "$ratio" -gt 150 ]; then
1197
+ echo "CUSTOMIZED"
1198
+ else
1199
+ echo "MODIFIED"
1200
+ fi
1201
+ }
1202
+
1203
+ # generate_config_from_existing: Scan what exists in .claude/ and derive a config
1204
+ # Args: $1=target_dir $2=dry_run (optional, "true" to skip writing)
1205
+ # Echoes: space-separated category list
1206
+ generate_config_from_existing() {
1207
+ local target_dir="$1"
1208
+ local dry_run="${2:-false}"
1209
+
1210
+ local categories="core"
1211
+
1212
+ local cat_list="strategy development quality operations hooks guardrails railway superpowers"
1213
+ for cat in $cat_list; do
1214
+ local items
1215
+ items=$(get_category_items "$cat")
1216
+ local found=false
1217
+ for item in $items; do
1218
+ local bname target_path
1219
+ bname=$(basename "$item")
1220
+ case "$item" in
1221
+ agents/*) target_path="$target_dir/.claude/agents/$bname" ;;
1222
+ skills/railway/*) target_path="$target_dir/.claude/skills/$bname" ;;
1223
+ skills/*) target_path="$target_dir/.claude/skills/$bname" ;;
1224
+ hooks/*) target_path="$target_dir/.claude/hooks/$bname" ;;
1225
+ *) continue ;;
1226
+ esac
1227
+ if [ -e "$target_path" ] || [ -L "$target_path" ]; then
1228
+ found=true
1229
+ break
1230
+ fi
1231
+ done
1232
+ if $found; then
1233
+ categories="$categories $cat"
1234
+ fi
1235
+ done
1236
+
1237
+ if [ "$dry_run" != "true" ]; then
1238
+ write_config "$target_dir" "$categories"
1239
+ fi
1240
+
1241
+ echo "$categories"
1242
+ }
1243
+
1244
+ # smart_sync: Enhanced symlink for brownfield upgrades
1245
+ # - Symlink → update (same as safe_symlink)
1246
+ # - Missing → create symlink (same as safe_symlink)
1247
+ # - IDENTICAL regular file → backup + convert to symlink
1248
+ # - STALE regular file → backup + convert to symlink + warning
1249
+ # - CUSTOMIZED/MODIFIED → skip
1250
+ smart_sync() {
1251
+ local source="$1"
1252
+ local target="$2"
1253
+
1254
+ if [ -L "$target" ]; then
1255
+ rm "$target"
1256
+ ln -s "$source" "$target"
1257
+ UPDATED=$((UPDATED + 1))
1258
+ return
1259
+ fi
1260
+
1261
+ if [ ! -e "$target" ]; then
1262
+ local target_parent
1263
+ target_parent=$(dirname "$target")
1264
+ mkdir -p "$target_parent"
1265
+ ln -s "$source" "$target"
1266
+ CREATED=$((CREATED + 1))
1267
+ return
1268
+ fi
1269
+
1270
+ # Regular file/dir exists
1271
+ local bname
1272
+ bname=$(basename "$target")
1273
+
1274
+ # Check --skip list (only for regular files — symlinks still updated, missing still created)
1275
+ if [ -n "$SKIP_ITEMS" ] && echo ",$SKIP_ITEMS," | grep -q ",$bname,"; then
1276
+ SKIPPED=$((SKIPPED + 1))
1277
+ return
1278
+ fi
1279
+
1280
+ # Classify it
1281
+ local classification
1282
+ classification=$(classify_file "$source" "$target")
1283
+
1284
+ # Derive project root from target path (strip /.claude/...)
1285
+ local project_root
1286
+ project_root=$(echo "$target" | sed 's|/.claude/.*||')
1287
+ local bdir
1288
+ bdir=$(backup_dir "$project_root")
1289
+ mkdir -p "$bdir"
1290
+
1291
+ case "$classification" in
1292
+ IDENTICAL)
1293
+ # Backup + convert to symlink
1294
+ cp -a "$target" "$bdir/$bname.pre-upgrade" 2>/dev/null || true
1295
+ if [ -d "$target" ]; then
1296
+ rm -rf "$target"
1297
+ else
1298
+ rm "$target"
1299
+ fi
1300
+ ln -s "$source" "$target"
1301
+ CONVERTED=$((CONVERTED + 1))
1302
+ ;;
1303
+ STALE)
1304
+ # Defer for interactive confirmation (or auto-convert with --yes)
1305
+ STALE_DEFERRED+=("$source|$target|$bname")
1306
+ ;;
1307
+ CUSTOMIZED|MODIFIED)
1308
+ SKIPPED=$((SKIPPED + 1))
1309
+ ;;
1310
+ esac
1311
+ }
1312
+
1313
+ # do_stale_upgrade: Performs the actual backup + symlink conversion for a stale item
1314
+ do_stale_upgrade() {
1315
+ local source="$1" target="$2" name="$3"
1316
+ local project_root
1317
+ project_root=$(echo "$target" | sed 's|/.claude/.*||')
1318
+ local bdir
1319
+ bdir=$(backup_dir "$project_root")
1320
+ mkdir -p "$bdir"
1321
+
1322
+ cp -a "$target" "$bdir/$name.pre-upgrade" 2>/dev/null || true
1323
+ if [ -d "$target" ]; then
1324
+ rm -rf "$target"
1325
+ else
1326
+ rm "$target"
1327
+ fi
1328
+ ln -s "$source" "$target"
1329
+ UPGRADED=$((UPGRADED + 1))
1330
+ }
1331
+
1332
+ # prompt_stale_conversion: Interactive per-file confirmation for stale items
1333
+ prompt_stale_conversion() {
1334
+ local source="$1" target="$2" name="$3"
1335
+ local source_size target_size
1336
+ source_size=$(get_size "$source")
1337
+ target_size=$(get_size "$target")
1338
+ local ratio=0
1339
+ if [ "$source_size" -gt 0 ]; then
1340
+ ratio=$((target_size * 100 / source_size))
1341
+ fi
1342
+
1343
+ echo -e " ⚠ ${BOLD}$name${NC} (${target_size}B → ${source_size}B toolkit — ${ratio}%)"
1344
+
1345
+ # Show preview of each version
1346
+ local your_line="" toolkit_line=""
1347
+ if [ -f "$target" ]; then
1348
+ your_line=$(head -5 "$target" 2>/dev/null | grep -v '^$' | grep -v '^---$' | head -1 | cut -c1-80)
1349
+ elif [ -d "$target" ]; then
1350
+ your_line="[directory: $(ls "$target" 2>/dev/null | wc -l | tr -d ' ') files]"
1351
+ fi
1352
+ if [ -f "$source" ]; then
1353
+ toolkit_line=$(head -5 "$source" 2>/dev/null | grep -v '^$' | grep -v '^---$' | head -1 | cut -c1-80)
1354
+ elif [ -d "$source" ]; then
1355
+ toolkit_line="[directory: $(ls "$source" 2>/dev/null | wc -l | tr -d ' ') files]"
1356
+ fi
1357
+ echo -e " ${DIM}Your version:${NC} $your_line"
1358
+ echo -e " ${DIM}Toolkit version:${NC} $toolkit_line"
1359
+
1360
+ while true; do
1361
+ if [ "$FORCE_YES" = "true" ]; then
1362
+ echo -e " Replace with toolkit version? [y/N/d(diff)] ${GREEN}y${NC} (--yes)"
1363
+ do_stale_upgrade "$source" "$target" "$name"
1364
+ echo -e " ${GREEN}✓ Upgraded${NC} $name"
1365
+ return
1366
+ fi
1367
+ printf " Replace with toolkit version? [y/N/d(diff)] " >&2
1368
+ read -r answer < /dev/tty 2>/dev/null || read -r answer
1369
+ case "$answer" in
1370
+ y|Y)
1371
+ do_stale_upgrade "$source" "$target" "$name"
1372
+ echo -e " ${GREEN}✓ Upgraded${NC} $name"
1373
+ return
1374
+ ;;
1375
+ d|D)
1376
+ echo ""
1377
+ if [ -d "$target" ]; then
1378
+ diff -r "$target" "$source" 2>/dev/null | head -40 || true
1379
+ else
1380
+ diff "$target" "$source" 2>/dev/null | head -40 || true
1381
+ fi
1382
+ echo -e " ${DIM}(showing first 40 lines of diff)${NC}"
1383
+ echo ""
1384
+ ;;
1385
+ *)
1386
+ echo -e " ${CYAN}Kept${NC} your version of $name"
1387
+ KEPT=$((KEPT + 1))
1388
+ return
1389
+ ;;
1390
+ esac
1391
+ done
1392
+ }
1393
+
1394
+ # ============================================================================
1395
+ # Manifest Parsing
1396
+ # ============================================================================
1397
+
1398
+ # Process entries from the manifest
1399
+ # Args: $1=target_dir $2=mode(safe|convert) $3=include_railway(true|false) $4=filter_items(optional)
1400
+ # If filter_items is provided (space-separated paths), only those entries are processed.
1401
+ # If empty, all entries are processed (with railway controlled by $3).
1402
+ process_manifest() {
1403
+ local target_dir="$1"
1404
+ local mode="$2"
1405
+ local include_railway="$3"
1406
+ local filter_items="${4:-}"
1407
+
1408
+ local linker="safe_symlink"
1409
+ if [ "$mode" = "convert" ]; then
1410
+ linker="convert_to_symlink"
1411
+ elif [ "$mode" = "smart" ]; then
1412
+ linker="smart_sync"
1413
+ fi
1414
+
1415
+ while IFS= read -r line; do
1416
+ # Skip comments and empty lines
1417
+ [[ "$line" =~ ^#.*$ ]] && continue
1418
+ [[ -z "$line" ]] && continue
1419
+
1420
+ local entry_type entry_path
1421
+ entry_type=$(echo "$line" | awk '{print $1}')
1422
+ entry_path=$(echo "$line" | awk '{print $2}')
1423
+
1424
+ # Apply filter if provided
1425
+ if [ -n "$filter_items" ]; then
1426
+ local in_filter=false
1427
+ for allowed in $filter_items; do
1428
+ if [ "$entry_path" = "$allowed" ]; then
1429
+ in_filter=true
1430
+ break
1431
+ fi
1432
+ done
1433
+ if ! $in_filter; then
1434
+ continue
1435
+ fi
1436
+ else
1437
+ # No filter — use railway flag for backwards compat
1438
+ if [ "$entry_type" = "railway-skill-dir" ] && [ "$include_railway" != "true" ]; then
1439
+ continue
1440
+ fi
1441
+ fi
1442
+
1443
+ local source_abs="$SCRIPT_DIR/$entry_path"
1444
+ local target_abs=""
1445
+
1446
+ case "$entry_type" in
1447
+ agent)
1448
+ local filename
1449
+ filename=$(basename "$entry_path")
1450
+ target_abs="$target_dir/.claude/agents/$filename"
1451
+ ;;
1452
+ skill)
1453
+ local filename
1454
+ filename=$(basename "$entry_path")
1455
+ target_abs="$target_dir/.claude/skills/$filename"
1456
+ ;;
1457
+ skill-dir)
1458
+ local dirname
1459
+ dirname=$(basename "$entry_path")
1460
+ target_abs="$target_dir/.claude/skills/$dirname"
1461
+ ;;
1462
+ hook)
1463
+ local filename
1464
+ filename=$(basename "$entry_path")
1465
+ target_abs="$target_dir/.claude/hooks/$filename"
1466
+ ;;
1467
+ railway-skill-dir)
1468
+ local dirname
1469
+ dirname=$(basename "$entry_path")
1470
+ target_abs="$target_dir/.claude/skills/$dirname"
1471
+ ;;
1472
+ *)
1473
+ echo -e " ${YELLOW}WARN${NC} Unknown entry type: $entry_type"
1474
+ continue
1475
+ ;;
1476
+ esac
1477
+
1478
+ # Verify source exists
1479
+ if [ ! -e "$source_abs" ]; then
1480
+ echo -e " ${RED}MISSING${NC} $entry_path (not found in repo)"
1481
+ ERRORS=$((ERRORS + 1))
1482
+ continue
1483
+ fi
1484
+
1485
+ $linker "$source_abs" "$target_abs"
1486
+ done < "$MANIFEST"
1487
+ }
1488
+
1489
+ # ============================================================================
1490
+ # Deselection Handler (Phase 7)
1491
+ # ============================================================================
1492
+ # Removes items for categories that were previously installed but are now deselected.
1493
+
1494
+ remove_category_items() {
1495
+ local target_dir="$1"
1496
+ local category="$2"
1497
+ local items
1498
+ items=$(get_category_items "$category")
1499
+
1500
+ for item in $items; do
1501
+ local target_abs=""
1502
+ local bname
1503
+ bname=$(basename "$item")
1504
+
1505
+ case "$item" in
1506
+ agents/*) target_abs="$target_dir/.claude/agents/$bname" ;;
1507
+ skills/railway/*) target_abs="$target_dir/.claude/skills/$bname" ;;
1508
+ skills/*) target_abs="$target_dir/.claude/skills/$bname" ;;
1509
+ hooks/*) target_abs="$target_dir/.claude/hooks/$bname" ;;
1510
+ esac
1511
+
1512
+ # Only remove if it's a symlink pointing to our repo
1513
+ if [ -L "$target_abs" ]; then
1514
+ local link_target
1515
+ link_target=$(readlink "$target_abs" 2>/dev/null || true)
1516
+ if echo "$link_target" | grep -q "/.claude-agents/"; then
1517
+ rm "$target_abs"
1518
+ REMOVED=$((REMOVED + 1))
1519
+ fi
1520
+ fi
1521
+ done
1522
+
1523
+ # Remove corresponding hooks from settings.json
1524
+ local hooks_to_remove
1525
+ hooks_to_remove=$(get_category_hooks "$category")
1526
+ if [ -n "$hooks_to_remove" ]; then
1527
+ merge_settings_hooks "$target_dir/.claude/settings.json" "remove" "$hooks_to_remove"
1528
+ fi
1529
+ }
1530
+
1531
+ handle_deselection() {
1532
+ local target_dir="$1"
1533
+ local old_cats="$2"
1534
+ local new_cats="$3"
1535
+
1536
+ for old_cat in $old_cats; do
1537
+ local still_selected=false
1538
+ for new_cat in $new_cats; do
1539
+ if [ "$old_cat" = "$new_cat" ]; then
1540
+ still_selected=true
1541
+ break
1542
+ fi
1543
+ done
1544
+
1545
+ if ! $still_selected; then
1546
+ echo -e " Removing ${YELLOW}$old_cat${NC} (deselected)..."
1547
+ remove_category_items "$target_dir" "$old_cat"
1548
+ fi
1549
+ done
1550
+ }
1551
+
1552
+ # ============================================================================
1553
+ # Uninstall Handler (Phase 4)
1554
+ # ============================================================================
1555
+
1556
+ handle_uninstall() {
1557
+ local target_dir="$1"
1558
+
1559
+ echo -e "${BOLD}Claude Agents — Uninstall${NC}"
1560
+ echo -e "Target: ${CYAN}$target_dir${NC}"
1561
+ echo ""
1562
+
1563
+ local bdir
1564
+ bdir=$(backup_dir "$target_dir")
1565
+ local manifest_file="$bdir/manifest.txt"
1566
+
1567
+ # Count what will be removed
1568
+ local symlink_count=0
1569
+ local preserved_count=0
1570
+
1571
+ if [ -f "$manifest_file" ]; then
1572
+ while IFS= read -r item_path; do
1573
+ [ -z "$item_path" ] && continue
1574
+ if [ -L "$item_path" ]; then
1575
+ local link_target
1576
+ link_target=$(readlink "$item_path" 2>/dev/null || true)
1577
+ if echo "$link_target" | grep -q "/.claude-agents/"; then
1578
+ symlink_count=$((symlink_count + 1))
1579
+ fi
1580
+ elif [ -e "$item_path" ]; then
1581
+ preserved_count=$((preserved_count + 1))
1582
+ fi
1583
+ done < "$manifest_file"
1584
+ else
1585
+ # No manifest — scan for symlinks
1586
+ for subdir in agents skills hooks; do
1587
+ local search_dir="$target_dir/.claude/$subdir"
1588
+ [ ! -d "$search_dir" ] && continue
1589
+ for item in "$search_dir"/*; do
1590
+ [ ! -e "$item" ] && [ ! -L "$item" ] && continue
1591
+ if [ -L "$item" ]; then
1592
+ local link_target
1593
+ link_target=$(readlink "$item" 2>/dev/null || true)
1594
+ if echo "$link_target" | grep -q "/.claude-agents/"; then
1595
+ symlink_count=$((symlink_count + 1))
1596
+ fi
1597
+ fi
1598
+ done
1599
+ done
1600
+ fi
1601
+
1602
+ echo "This will:"
1603
+ echo " - Remove $symlink_count symlinks managed by claude-agents"
1604
+ if [ "$preserved_count" -gt 0 ]; then
1605
+ echo -e " - ${GREEN}Preserve $preserved_count project-specific files${NC} (not ours)"
1606
+ fi
1607
+ if [ -f "$bdir/settings.json.bak" ]; then
1608
+ echo " - Restore settings.json from backup"
1609
+ else
1610
+ echo " - Remove our hook entries from settings.json"
1611
+ fi
1612
+ if [ -f "$bdir/gitignore.bak" ]; then
1613
+ echo " - Restore .gitignore from backup"
1614
+ fi
1615
+ echo " - Remove config and backup files"
1616
+ echo ""
1617
+
1618
+ # Confirm
1619
+ printf "Proceed? [y/N] "
1620
+ local confirm
1621
+ read -r confirm < /dev/tty 2>/dev/null || read -r confirm
1622
+ if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
1623
+ echo "Aborted."
1624
+ exit 0
1625
+ fi
1626
+
1627
+ echo ""
1628
+
1629
+ # Step 1: Remove symlinks
1630
+ local removed=0
1631
+ local kept=0
1632
+
1633
+ remove_our_symlinks() {
1634
+ local search_path="$1"
1635
+ if [ -L "$search_path" ]; then
1636
+ local lt
1637
+ lt=$(readlink "$search_path" 2>/dev/null || true)
1638
+ if echo "$lt" | grep -q "/.claude-agents/"; then
1639
+ rm "$search_path"
1640
+ removed=$((removed + 1))
1641
+ return
1642
+ fi
1643
+ fi
1644
+ if [ -e "$search_path" ] && [ ! -L "$search_path" ]; then
1645
+ kept=$((kept + 1))
1646
+ fi
1647
+ }
1648
+
1649
+ if [ -f "$manifest_file" ]; then
1650
+ while IFS= read -r item_path; do
1651
+ [ -z "$item_path" ] && continue
1652
+ remove_our_symlinks "$item_path"
1653
+ done < "$manifest_file"
1654
+ else
1655
+ for subdir in agents skills hooks; do
1656
+ local search_dir="$target_dir/.claude/$subdir"
1657
+ [ ! -d "$search_dir" ] && continue
1658
+ for item in "$search_dir"/*; do
1659
+ [ ! -e "$item" ] && [ ! -L "$item" ] && continue
1660
+ remove_our_symlinks "$item"
1661
+ done
1662
+ done
1663
+ fi
1664
+
1665
+ echo -e " Symlinks removed: ${GREEN}$removed${NC}"
1666
+ if [ "$kept" -gt 0 ]; then
1667
+ echo -e " Project files preserved: ${GREEN}$kept${NC}"
1668
+ fi
1669
+
1670
+ # Step 2: Restore settings.json
1671
+ if [ -f "$bdir/settings.json.bak" ]; then
1672
+ cp "$bdir/settings.json.bak" "$target_dir/.claude/settings.json"
1673
+ echo -e " Settings.json: ${GREEN}restored from backup${NC}"
1674
+ elif [ -f "$target_dir/.claude/settings.json" ]; then
1675
+ # No backup — remove our hooks by fingerprint
1676
+ local hooks_to_remove
1677
+ hooks_to_remove=""
1678
+ if config_exists "$target_dir"; then
1679
+ load_config "$target_dir"
1680
+ hooks_to_remove="$CONF_SETTINGS_HOOKS_ADDED"
1681
+ else
1682
+ hooks_to_remove="sync-agents triage-router session-end session-bootstrap pre-bash-guard pre-task-context pre-edit-guard pre-write-guard post-test-summary post-edit-lint post-deploy-health"
1683
+ fi
1684
+ if [ -n "$hooks_to_remove" ]; then
1685
+ merge_settings_hooks "$target_dir/.claude/settings.json" "remove" "$hooks_to_remove"
1686
+ echo -e " Settings.json: ${GREEN}removed our hook entries${NC}"
1687
+ fi
1688
+ fi
1689
+
1690
+ # Step 3: Restore .gitignore
1691
+ if [ -f "$bdir/gitignore.bak" ]; then
1692
+ cp "$bdir/gitignore.bak" "$target_dir/.claude/.gitignore"
1693
+ echo -e " .gitignore: ${GREEN}restored from backup${NC}"
1694
+ else
1695
+ remove_gitignore_block "$target_dir"
1696
+ echo -e " .gitignore: ${GREEN}removed our marker block${NC}"
1697
+ fi
1698
+
1699
+ # Step 3b: Remove CLAUDE.md managed block
1700
+ remove_claudemd_block "$target_dir"
1701
+
1702
+ # Step 4: Remove empty directories
1703
+ for subdir in agents skills hooks; do
1704
+ local dir="$target_dir/.claude/$subdir"
1705
+ if [ -d "$dir" ]; then
1706
+ # Remove if empty (rmdir fails on non-empty, which is what we want)
1707
+ rmdir "$dir" 2>/dev/null && echo -e " ${DIM}Removed empty .claude/$subdir/${NC}" || true
1708
+ fi
1709
+ done
1710
+
1711
+ # Step 5: Remove our config and backup
1712
+ rm -f "$(config_path "$target_dir")"
1713
+ rm -rf "$bdir"
1714
+
1715
+ echo ""
1716
+ echo -e "${BOLD}Uninstall complete.${NC} Your project is restored to its pre-install state."
1717
+ }
1718
+
1719
+ # ============================================================================
1720
+ # Setup Handler (Phase 2 + 7)
1721
+ # ============================================================================
1722
+ # Full interactive setup flow: detect state, show menu, install, configure.
1723
+
1724
+ handle_setup() {
1725
+ local target_dir="$1"
1726
+
1727
+ # Detect project state
1728
+ local project_state
1729
+ project_state=$(detect_project_state "$target_dir")
1730
+
1731
+ # Load existing config if present (for re-setup)
1732
+ local old_categories=""
1733
+ if config_exists "$target_dir"; then
1734
+ load_config "$target_dir"
1735
+ old_categories="$CONF_INSTALLED_CATEGORIES"
1736
+ fi
1737
+
1738
+ # Show interactive menu
1739
+ local new_categories
1740
+ new_categories=$(show_interactive_menu "$target_dir" "$old_categories" "$project_state")
1741
+
1742
+ if [ -z "$new_categories" ]; then
1743
+ echo -e "${YELLOW}No categories selected. Nothing to install.${NC}"
1744
+ exit 0
1745
+ fi
1746
+
1747
+ echo -e "${BOLD}Claude Agents${NC} — Installing into: ${CYAN}$target_dir${NC}"
1748
+ echo ""
1749
+
1750
+ # Snapshot backup (first time only)
1751
+ snapshot_pre_install "$target_dir"
1752
+
1753
+ # Handle deselections (re-setup only)
1754
+ if [ -n "$old_categories" ]; then
1755
+ handle_deselection "$target_dir" "$old_categories" "$new_categories"
1756
+ fi
1757
+
1758
+ # Build filter list from selected categories
1759
+ local filter_items
1760
+ filter_items=$(items_for_categories "$new_categories")
1761
+
1762
+ # Ensure directories
1763
+ mkdir -p "$target_dir/.claude/agents"
1764
+ mkdir -p "$target_dir/.claude/skills"
1765
+ mkdir -p "$target_dir/.claude/hooks"
1766
+
1767
+ # Process manifest (filtered)
1768
+ echo -e "Mode: ${GREEN}setup${NC} (interactive install)"
1769
+ process_manifest "$target_dir" "safe" "false" "$filter_items"
1770
+
1771
+ # Merge settings.json hooks
1772
+ local hooks_to_add
1773
+ hooks_to_add=$(hooks_for_categories "$new_categories")
1774
+ if [ -n "$hooks_to_add" ]; then
1775
+ merge_settings_hooks "$target_dir/.claude/settings.json" "add" "$hooks_to_add"
1776
+ fi
1777
+
1778
+ # If hooks were deselected, make sure they're removed
1779
+ if [ -n "$old_categories" ]; then
1780
+ local old_hooks new_hooks
1781
+ old_hooks=$(hooks_for_categories "$old_categories")
1782
+ new_hooks=$(hooks_for_categories "$new_categories")
1783
+ for oh in $old_hooks; do
1784
+ local still_needed=false
1785
+ for nh in $new_hooks; do
1786
+ if [ "$oh" = "$nh" ]; then
1787
+ still_needed=true
1788
+ break
1789
+ fi
1790
+ done
1791
+ if ! $still_needed; then
1792
+ merge_settings_hooks "$target_dir/.claude/settings.json" "remove" "$oh"
1793
+ fi
1794
+ done
1795
+ fi
1796
+
1797
+ # Update .gitignore marker block
1798
+ update_gitignore_block "$target_dir" "$filter_items"
1799
+
1800
+ # Create CLAUDE.md from template if it doesn't exist (greenfield setup)
1801
+ if [ ! -f "$target_dir/CLAUDE.md" ]; then
1802
+ local project_name
1803
+ project_name=$(basename "$target_dir")
1804
+ sed "s/{{PROJECT_NAME}}/$project_name/g" "$SCRIPT_DIR/templates/CLAUDE.md.template" > "$target_dir/CLAUDE.md"
1805
+ # Inject toolkit managed block into freshly created CLAUDE.md
1806
+ if [ -f "$CLAUDEMD_MANAGED_TEMPLATE" ]; then
1807
+ local tmp block_content
1808
+ tmp=$(mktemp)
1809
+ block_content="$CLAUDEMD_MARKER_START"$'\n'
1810
+ block_content+=$(cat "$CLAUDEMD_MANAGED_TEMPLATE")
1811
+ block_content+=$'\n'"$CLAUDEMD_MARKER_END"
1812
+ local first_line=true
1813
+ while IFS= read -r line; do
1814
+ echo "$line" >> "$tmp"
1815
+ if $first_line; then
1816
+ first_line=false
1817
+ echo "" >> "$tmp"
1818
+ echo "$block_content" >> "$tmp"
1819
+ fi
1820
+ done < "$target_dir/CLAUDE.md"
1821
+ mv "$tmp" "$target_dir/CLAUDE.md"
1822
+ fi
1823
+ echo -e " ${GREEN}Created${NC} CLAUDE.md (edit the TODOs)"
1824
+ fi
1825
+
1826
+ # Update CLAUDE.md managed block (ask on setup, silent on sync)
1827
+ if [ "$MODE" = "setup" ]; then
1828
+ update_claudemd_block "$target_dir" "ask"
1829
+ else
1830
+ update_claudemd_block "$target_dir" "silent"
1831
+ fi
1832
+
1833
+ # Make hooks executable
1834
+ for hook in "$target_dir/.claude/hooks/"*.sh; do
1835
+ if [ -f "$hook" ] || [ -L "$hook" ]; then
1836
+ chmod +x "$hook" 2>/dev/null || true
1837
+ fi
1838
+ done
1839
+
1840
+ # Write config
1841
+ write_config "$target_dir" "$new_categories"
1842
+
1843
+ # Post-install record (manifest.txt of symlinks)
1844
+ post_install_record "$target_dir"
1845
+
1846
+ # Summary
1847
+ echo ""
1848
+ echo -e "${BOLD}Done!${NC}"
1849
+ echo -e " Created: ${GREEN}$CREATED${NC}"
1850
+ echo -e " Updated: ${GREEN}$UPDATED${NC}"
1851
+ echo -e " Skipped: ${YELLOW}$SKIPPED${NC} (project overrides)"
1852
+ if [ "$REMOVED" -gt 0 ]; then
1853
+ echo -e " Removed: ${YELLOW}$REMOVED${NC} (deselected)"
1854
+ fi
1855
+ if [ "$ERRORS" -gt 0 ]; then
1856
+ echo -e " Errors: ${RED}$ERRORS${NC}"
1857
+ fi
1858
+
1859
+ # Post-setup guidance based on project state
1860
+ echo ""
1861
+ case "$project_state" in
1862
+ greenfield)
1863
+ echo -e "${BOLD}Your project is ready!${NC} Here's what was set up:"
1864
+ echo ""
1865
+ echo " Agents: AI-powered specialists (architect, code-reviewer, design, PM, QA)"
1866
+ echo " Skills: Workflow automations (/planning, /implement, /pr, /issue)"
1867
+ echo " Hooks: Auto-sync on session start + cost-saving triage router"
1868
+ echo ""
1869
+ echo "Start with:"
1870
+ echo " - Run ${CYAN}claude${NC} to begin — the triage router auto-delegates to cheap agents"
1871
+ echo " - Use ${CYAN}/planning <feature>${NC} to plan a feature with a team of agents"
1872
+ echo " - Use ${CYAN}/pr${NC} to create a PR with automated QA"
1873
+ echo " - Use ${CYAN}/sync status${NC} to see what's installed"
1874
+ ;;
1875
+ minimal)
1876
+ echo -e "${BOLD}Tools installed alongside your existing setup.${NC}"
1877
+ echo ""
1878
+ echo " Your existing agents/skills/hooks are auto-discovered by the triage"
1879
+ echo " router and will be included in routing decisions alongside ours."
1880
+ echo ""
1881
+ echo " Use ${CYAN}/sync status${NC} to see everything that's linked."
1882
+ echo " Use ${CYAN}/sync setup${NC} to change your selections later."
1883
+ ;;
1884
+ established)
1885
+ echo -e "${BOLD}Portable tools symlinked — your project files are untouched.${NC}"
1886
+ echo ""
1887
+ echo " The triage router sees both your tools and ours. Routing is automatic."
1888
+ echo " Use ${CYAN}/sync status${NC} to review. Use ${CYAN}/sync setup${NC} to adjust."
1889
+ ;;
1890
+ esac
1891
+
1892
+ # Cross-sell clade/arth if available
1893
+ if command -v clade &>/dev/null || command -v arth &>/dev/null; then
1894
+ echo ""
1895
+ echo -e "${BOLD}Also available:${NC}"
1896
+ if command -v clade &>/dev/null; then
1897
+ echo -e " ${CYAN}clade sync${NC} Translate toolkit to Cursor/Codex/Kiro/Gemini"
1898
+ fi
1899
+ if command -v arth &>/dev/null; then
1900
+ echo -e " ${CYAN}arth setup .${NC} Full setup: toolkit + multi-engine + dashboard"
1901
+ fi
1902
+ fi
1903
+ }
1904
+
1905
+ # ============================================================================
1906
+ # Sync From Config (Phase 6)
1907
+ # ============================================================================
1908
+ # Silent sync respecting previously configured categories.
1909
+
1910
+ handle_sync_from_config() {
1911
+ local target_dir="$1"
1912
+
1913
+ if ! config_exists "$target_dir"; then
1914
+ # No config — fall through to full sync (backwards compat)
1915
+ return 1
1916
+ fi
1917
+
1918
+ load_config "$target_dir"
1919
+
1920
+ local filter_items
1921
+ filter_items=$(items_for_categories "$CONF_INSTALLED_CATEGORIES")
1922
+
1923
+ # Ensure directories
1924
+ mkdir -p "$target_dir/.claude/agents"
1925
+ mkdir -p "$target_dir/.claude/skills"
1926
+ mkdir -p "$target_dir/.claude/hooks"
1927
+
1928
+ echo -e "${BOLD}Claude Agents${NC} — Installing into: ${CYAN}$target_dir${NC}"
1929
+ echo ""
1930
+ echo -e "Mode: ${GREEN}sync${NC} (safe symlinks — won't touch regular files)"
1931
+
1932
+ process_manifest "$target_dir" "safe" "false" "$filter_items"
1933
+
1934
+ # Merge settings.json hooks (ensure all configured hooks are registered)
1935
+ local hooks_to_add
1936
+ hooks_to_add=$(hooks_for_categories "$CONF_INSTALLED_CATEGORIES")
1937
+ if [ -n "$hooks_to_add" ]; then
1938
+ merge_settings_hooks "$target_dir/.claude/settings.json" "add" "$hooks_to_add"
1939
+ fi
1940
+
1941
+ # Update CLAUDE.md managed block (silent — only updates existing markers)
1942
+ update_claudemd_block "$target_dir" "silent"
1943
+
1944
+ # Make hooks executable
1945
+ for hook in "$target_dir/.claude/hooks/"*.sh; do
1946
+ if [ -f "$hook" ] || [ -L "$hook" ]; then
1947
+ chmod +x "$hook" 2>/dev/null || true
1948
+ fi
1949
+ done
1950
+
1951
+ # Update manifest.txt
1952
+ post_install_record "$target_dir"
1953
+
1954
+ # Summary
1955
+ echo ""
1956
+ echo -e "${BOLD}Done!${NC}"
1957
+ echo -e " Created: ${GREEN}$CREATED${NC}"
1958
+ echo -e " Updated: ${GREEN}$UPDATED${NC}"
1959
+ echo -e " Skipped: ${YELLOW}$SKIPPED${NC} (project overrides)"
1960
+ if [ "$ERRORS" -gt 0 ]; then
1961
+ echo -e " Errors: ${RED}$ERRORS${NC}"
1962
+ fi
1963
+
1964
+ return 0
1965
+ }
1966
+
1967
+ # ============================================================================
1968
+ # Status Report
1969
+ # ============================================================================
1970
+
1971
+ show_status() {
1972
+ local target_dir="$1"
1973
+ local include_railway="$2"
1974
+
1975
+ echo -e "${BOLD}Claude Agents — Status Report${NC}"
1976
+ echo -e "Project: ${CYAN}$target_dir${NC}"
1977
+ echo -e "Repo: ${CYAN}$SCRIPT_DIR${NC}"
1978
+ echo ""
1979
+
1980
+ # Show config info if present
1981
+ if config_exists "$target_dir"; then
1982
+ load_config "$target_dir"
1983
+ echo -e "Config: ${GREEN}found${NC} (categories: $CONF_INSTALLED_CATEGORIES)"
1984
+ echo ""
1985
+ else
1986
+ echo -e "Config: ${YELLOW}none${NC} (using full manifest)"
1987
+ echo ""
1988
+ fi
1989
+
1990
+ local linked=0
1991
+ local overridden=0
1992
+ local missing=0
1993
+
1994
+ printf "%-14s %-30s %s\n" "TYPE" "NAME" "STATUS"
1995
+ printf "%-14s %-30s %s\n" "----" "----" "------"
1996
+
1997
+ while IFS= read -r line; do
1998
+ [[ "$line" =~ ^#.*$ ]] && continue
1999
+ [[ -z "$line" ]] && continue
2000
+
2001
+ local entry_type entry_path
2002
+ entry_type=$(echo "$line" | awk '{print $1}')
2003
+ entry_path=$(echo "$line" | awk '{print $2}')
2004
+
2005
+ if [ "$entry_type" = "railway-skill-dir" ] && [ "$include_railway" != "true" ]; then
2006
+ continue
2007
+ fi
2008
+
2009
+ local target_abs=""
2010
+ local display_name
2011
+ display_name=$(basename "$entry_path")
2012
+
2013
+ case "$entry_type" in
2014
+ agent) target_abs="$target_dir/.claude/agents/$display_name" ;;
2015
+ skill) target_abs="$target_dir/.claude/skills/$display_name" ;;
2016
+ skill-dir) target_abs="$target_dir/.claude/skills/$display_name" ;;
2017
+ hook) target_abs="$target_dir/.claude/hooks/$display_name" ;;
2018
+ railway-skill-dir)
2019
+ entry_type="railway"
2020
+ target_abs="$target_dir/.claude/skills/$display_name"
2021
+ ;;
2022
+ esac
2023
+
2024
+ local status=""
2025
+ if [ -L "$target_abs" ]; then
2026
+ status="${GREEN}symlinked${NC}"
2027
+ linked=$((linked + 1))
2028
+ elif [ -e "$target_abs" ]; then
2029
+ status="${YELLOW}override${NC}"
2030
+ overridden=$((overridden + 1))
2031
+ else
2032
+ status="${RED}missing${NC}"
2033
+ missing=$((missing + 1))
2034
+ fi
2035
+
2036
+ printf "%-14s %-30s " "$entry_type" "$display_name"
2037
+ echo -e "$status"
2038
+ done < "$MANIFEST"
2039
+
2040
+ echo ""
2041
+ echo -e "Symlinked: ${GREEN}$linked${NC} Overridden: ${YELLOW}$overridden${NC} Missing: ${RED}$missing${NC}"
2042
+
2043
+ # Show project-specific items (auto-discovered)
2044
+ local project_agents=0
2045
+ local project_skills=0
2046
+ if [ -d "$target_dir/.claude/agents" ]; then
2047
+ for f in "$target_dir/.claude/agents"/*.md; do
2048
+ [ -f "$f" ] && [ ! -L "$f" ] && project_agents=$((project_agents + 1))
2049
+ done
2050
+ fi
2051
+ if [ -d "$target_dir/.claude/skills" ]; then
2052
+ for d in "$target_dir/.claude/skills"/*/; do
2053
+ [ -d "$d" ] && [ ! -L "${d%/}" ] && project_skills=$((project_skills + 1))
2054
+ done
2055
+ fi
2056
+ if [ $((project_agents + project_skills)) -gt 0 ]; then
2057
+ echo ""
2058
+ echo -e "${DIM}Project-specific (auto-discovered by triage router):${NC}"
2059
+ if [ -d "$target_dir/.claude/agents" ]; then
2060
+ for f in "$target_dir/.claude/agents"/*.md; do
2061
+ [ -f "$f" ] && [ ! -L "$f" ] && printf " %-14s %s\n" "agent" "$(basename "$f")"
2062
+ done
2063
+ fi
2064
+ if [ -d "$target_dir/.claude/skills" ]; then
2065
+ for d in "$target_dir/.claude/skills"/*/; do
2066
+ [ -d "$d" ] && [ ! -L "${d%/}" ] && printf " %-14s %s\n" "skill" "$(basename "$d")"
2067
+ done
2068
+ fi
2069
+ fi
2070
+
2071
+ # License status
2072
+ echo ""
2073
+ if [ -f "$LICENSE_FILE" ]; then
2074
+ local key
2075
+ key=$(cat "$LICENSE_FILE")
2076
+ local masked="${key:0:9}****-****"
2077
+ echo -e "License: ${GREEN}$masked${NC}"
2078
+ else
2079
+ echo -e "License: ${RED}not configured${NC}"
2080
+ fi
2081
+ }
2082
+
2083
+ # ============================================================================
2084
+ # Scaffold (Greenfield)
2085
+ # ============================================================================
2086
+
2087
+ scaffold_project() {
2088
+ local target_dir="$1"
2089
+
2090
+ echo -e "${BOLD}Scaffolding new project...${NC}"
2091
+
2092
+ # Create directory structure
2093
+ mkdir -p "$target_dir/.claude/agents"
2094
+ mkdir -p "$target_dir/.claude/skills"
2095
+ mkdir -p "$target_dir/.claude/hooks"
2096
+ mkdir -p "$target_dir/.claude/plans"
2097
+ mkdir -p "$target_dir/.claude/qa-knowledge"
2098
+
2099
+ # Copy settings template if doesn't exist
2100
+ if [ ! -f "$target_dir/.claude/settings.json" ]; then
2101
+ cp "$SCRIPT_DIR/templates/settings.json" "$target_dir/.claude/settings.json"
2102
+ echo -e " ${GREEN}Created${NC} .claude/settings.json"
2103
+ else
2104
+ echo -e " ${YELLOW}Exists${NC} .claude/settings.json"
2105
+ fi
2106
+
2107
+ # Copy CLAUDE.md template if doesn't exist
2108
+ if [ ! -f "$target_dir/CLAUDE.md" ]; then
2109
+ local project_name
2110
+ project_name=$(basename "$target_dir")
2111
+ sed "s/{{PROJECT_NAME}}/$project_name/g" "$SCRIPT_DIR/templates/CLAUDE.md.template" > "$target_dir/CLAUDE.md"
2112
+ # Inject toolkit managed block into freshly created CLAUDE.md
2113
+ if [ -f "$CLAUDEMD_MANAGED_TEMPLATE" ]; then
2114
+ local tmp block_content
2115
+ tmp=$(mktemp)
2116
+ block_content="$CLAUDEMD_MARKER_START"$'\n'
2117
+ block_content+=$(cat "$CLAUDEMD_MANAGED_TEMPLATE")
2118
+ block_content+=$'\n'"$CLAUDEMD_MARKER_END"
2119
+ local first_line=true
2120
+ while IFS= read -r line; do
2121
+ echo "$line" >> "$tmp"
2122
+ if $first_line; then
2123
+ first_line=false
2124
+ echo "" >> "$tmp"
2125
+ echo "$block_content" >> "$tmp"
2126
+ fi
2127
+ done < "$target_dir/CLAUDE.md"
2128
+ mv "$tmp" "$target_dir/CLAUDE.md"
2129
+ fi
2130
+ echo -e " ${GREEN}Created${NC} CLAUDE.md (edit the TODOs)"
2131
+ else
2132
+ echo -e " ${YELLOW}Exists${NC} CLAUDE.md"
2133
+ fi
2134
+
2135
+ # Copy example agents for reference
2136
+ if [ -d "$SCRIPT_DIR/examples/agents" ]; then
2137
+ echo ""
2138
+ echo "Example project-specific agents available at:"
2139
+ echo " $SCRIPT_DIR/examples/agents/"
2140
+ echo " Copy any you need to: $target_dir/.claude/agents/"
2141
+ fi
2142
+
2143
+ echo ""
2144
+ }
2145
+
2146
+ # ============================================================================
2147
+ # Brownfield Assessment (--assess)
2148
+ # ============================================================================
2149
+
2150
+ handle_assess() {
2151
+ local target_dir="$1"
2152
+
2153
+ echo -e "${BOLD}Claude Agents — Brownfield Assessment${NC}"
2154
+ echo -e "Project: ${CYAN}$target_dir${NC}"
2155
+ echo ""
2156
+
2157
+ # Legend
2158
+ echo -e "${BOLD}Legend:${NC}"
2159
+ echo -e " ✓ ${GREEN}SYMLINK${NC} Already managed by toolkit. Auto-updated on sync."
2160
+ echo -e " = ${CYAN}IDENTICAL${NC} Same content as toolkit. Safe to convert to symlink."
2161
+ echo -e " ⚠ ${YELLOW}STALE${NC} Much smaller than toolkit version (<70% size)."
2162
+ echo -e " Could be: outdated copy OR completely different file sharing a name."
2163
+ echo -e " ★ ${BOLD}CUSTOMIZED${NC} Much larger than toolkit (>150% size). Your project-specific content."
2164
+ echo -e " ~ ${DIM}MODIFIED${NC} Similar size but different content. Your edits preserved."
2165
+ echo -e " ◆ PROJECT-ONLY No toolkit equivalent. Unique to your project."
2166
+ echo ""
2167
+
2168
+ local section_symlinks="" section_identical="" section_stale=""
2169
+ local section_customized="" section_modified="" section_missing=""
2170
+ local count_symlinks=0 count_identical=0 count_stale=0
2171
+ local count_customized=0 count_modified=0 count_missing=0
2172
+ local stale_data=()
2173
+
2174
+ while IFS= read -r line; do
2175
+ [[ "$line" =~ ^#.*$ ]] && continue
2176
+ [[ -z "$line" ]] && continue
2177
+
2178
+ local entry_type entry_path
2179
+ entry_type=$(echo "$line" | awk '{print $1}')
2180
+ entry_path=$(echo "$line" | awk '{print $2}')
2181
+
2182
+ local source_abs="$SCRIPT_DIR/$entry_path"
2183
+ local target_abs=""
2184
+ local display_name
2185
+ display_name=$(basename "$entry_path")
2186
+
2187
+ case "$entry_type" in
2188
+ agent) target_abs="$target_dir/.claude/agents/$display_name" ;;
2189
+ skill|skill-dir) target_abs="$target_dir/.claude/skills/$display_name" ;;
2190
+ hook) target_abs="$target_dir/.claude/hooks/$display_name" ;;
2191
+ railway-skill-dir) target_abs="$target_dir/.claude/skills/$display_name" ;;
2192
+ *) continue ;;
2193
+ esac
2194
+
2195
+ if [ ! -e "$source_abs" ]; then
2196
+ continue
2197
+ fi
2198
+
2199
+ local classification
2200
+ classification=$(classify_file "$source_abs" "$target_abs")
2201
+
2202
+ local source_size target_size
2203
+ source_size=$(get_size "$source_abs")
2204
+ target_size=$(get_size "$target_abs")
2205
+
2206
+ case "$classification" in
2207
+ SYMLINK)
2208
+ local link_target
2209
+ link_target=$(readlink "$target_abs" 2>/dev/null || true)
2210
+ section_symlinks="${section_symlinks} ✓ $display_name → $link_target\n"
2211
+ count_symlinks=$((count_symlinks + 1))
2212
+ ;;
2213
+ IDENTICAL)
2214
+ section_identical="${section_identical} = $display_name (${target_size}B = ${source_size}B)\n"
2215
+ count_identical=$((count_identical + 1))
2216
+ ;;
2217
+ STALE)
2218
+ local ratio=$((target_size * 100 / source_size))
2219
+ stale_data+=("$source_abs|$target_abs|$display_name|$source_size|$target_size|$ratio")
2220
+ section_stale="${section_stale} ⚠ $display_name (${target_size}B vs ${source_size}B toolkit — ${ratio}%)\n"
2221
+ count_stale=$((count_stale + 1))
2222
+ ;;
2223
+ CUSTOMIZED)
2224
+ local ratio=$((target_size * 100 / source_size))
2225
+ section_customized="${section_customized} ★ $display_name (${target_size}B vs ${source_size}B — ${ratio}%)\n"
2226
+ count_customized=$((count_customized + 1))
2227
+ ;;
2228
+ MODIFIED)
2229
+ local ratio=$((target_size * 100 / source_size))
2230
+ section_modified="${section_modified} ~ $display_name (${target_size}B vs ${source_size}B — ${ratio}%)\n"
2231
+ count_modified=$((count_modified + 1))
2232
+ ;;
2233
+ MISSING)
2234
+ section_missing="${section_missing} - $display_name\n"
2235
+ count_missing=$((count_missing + 1))
2236
+ ;;
2237
+ esac
2238
+ done < "$MANIFEST"
2239
+
2240
+ # Display sections
2241
+ if [ $count_symlinks -gt 0 ]; then
2242
+ echo -e " ${GREEN}SYMLINKS${NC} (managed by toolkit — auto-updated):"
2243
+ echo -e "$section_symlinks"
2244
+ fi
2245
+
2246
+ if [ $count_identical -gt 0 ]; then
2247
+ echo -e " ${CYAN}IDENTICAL${NC} (can safely convert to symlink):"
2248
+ echo -e "$section_identical"
2249
+ fi
2250
+
2251
+ if [ ${#stale_data[@]} -gt 0 ]; then
2252
+ echo -e " ${YELLOW}STALE${NC} (recommend upgrade — review diffs below):"
2253
+ for _stale_entry in "${stale_data[@]}"; do
2254
+ IFS='|' read -r _src _tgt _dname _ssize _tsize _ratio <<< "$_stale_entry"
2255
+ echo -e " ⚠ $_dname (${_tsize}B vs ${_ssize}B toolkit — ${_ratio}%)"
2256
+ # Show preview of first line from each version
2257
+ local _your_preview="" _toolkit_preview=""
2258
+ if [ -f "$_tgt" ]; then
2259
+ _your_preview=$(head -5 "$_tgt" 2>/dev/null | grep -v '^$' | grep -v '^---$' | head -1 | cut -c1-80)
2260
+ elif [ -d "$_tgt" ]; then
2261
+ _your_preview="[directory: $(ls "$_tgt" 2>/dev/null | wc -l | tr -d ' ') files]"
2262
+ fi
2263
+ if [ -f "$_src" ]; then
2264
+ _toolkit_preview=$(head -5 "$_src" 2>/dev/null | grep -v '^$' | grep -v '^---$' | head -1 | cut -c1-80)
2265
+ elif [ -d "$_src" ]; then
2266
+ _toolkit_preview="[directory: $(ls "$_src" 2>/dev/null | wc -l | tr -d ' ') files]"
2267
+ fi
2268
+ echo -e " ${DIM}--- your version ---${NC}"
2269
+ echo -e " $_your_preview"
2270
+ echo -e " ${DIM}--- toolkit version ---${NC}"
2271
+ echo -e " $_toolkit_preview"
2272
+ echo -e " ${DIM}→ --upgrade will ASK before converting this file.${NC}"
2273
+ echo ""
2274
+ done
2275
+ fi
2276
+
2277
+ if [ $count_customized -gt 0 ]; then
2278
+ echo -e " ${BOLD}CUSTOMIZED${NC} (project-specific — will not touch):"
2279
+ echo -e "$section_customized"
2280
+ fi
2281
+
2282
+ if [ $count_modified -gt 0 ]; then
2283
+ echo -e " ${DIM}MODIFIED${NC} (similar size but different — will not touch):"
2284
+ echo -e "$section_modified"
2285
+ fi
2286
+
2287
+ if [ $count_missing -gt 0 ]; then
2288
+ echo -e " ${RED}MISSING${NC} (not installed):"
2289
+ echo -e "$section_missing"
2290
+ fi
2291
+
2292
+ # Scan for project-only items (exist in .claude/ but NOT in manifest)
2293
+ local section_project_only=""
2294
+ local count_project_only=0
2295
+ if [ -d "$target_dir/.claude/agents" ]; then
2296
+ for f in "$target_dir/.claude/agents"/*.md; do
2297
+ [ ! -f "$f" ] && continue
2298
+ [ -L "$f" ] && continue
2299
+ local bname
2300
+ bname=$(basename "$f")
2301
+ if ! grep -q "agents/$bname" "$MANIFEST" 2>/dev/null; then
2302
+ section_project_only="${section_project_only} ◆ agents/$bname\n"
2303
+ count_project_only=$((count_project_only + 1))
2304
+ fi
2305
+ done
2306
+ fi
2307
+ if [ -d "$target_dir/.claude/skills" ]; then
2308
+ for d in "$target_dir/.claude/skills"/*/; do
2309
+ [ ! -d "$d" ] && continue
2310
+ [ -L "${d%/}" ] && continue
2311
+ local bname
2312
+ bname=$(basename "$d")
2313
+ if ! grep -q "skills/$bname" "$MANIFEST" 2>/dev/null; then
2314
+ section_project_only="${section_project_only} ◆ skills/$bname\n"
2315
+ count_project_only=$((count_project_only + 1))
2316
+ fi
2317
+ done
2318
+ fi
2319
+ if [ -d "$target_dir/.claude/hooks" ]; then
2320
+ for f in "$target_dir/.claude/hooks"/*.sh; do
2321
+ [ ! -f "$f" ] && continue
2322
+ [ -L "$f" ] && continue
2323
+ local bname
2324
+ bname=$(basename "$f")
2325
+ if ! grep -q "hooks/$bname" "$MANIFEST" 2>/dev/null; then
2326
+ section_project_only="${section_project_only} ◆ hooks/$bname\n"
2327
+ count_project_only=$((count_project_only + 1))
2328
+ fi
2329
+ done
2330
+ fi
2331
+
2332
+ if [ $count_project_only -gt 0 ]; then
2333
+ echo -e " ${DIM}PROJECT-ONLY${NC} (no toolkit equivalent):"
2334
+ echo -e "$section_project_only"
2335
+ fi
2336
+
2337
+ # Summary box
2338
+ local _skip_count=$((count_customized + count_modified + count_project_only))
2339
+ echo ""
2340
+ echo "─── What --upgrade would do ────────────────────────────────"
2341
+ printf " Auto-convert: %3d (identical files → symlink, backed up)\n" "$count_identical"
2342
+ printf " Ask you first: %3d (stale files — you decide per file)\n" "$count_stale"
2343
+ printf " Create new: %3d (missing toolkit items)\n" "$count_missing"
2344
+ printf " Skip: %3d (modified + customized + project-only)\n" "$_skip_count"
2345
+ echo ""
2346
+ echo -e " Backups saved to: ${CYAN}.claude/$BACKUP_DIR_NAME/${NC}"
2347
+ echo " Each converted file gets a .pre-upgrade copy."
2348
+ echo "────────────────────────────────────────────────────────────"
2349
+
2350
+ # Config status
2351
+ if config_exists "$target_dir"; then
2352
+ load_config "$target_dir"
2353
+ echo -e " CONFIG: ${GREEN}found${NC} (categories: $CONF_INSTALLED_CATEGORIES)"
2354
+ else
2355
+ echo -e " CONFIG: ${YELLOW}⚠ No .claude-agents.conf found${NC}"
2356
+ local derived
2357
+ derived=$(generate_config_from_existing "$target_dir" "true")
2358
+ if [ -n "$derived" ]; then
2359
+ echo " → Will generate from existing files"
2360
+ echo " → Derived categories: $derived"
2361
+ fi
2362
+ fi
2363
+
2364
+ echo ""
2365
+ echo -e "${DIM}Run install.sh --upgrade to apply changes.${NC}"
2366
+ echo -e "${DIM} Add --yes to auto-approve all stale conversions (CI/non-interactive mode).${NC}"
2367
+ echo -e "${DIM} Add --skip file1,file2 to exclude specific items.${NC}"
2368
+ }
2369
+
2370
+ # ============================================================================
2371
+ # Brownfield Upgrade (--upgrade)
2372
+ # ============================================================================
2373
+
2374
+ handle_upgrade() {
2375
+ local target_dir="$1"
2376
+
2377
+ echo -e "${BOLD}Claude Agents — Brownfield Upgrade${NC}"
2378
+ echo -e "Target: ${CYAN}$target_dir${NC}"
2379
+ echo ""
2380
+
2381
+ # Step 1: Assess current state
2382
+ echo -e "${BOLD}Step 1/4:${NC} Assessing current state..."
2383
+
2384
+ # Step 2: Generate config if missing
2385
+ echo -e "${BOLD}Step 2/4:${NC} Generating config..."
2386
+ if ! config_exists "$target_dir"; then
2387
+ local derived_categories
2388
+ derived_categories=$(generate_config_from_existing "$target_dir")
2389
+ echo -e " Derived categories: ${GREEN}$derived_categories${NC}"
2390
+ else
2391
+ load_config "$target_dir"
2392
+ echo -e " Using existing config: ${GREEN}$CONF_INSTALLED_CATEGORIES${NC}"
2393
+ fi
2394
+ echo ""
2395
+
2396
+ # Load config (either existing or newly generated)
2397
+ load_config "$target_dir"
2398
+
2399
+ local filter_items
2400
+ filter_items=$(items_for_categories "$CONF_INSTALLED_CATEGORIES")
2401
+
2402
+ # Ensure directories
2403
+ mkdir -p "$target_dir/.claude/agents"
2404
+ mkdir -p "$target_dir/.claude/skills"
2405
+ mkdir -p "$target_dir/.claude/hooks"
2406
+
2407
+ # Snapshot backup
2408
+ snapshot_pre_install "$target_dir"
2409
+
2410
+ # Step 3: Converting files
2411
+ echo -e "${BOLD}Step 3/4:${NC} Converting files..."
2412
+ echo ""
2413
+
2414
+ # Reset deferred stale array
2415
+ STALE_DEFERRED=()
2416
+
2417
+ # Process manifest with smart_sync (identical auto-converts, stale deferred)
2418
+ process_manifest "$target_dir" "smart" "false" "$filter_items"
2419
+
2420
+ # Report auto-converted identical files
2421
+ if [ $CONVERTED -gt 0 ]; then
2422
+ echo -e " ${CYAN}IDENTICAL files${NC} (auto-converted — content is the same):"
2423
+ echo -e " ✓ Converted $CONVERTED identical files to symlinks"
2424
+ echo ""
2425
+ fi
2426
+
2427
+ # Handle stale items interactively (or auto with --yes)
2428
+ if [ ${#STALE_DEFERRED[@]} -gt 0 ]; then
2429
+ echo -e " ${YELLOW}STALE files${NC} (asking per file):"
2430
+ echo ""
2431
+ for entry in "${STALE_DEFERRED[@]}"; do
2432
+ IFS='|' read -r src tgt name <<< "$entry"
2433
+ # Check --skip list
2434
+ if [ -n "$SKIP_ITEMS" ] && echo ",$SKIP_ITEMS," | grep -q ",$name,"; then
2435
+ echo -e " ${DIM}Skipped${NC} $name (--skip)"
2436
+ SKIPPED=$((SKIPPED + 1))
2437
+ continue
2438
+ fi
2439
+ prompt_stale_conversion "$src" "$tgt" "$name"
2440
+ done
2441
+ echo ""
2442
+ fi
2443
+
2444
+ # Report created items
2445
+ if [ $CREATED -gt 0 ]; then
2446
+ echo -e " Creating missing toolkit items..."
2447
+ echo -e " ✓ Created $CREATED new symlinks"
2448
+ echo ""
2449
+ fi
2450
+
2451
+ # Merge settings.json hooks
2452
+ local hooks_to_add
2453
+ hooks_to_add=$(hooks_for_categories "$CONF_INSTALLED_CATEGORIES")
2454
+ if [ -n "$hooks_to_add" ]; then
2455
+ merge_settings_hooks "$target_dir/.claude/settings.json" "add" "$hooks_to_add"
2456
+ fi
2457
+
2458
+ # Update .gitignore
2459
+ update_gitignore_block "$target_dir" "$filter_items"
2460
+
2461
+ # Update CLAUDE.md managed block (ask — offer to inject if not present)
2462
+ update_claudemd_block "$target_dir" "ask"
2463
+
2464
+ # Make hooks executable
2465
+ for hook in "$target_dir/.claude/hooks/"*.sh; do
2466
+ if [ -f "$hook" ] || [ -L "$hook" ]; then
2467
+ chmod +x "$hook" 2>/dev/null || true
2468
+ fi
2469
+ done
2470
+
2471
+ # Post-install record
2472
+ post_install_record "$target_dir"
2473
+
2474
+ # Step 4: Summary
2475
+ echo ""
2476
+ echo -e "${BOLD}Step 4/4: Done!${NC}"
2477
+ echo -e " Converted: ${GREEN}$CONVERTED${NC} (identical → symlink)"
2478
+ echo -e " Upgraded: ${GREEN}$UPGRADED${NC} (stale → symlink, user approved)"
2479
+ echo -e " Kept: ${CYAN}$KEPT${NC} (stale → user chose to keep)"
2480
+ echo -e " Created: ${GREEN}$CREATED${NC} (new)"
2481
+ echo -e " Skipped: ${YELLOW}$SKIPPED${NC} (modified/customized)"
2482
+ echo -e " Updated: ${GREEN}$UPDATED${NC}"
2483
+ if [ "$ERRORS" -gt 0 ]; then
2484
+ echo -e " Errors: ${RED}$ERRORS${NC}"
2485
+ fi
2486
+ echo ""
2487
+ echo -e " Backups at: ${CYAN}.claude/$BACKUP_DIR_NAME/${NC}"
2488
+ }
2489
+
2490
+ # ============================================================================
2491
+ # Generate Config (--generate-config)
2492
+ # ============================================================================
2493
+
2494
+ handle_generate_config() {
2495
+ local target_dir="$1"
2496
+
2497
+ if config_exists "$target_dir"; then
2498
+ echo -e "${YELLOW}Config already exists at$(config_path "$target_dir")${NC}"
2499
+ echo "Use --setup to change selections."
2500
+ return 0
2501
+ fi
2502
+
2503
+ local derived
2504
+ derived=$(generate_config_from_existing "$target_dir")
2505
+ echo -e "${GREEN}Generated config${NC} with categories: $derived"
2506
+ }
2507
+
2508
+ # ============================================================================
2509
+ # Main
2510
+ # ============================================================================
2511
+
2512
+ TARGET_DIR=""
2513
+ MODE="sync" # sync | setup | uninstall | sync-from-config | init | convert | status | assess | upgrade | generate-config | store-key | check-license
2514
+ INSTALL_RAILWAY=false
2515
+ LICENSE_KEY=""
2516
+ CATEGORIES_ARG=""
2517
+ JSON_OUTPUT=false
2518
+
2519
+ # Parse arguments
2520
+ while [ $# -gt 0 ]; do
2521
+ case $1 in
2522
+ --setup)
2523
+ MODE="setup"
2524
+ shift
2525
+ ;;
2526
+ --uninstall)
2527
+ MODE="uninstall"
2528
+ shift
2529
+ ;;
2530
+ --sync-from-config)
2531
+ MODE="sync-from-config"
2532
+ shift
2533
+ ;;
2534
+ --init)
2535
+ MODE="init"
2536
+ shift
2537
+ ;;
2538
+ --convert)
2539
+ MODE="convert"
2540
+ shift
2541
+ ;;
2542
+ --assess)
2543
+ MODE="assess"
2544
+ shift
2545
+ ;;
2546
+ --upgrade)
2547
+ MODE="upgrade"
2548
+ shift
2549
+ ;;
2550
+ --skip)
2551
+ SKIP_ITEMS="${2:-}"
2552
+ shift
2553
+ [ $# -gt 0 ] && shift
2554
+ ;;
2555
+ --yes|-y)
2556
+ FORCE_YES=true
2557
+ shift
2558
+ ;;
2559
+ --generate-config)
2560
+ MODE="generate-config"
2561
+ shift
2562
+ ;;
2563
+ --status)
2564
+ MODE="status"
2565
+ shift
2566
+ ;;
2567
+ --key)
2568
+ MODE="store-key"
2569
+ LICENSE_KEY="${2:-}"
2570
+ shift
2571
+ [ $# -gt 0 ] && shift
2572
+ ;;
2573
+ --railway)
2574
+ INSTALL_RAILWAY=true
2575
+ shift
2576
+ ;;
2577
+ --check-license-only)
2578
+ MODE="check-license"
2579
+ shift
2580
+ ;;
2581
+ --force-remove-hooks)
2582
+ MODE="force-remove-hooks"
2583
+ FORCE_HOOKS_TARGET="${2:-}"
2584
+ FORCE_HOOKS_LIST="${3:-}"
2585
+ shift
2586
+ [ $# -gt 0 ] && shift
2587
+ [ $# -gt 0 ] && shift
2588
+ ;;
2589
+ --revoke)
2590
+ MODE="revoke"
2591
+ REVOKE_LABEL="${2:-}"
2592
+ shift
2593
+ [ $# -gt 0 ] && shift
2594
+ ;;
2595
+ --add-user)
2596
+ MODE="add-user"
2597
+ ADD_USER_LABEL="${2:-}"
2598
+ shift
2599
+ [ $# -gt 0 ] && shift
2600
+ ;;
2601
+ --list-users)
2602
+ MODE="list-users"
2603
+ shift
2604
+ ;;
2605
+ --categories)
2606
+ CATEGORIES_ARG="${2:-}"
2607
+ shift
2608
+ [ $# -gt 0 ] && shift
2609
+ ;;
2610
+ --bundle)
2611
+ # Install a named bundle (e.g., forge, spark, prime)
2612
+ # Delegates to bin/cli.js install for compiled plugin mode.
2613
+ BUNDLE_NAME="${2:-}"
2614
+ if [ -z "$BUNDLE_NAME" ]; then
2615
+ echo "Error: --bundle requires a bundle name" >&2
2616
+ echo "Available bundles: forge spark scalpel sentinel prism canvas compass counsel shield cruise prime" >&2
2617
+ exit 1
2618
+ fi
2619
+ shift
2620
+ [ $# -gt 0 ] && shift
2621
+ # Determine target path (next arg if present and not a flag)
2622
+ BUNDLE_TARGET="${1:-}"
2623
+ if [[ -n "$BUNDLE_TARGET" && ! "$BUNDLE_TARGET" =~ ^-- ]]; then
2624
+ shift
2625
+ else
2626
+ BUNDLE_TARGET="."
2627
+ fi
2628
+ BUNDLE_TARGET="$(cd "$BUNDLE_TARGET" && pwd)"
2629
+ CLI_JS="$SCRIPT_DIR/bin/cli.js"
2630
+ if command -v node &>/dev/null && [ -f "$CLI_JS" ]; then
2631
+ exec node "$CLI_JS" install "$BUNDLE_NAME" "$BUNDLE_TARGET"
2632
+ else
2633
+ echo "Error: node not found or bin/cli.js missing. Cannot install bundle." >&2
2634
+ echo "Ensure Node.js is installed and the toolkit is fully set up." >&2
2635
+ exit 1
2636
+ fi
2637
+ ;;
2638
+ --json-output)
2639
+ JSON_OUTPUT=true
2640
+ shift
2641
+ ;;
2642
+ --help|-h)
2643
+ echo -e "${BOLD}Claude Agents — Symlink Install System${NC}"
2644
+ echo ""
2645
+ echo "Usage:"
2646
+ echo " install.sh [path] Sync (auto-detects if setup needed)"
2647
+ echo " install.sh --setup [path] Interactive setup (pick categories)"
2648
+ echo " install.sh --uninstall [path] Clean uninstall (restore everything)"
2649
+ echo " install.sh --sync-from-config [path] Sync configured categories only"
2650
+ echo " install.sh --init [path] Scaffold new project + sync"
2651
+ echo " install.sh --convert [path] Replace matching copies with symlinks"
2652
+ echo " install.sh --assess [path] Brownfield assessment (read-only)"
2653
+ echo " install.sh --upgrade [path] Brownfield upgrade (converts stale/identical)"
2654
+ echo " --yes Auto-approve all stale conversions (CI mode)"
2655
+ echo " --skip file1,file2 Exclude specific items from conversion"
2656
+ echo " install.sh --generate-config [path] Generate config from existing files"
2657
+ echo " install.sh --status [path] Show what's linked/overridden/missing"
2658
+ echo " install.sh --key KEY Store your license key"
2659
+ echo " install.sh --railway Include Railway deployment skills"
2660
+ echo " install.sh --categories cat1,cat2 Install specific categories (non-interactive)"
2661
+ echo " install.sh --bundle NAME [path] Install a compiled plugin bundle (requires node)"
2662
+ echo " install.sh --json-output Output JSON summary on completion"
2663
+ echo ""
2664
+ echo "Admin commands (Arth employees only — requires repo write access):"
2665
+ echo " install.sh --add-user NAME Generate a license key for a new user"
2666
+ echo " install.sh --revoke NAME Revoke a user's license (removes on next session)"
2667
+ echo " install.sh --list-users List all licensed users"
2668
+ echo ""
2669
+ echo " install.sh --help Show this help"
2670
+ echo ""
2671
+ echo "Categories:"
2672
+ echo " core Sync skill, explore-light agent, auto-update hook (always included)"
2673
+ echo " strategy Strategy & Design agents (architect, PM, GTM, design-studio)"
2674
+ echo " development Development agents + planning, implement, PR, issue skills"
2675
+ echo " quality QA agents + QA skills"
2676
+ echo " operations SRE agent + operations skills"
2677
+ echo " hooks Triage router + git worktree sync"
2678
+ echo " guardrails Safety hooks + onboard, autopilot skills"
2679
+ echo " railway Railway deployment skills"
2680
+ echo " superpowers Superpowers + deep explore skills"
2681
+ echo ""
2682
+ echo "First install auto-launches interactive setup."
2683
+ echo "Subsequent runs do silent sync respecting your selections."
2684
+ echo ""
2685
+ echo "Symlink behavior (sync):"
2686
+ echo " Symlink exists → Update (managed by us)"
2687
+ echo " Regular file exists → Skip (your project override)"
2688
+ echo " Doesn't exist → Create symlink"
2689
+ exit 0
2690
+ ;;
2691
+ *)
2692
+ TARGET_DIR="$1"
2693
+ shift
2694
+ ;;
2695
+ esac
2696
+ done
2697
+
2698
+ # Handle --key mode (no license check needed)
2699
+ if [ "$MODE" = "store-key" ]; then
2700
+ if [ -z "$LICENSE_KEY" ]; then
2701
+ echo -e "${RED}Usage: $0 --key ARTH-XXXX-XXXX-XXXX-XXXX${NC}"
2702
+ exit 1
2703
+ fi
2704
+ echo "$LICENSE_KEY" > "$LICENSE_FILE"
2705
+ echo -e "${GREEN}License key stored.${NC}"
2706
+
2707
+ # Validate immediately
2708
+ if check_license 2>/dev/null; then
2709
+ echo -e "${GREEN}Key validated successfully.${NC}"
2710
+ fi
2711
+ exit 0
2712
+ fi
2713
+
2714
+ # Handle --check-license-only mode
2715
+ if [ "$MODE" = "check-license" ]; then
2716
+ check_license
2717
+ exit 0
2718
+ fi
2719
+
2720
+ # Handle --force-remove-hooks (used by sync-agents.sh during revocation)
2721
+ if [ "$MODE" = "force-remove-hooks" ]; then
2722
+ if [ -n "$FORCE_HOOKS_TARGET" ] && [ -f "$FORCE_HOOKS_TARGET/.claude/settings.json" ] && [ -n "$FORCE_HOOKS_LIST" ]; then
2723
+ merge_settings_hooks "$FORCE_HOOKS_TARGET/.claude/settings.json" "remove" "$FORCE_HOOKS_LIST"
2724
+ fi
2725
+ exit 0
2726
+ fi
2727
+
2728
+ # ============================================================================
2729
+ # Admin Commands (Arth employees only — requires repo write access)
2730
+ # ============================================================================
2731
+
2732
+ # Handle --list-users
2733
+ if [ "$MODE" = "list-users" ]; then
2734
+ if [ ! -f "$AUTHORIZED_KEYS" ]; then
2735
+ echo -e "${RED}authorized-keys.txt not found.${NC}"
2736
+ exit 1
2737
+ fi
2738
+ echo -e "${BOLD}Licensed Users${NC}"
2739
+ echo "---"
2740
+ current_label=""
2741
+ while IFS= read -r line; do
2742
+ # Skip empty lines
2743
+ [ -z "$line" ] && continue
2744
+ # Comment line = label
2745
+ if echo "$line" | grep -q '^#'; then
2746
+ current_label=$(echo "$line" | sed 's/^#[[:space:]]*//')
2747
+ continue
2748
+ fi
2749
+ # Hash line
2750
+ hash="$line"
2751
+ short_hash="${hash:0:12}..."
2752
+ if [ -n "$current_label" ]; then
2753
+ echo -e " ${CYAN}$current_label${NC} ($short_hash)"
2754
+ current_label=""
2755
+ else
2756
+ echo -e " ${DIM}(unlabeled)${NC} ($short_hash)"
2757
+ fi
2758
+ done < "$AUTHORIZED_KEYS"
2759
+ echo ""
2760
+ echo "Total: $(grep -v '^#' "$AUTHORIZED_KEYS" | grep -v '^$' | wc -l | tr -d ' ') key(s)"
2761
+ exit 0
2762
+ fi
2763
+
2764
+ # Handle --add-user LABEL
2765
+ if [ "$MODE" = "add-user" ]; then
2766
+ if [ -z "$ADD_USER_LABEL" ]; then
2767
+ echo -e "${RED}Usage: $0 --add-user USERNAME${NC}"
2768
+ echo " Example: $0 --add-user john"
2769
+ exit 1
2770
+ fi
2771
+ if [ ! -f "$AUTHORIZED_KEYS" ]; then
2772
+ echo -e "${RED}authorized-keys.txt not found.${NC}"
2773
+ exit 1
2774
+ fi
2775
+
2776
+ # Check for duplicate label
2777
+ if grep -q "^# $ADD_USER_LABEL$" "$AUTHORIZED_KEYS" 2>/dev/null; then
2778
+ echo -e "${RED}User '$ADD_USER_LABEL' already exists in authorized-keys.txt${NC}"
2779
+ exit 1
2780
+ fi
2781
+
2782
+ # Generate key: ARTH-XXXX-XXXX-XXXX-XXXX (uppercase alphanumeric)
2783
+ GEN_KEY="ARTH"
2784
+ for i in 1 2 3 4; do
2785
+ SEGMENT=$(openssl rand -hex 2 | tr '[:lower:]' '[:upper:]')
2786
+ GEN_KEY="${GEN_KEY}-${SEGMENT}"
2787
+ done
2788
+
2789
+ # Hash the key
2790
+ GEN_HASH=$(echo -n "$GEN_KEY" | shasum -a 256 | awk '{print $1}')
2791
+
2792
+ # Append to authorized-keys.txt
2793
+ echo "" >> "$AUTHORIZED_KEYS"
2794
+ echo "# $ADD_USER_LABEL" >> "$AUTHORIZED_KEYS"
2795
+ echo "$GEN_HASH" >> "$AUTHORIZED_KEYS"
2796
+
2797
+ echo -e "${BOLD}User added: ${CYAN}$ADD_USER_LABEL${NC}"
2798
+ echo ""
2799
+ echo -e "${BOLD}License key (give to user — store securely, shown only once):${NC}"
2800
+ echo ""
2801
+ echo -e " ${GREEN}$GEN_KEY${NC}"
2802
+ echo ""
2803
+ echo -e "Hash: ${DIM}$GEN_HASH${NC}"
2804
+ echo ""
2805
+ echo -e "${YELLOW}Next steps:${NC}"
2806
+ echo " 1. Give the key above to $ADD_USER_LABEL (securely — not in plain text chat)"
2807
+ echo " 2. Commit: git add authorized-keys.txt && git commit -m 'add license: $ADD_USER_LABEL'"
2808
+ echo " 3. Push: git push"
2809
+ echo ""
2810
+ echo " User installs with: install.sh --key $GEN_KEY"
2811
+ exit 0
2812
+ fi
2813
+
2814
+ # Handle --revoke LABEL
2815
+ if [ "$MODE" = "revoke" ]; then
2816
+ if [ -z "$REVOKE_LABEL" ]; then
2817
+ echo -e "${RED}Usage: $0 --revoke USERNAME${NC}"
2818
+ echo " Example: $0 --revoke john"
2819
+ echo ""
2820
+ echo " List users: $0 --list-users"
2821
+ exit 1
2822
+ fi
2823
+ if [ ! -f "$AUTHORIZED_KEYS" ]; then
2824
+ echo -e "${RED}authorized-keys.txt not found.${NC}"
2825
+ exit 1
2826
+ fi
2827
+
2828
+ # Find the label
2829
+ if ! grep -q "^# $REVOKE_LABEL$" "$AUTHORIZED_KEYS" 2>/dev/null; then
2830
+ echo -e "${RED}User '$REVOKE_LABEL' not found in authorized-keys.txt${NC}"
2831
+ echo ""
2832
+ echo "Available users:"
2833
+ # Show only single-word labels (user names) — lines like "# username" with no spaces in the name
2834
+ grep -E '^# [a-zA-Z0-9_-]+$' "$AUTHORIZED_KEYS" | sed 's/^# / /'
2835
+ exit 1
2836
+ fi
2837
+
2838
+ # Remove the label line and the hash line immediately after it
2839
+ tmp=$(mktemp)
2840
+ skip_next=false
2841
+ while IFS= read -r line; do
2842
+ if $skip_next; then
2843
+ # This is the hash line — skip it
2844
+ skip_next=false
2845
+ continue
2846
+ fi
2847
+ if [ "$line" = "# $REVOKE_LABEL" ]; then
2848
+ skip_next=true
2849
+ continue
2850
+ fi
2851
+ echo "$line" >> "$tmp"
2852
+ done < "$AUTHORIZED_KEYS"
2853
+ mv "$tmp" "$AUTHORIZED_KEYS"
2854
+
2855
+ echo -e "${BOLD}License revoked: ${RED}$REVOKE_LABEL${NC}"
2856
+ echo ""
2857
+ echo -e "${YELLOW}What happens next:${NC}"
2858
+ echo " 1. Commit & push this change to propagate revocation"
2859
+ echo " 2. On their next Claude Code session, sync-agents.sh will:"
2860
+ echo " a. Pull the updated authorized-keys.txt"
2861
+ echo " b. Detect the license is no longer valid"
2862
+ echo " c. Remove all toolkit symlinks from their project"
2863
+ echo " d. Remove hook entries from settings.json"
2864
+ echo " e. Remove the managed CLAUDE.md block"
2865
+ echo " f. Display: 'License revoked. Toolkit has been disabled.'"
2866
+ echo ""
2867
+ echo -e "${YELLOW}To complete revocation:${NC}"
2868
+ echo " git add authorized-keys.txt && git commit -m 'revoke license: $REVOKE_LABEL' && git push"
2869
+ exit 0
2870
+ fi
2871
+
2872
+ # --status and --assess work WITHOUT a license (users can inspect before buying)
2873
+ if [ "$MODE" = "status" ] || [ "$MODE" = "assess" ]; then
2874
+ if [ -z "$TARGET_DIR" ]; then
2875
+ TARGET_DIR="$(pwd)"
2876
+ fi
2877
+ TARGET_DIR="$(cd "$TARGET_DIR" 2>/dev/null && pwd)" || {
2878
+ echo -e "${RED}Error: $TARGET_DIR is not a valid directory${NC}"
2879
+ exit 1
2880
+ }
2881
+ if [ "$MODE" = "status" ]; then
2882
+ show_status "$TARGET_DIR" "$INSTALL_RAILWAY"
2883
+ else
2884
+ handle_assess "$TARGET_DIR"
2885
+ fi
2886
+ exit 0
2887
+ fi
2888
+
2889
+ # All other modes require a valid license
2890
+ check_license
2891
+
2892
+ # Default target to current directory
2893
+ if [ -z "$TARGET_DIR" ]; then
2894
+ TARGET_DIR="$(pwd)"
2895
+ fi
2896
+
2897
+ # Resolve to absolute path
2898
+ TARGET_DIR="$(cd "$TARGET_DIR" 2>/dev/null && pwd)" || {
2899
+ echo -e "${RED}Error: $TARGET_DIR is not a valid directory${NC}"
2900
+ exit 1
2901
+ }
2902
+
2903
+ # Verify manifest exists
2904
+ if [ ! -f "$MANIFEST" ]; then
2905
+ echo -e "${RED}Error: portable.manifest not found. Re-clone the repo.${NC}"
2906
+ exit 1
2907
+ fi
2908
+
2909
+ # ---- Mode dispatch ----
2910
+
2911
+ # Handle --uninstall
2912
+ if [ "$MODE" = "uninstall" ]; then
2913
+ handle_uninstall "$TARGET_DIR"
2914
+ exit 0
2915
+ fi
2916
+
2917
+ # Handle --setup (explicit interactive setup)
2918
+ if [ "$MODE" = "setup" ]; then
2919
+ handle_setup "$TARGET_DIR"
2920
+ exit 0
2921
+ fi
2922
+
2923
+ # Handle --upgrade (brownfield upgrade)
2924
+ if [ "$MODE" = "upgrade" ]; then
2925
+ handle_upgrade "$TARGET_DIR"
2926
+ exit 0
2927
+ fi
2928
+
2929
+ # Handle --generate-config (generate config from existing files)
2930
+ if [ "$MODE" = "generate-config" ]; then
2931
+ handle_generate_config "$TARGET_DIR"
2932
+ exit 0
2933
+ fi
2934
+
2935
+ # Handle --sync-from-config (used by sync hook)
2936
+ if [ "$MODE" = "sync-from-config" ]; then
2937
+ if handle_sync_from_config "$TARGET_DIR"; then
2938
+ exit 0
2939
+ fi
2940
+ # Falls through to full sync if no config found
2941
+ MODE="sync"
2942
+ fi
2943
+
2944
+ # Handle --init
2945
+ if [ "$MODE" = "init" ]; then
2946
+ echo -e "${BOLD}Claude Agents${NC} — Installing into: ${CYAN}$TARGET_DIR${NC}"
2947
+ echo ""
2948
+ scaffold_project "$TARGET_DIR"
2949
+
2950
+ # After scaffolding, run setup if interactive
2951
+ if [ -t 0 ]; then
2952
+ handle_setup "$TARGET_DIR"
2953
+ else
2954
+ # Non-interactive init — install everything
2955
+ mkdir -p "$TARGET_DIR/.claude/agents"
2956
+ mkdir -p "$TARGET_DIR/.claude/skills"
2957
+ mkdir -p "$TARGET_DIR/.claude/hooks"
2958
+ process_manifest "$TARGET_DIR" "safe" "$INSTALL_RAILWAY"
2959
+
2960
+ for hook in "$TARGET_DIR/.claude/hooks/"*.sh; do
2961
+ if [ -f "$hook" ] || [ -L "$hook" ]; then
2962
+ chmod +x "$hook" 2>/dev/null || true
2963
+ fi
2964
+ done
2965
+
2966
+ echo ""
2967
+ echo -e "${BOLD}Done!${NC}"
2968
+ echo -e " Created: ${GREEN}$CREATED${NC}"
2969
+ echo -e " Updated: ${GREEN}$UPDATED${NC}"
2970
+ echo -e " Skipped: ${YELLOW}$SKIPPED${NC} (project overrides)"
2971
+
2972
+ if [ "$JSON_OUTPUT" = "true" ]; then
2973
+ emit_json_summary "init" "$TARGET_DIR"
2974
+ elif [ -t 1 ]; then
2975
+ echo ""
2976
+ echo -e "${BOLD}Next steps:${NC}"
2977
+ echo -e " ${CYAN}cd $(basename "$TARGET_DIR") && claude${NC} Start Claude Code in this project"
2978
+ echo -e " ${CYAN}/calibrate${NC} Deep-learn your project (first session — run this once)"
2979
+ echo ""
2980
+ echo -e " ${DIM}/calibrate reads your source code to learn architecture patterns, coding"
2981
+ echo -e " conventions, and domain language. It then recommends and installs MCP servers,"
2982
+ echo -e " agents, skills, and workflows tailored to your project.${NC}"
2983
+ echo ""
2984
+ if command -v clade &>/dev/null; then
2985
+ echo -e " ${CYAN}clade sync${NC} Translate toolkit to Cursor/Codex/Kiro/Gemini"
2986
+ fi
2987
+ if command -v arth &>/dev/null; then
2988
+ echo -e " ${CYAN}arth setup .${NC} Full setup: toolkit + multi-engine + dashboard"
2989
+ fi
2990
+ echo -e " ${CYAN}install.sh --status .${NC} See what's linked/overridden"
2991
+ echo -e " ${CYAN}install.sh --setup .${NC} Change category selections"
2992
+ fi
2993
+ fi
2994
+ exit 0
2995
+ fi
2996
+
2997
+ # ---- Default sync mode ----
2998
+ # Smart first-run detection:
2999
+ # config exists → sync from config (silent)
3000
+ # --categories provided → use those directly (non-interactive)
3001
+ # no .claude/ AND no config → greenfield, run init flow (interactive)
3002
+ # .claude/ exists, no config → suggest --assess (interactive)
3003
+ # interactive + no config → launch setup
3004
+
3005
+ if config_exists "$TARGET_DIR"; then
3006
+ # Config exists — sync from config silently
3007
+ if handle_sync_from_config "$TARGET_DIR"; then
3008
+ if [ "$JSON_OUTPUT" = "true" ]; then
3009
+ emit_json_summary "sync-from-config" "$TARGET_DIR"
3010
+ fi
3011
+ exit 0
3012
+ fi
3013
+ fi
3014
+
3015
+ # --categories provided → use those directly (non-interactive)
3016
+ if [ -n "$CATEGORIES_ARG" ]; then
3017
+ cats="${CATEGORIES_ARG//,/ }"
3018
+ # Validate categories
3019
+ for cat in $cats; do
3020
+ label=$(get_category_label "$cat")
3021
+ if [ -z "$label" ]; then
3022
+ echo -e "${RED}Unknown category: $cat${NC}" >&2
3023
+ echo "Valid categories: core, strategy, development, quality, operations, hooks, guardrails, railway, superpowers" >&2
3024
+ echo "" >&2
3025
+ echo "Example: install.sh --categories strategy,development,railway ." >&2
3026
+ exit 1
3027
+ fi
3028
+ done
3029
+ # Ensure core is always included
3030
+ if ! echo " $cats " | grep -q " core "; then
3031
+ cats="core $cats"
3032
+ fi
3033
+ echo -e "${BOLD}Claude Agents${NC} — Installing into: ${CYAN}$TARGET_DIR${NC}"
3034
+ echo -e "Categories: ${GREEN}$cats${NC}"
3035
+ echo ""
3036
+ mkdir -p "$TARGET_DIR/.claude/agents"
3037
+ mkdir -p "$TARGET_DIR/.claude/skills"
3038
+ mkdir -p "$TARGET_DIR/.claude/hooks"
3039
+ snapshot_pre_install "$TARGET_DIR"
3040
+ write_config "$TARGET_DIR" "$cats"
3041
+ handle_sync_from_config "$TARGET_DIR"
3042
+ if [ "$JSON_OUTPUT" = "true" ]; then
3043
+ emit_json_summary "categories" "$TARGET_DIR"
3044
+ elif [ -t 1 ]; then
3045
+ echo ""
3046
+ echo -e "${DIM}Change selections: ${CYAN}install.sh --setup .${NC}${DIM} | See status: ${CYAN}install.sh --status .${NC}"
3047
+ fi
3048
+ exit 0
3049
+ fi
3050
+
3051
+ if [ ! -d "$TARGET_DIR/.claude" ] && [ ! -f "$(config_path "$TARGET_DIR")" ]; then
3052
+ # No .claude/ AND no config → greenfield, run init flow
3053
+ if [ -t 0 ]; then
3054
+ echo -e "${BOLD}Claude Agents${NC} — New project detected: ${CYAN}$TARGET_DIR${NC}"
3055
+ echo ""
3056
+ scaffold_project "$TARGET_DIR"
3057
+ handle_setup "$TARGET_DIR"
3058
+ exit 0
3059
+ fi
3060
+ fi
3061
+
3062
+ if [ -d "$TARGET_DIR/.claude" ] && [ ! -f "$(config_path "$TARGET_DIR")" ]; then
3063
+ # .claude/ exists but no config → suggest --assess
3064
+ if [ -t 0 ]; then
3065
+ echo -e "${YELLOW}Existing .claude/ directory found but no claude-agents config.${NC}"
3066
+ echo ""
3067
+ echo "This project may have manually-created .claude/ files."
3068
+ echo "Options:"
3069
+ echo " install.sh --setup . Interactive setup — pick categories to install"
3070
+ echo " install.sh --categories cat1,cat2 . Non-interactive install of specific categories"
3071
+ echo " install.sh --assess . See what you have vs the toolkit"
3072
+ echo " install.sh --generate-config . Generate config from existing files"
3073
+ echo ""
3074
+ echo "Available categories: core (always), strategy, development, quality,"
3075
+ echo " operations, hooks, guardrails, railway, superpowers"
3076
+ exit 0
3077
+ fi
3078
+ fi
3079
+
3080
+ if [ -t 0 ] && [ ! -f "$(config_path "$TARGET_DIR")" ]; then
3081
+ # Interactive + no config → launch setup
3082
+ handle_setup "$TARGET_DIR"
3083
+ exit 0
3084
+ fi
3085
+
3086
+ # Fallback: no config, non-interactive — install defaults with category awareness
3087
+ # Use default categories (all on except railway, superpowers)
3088
+ FALLBACK_CATEGORIES="core strategy development quality operations hooks guardrails"
3089
+ if [ "$INSTALL_RAILWAY" = "true" ]; then
3090
+ FALLBACK_CATEGORIES="$FALLBACK_CATEGORIES railway"
3091
+ fi
3092
+
3093
+ echo -e "${BOLD}Claude Agents${NC} — Installing into: ${CYAN}$TARGET_DIR${NC}"
3094
+ echo ""
3095
+ echo -e "${DIM}Installing default categories: $FALLBACK_CATEGORIES${NC}"
3096
+ echo -e "${DIM}Tip: Use ${CYAN}--setup${DIM} for interactive selection or ${CYAN}--categories${DIM} for specific categories${NC}"
3097
+ echo ""
3098
+
3099
+ # Build filter list from fallback categories
3100
+ FALLBACK_ITEMS=$(items_for_categories "$FALLBACK_CATEGORIES")
3101
+
3102
+ # Ensure base directories exist
3103
+ mkdir -p "$TARGET_DIR/.claude/agents"
3104
+ mkdir -p "$TARGET_DIR/.claude/skills"
3105
+ mkdir -p "$TARGET_DIR/.claude/hooks"
3106
+
3107
+ if [ "$MODE" = "convert" ]; then
3108
+ echo -e "Mode: ${YELLOW}convert${NC} (replacing matching copies with symlinks)"
3109
+ process_manifest "$TARGET_DIR" "convert" "false" "$FALLBACK_ITEMS"
3110
+ else
3111
+ echo -e "Mode: ${GREEN}sync${NC} (safe symlinks — won't touch regular files)"
3112
+ process_manifest "$TARGET_DIR" "safe" "false" "$FALLBACK_ITEMS"
3113
+ fi
3114
+
3115
+ # Write config so subsequent runs use sync-from-config
3116
+ write_config "$TARGET_DIR" "$FALLBACK_CATEGORIES"
3117
+
3118
+ # Make hooks executable
3119
+ for hook in "$TARGET_DIR/.claude/hooks/"*.sh; do
3120
+ if [ -f "$hook" ] || [ -L "$hook" ]; then
3121
+ chmod +x "$hook" 2>/dev/null || true
3122
+ fi
3123
+ done
3124
+
3125
+ # Summary
3126
+ echo ""
3127
+ echo -e "${BOLD}Done!${NC}"
3128
+ echo -e " Created: ${GREEN}$CREATED${NC}"
3129
+ echo -e " Updated: ${GREEN}$UPDATED${NC}"
3130
+ if [ "$MODE" = "convert" ]; then
3131
+ echo -e " Converted: ${GREEN}$CONVERTED${NC}"
3132
+ fi
3133
+ echo -e " Skipped: ${YELLOW}$SKIPPED${NC} (project overrides)"
3134
+ if [ "$ERRORS" -gt 0 ]; then
3135
+ echo -e " Errors: ${RED}$ERRORS${NC}"
3136
+ fi
3137
+
3138
+ # Next steps (only in interactive mode, not with --json-output)
3139
+ if [ "$JSON_OUTPUT" != "true" ] && [ -t 1 ]; then
3140
+ echo ""
3141
+ # Check if project has been calibrated
3142
+ local has_profile=false
3143
+ [ -f "$TARGET_DIR/.claude/project-profile.md" ] && has_profile=true
3144
+
3145
+ if ! $has_profile; then
3146
+ echo -e "${BOLD}First time? Calibrate the toolkit to your project:${NC}"
3147
+ echo -e " ${CYAN}claude${NC} Start Claude Code"
3148
+ echo -e " ${CYAN}/calibrate${NC} Deep-learn your project (run once in Claude Code)"
3149
+ echo ""
3150
+ echo -e " ${DIM}/calibrate reads source code to learn your architecture, conventions, and domain."
3151
+ echo -e " It recommends and installs MCP servers, agents, skills, and workflows.${NC}"
3152
+ echo ""
3153
+ fi
3154
+
3155
+ echo -e "${BOLD}Customize your install:${NC}"
3156
+ echo -e " ${CYAN}install.sh --setup .${NC} Interactive category picker"
3157
+ echo -e " ${CYAN}install.sh --categories railway,superpowers .${NC}"
3158
+ echo -e " Add optional categories (non-interactive)"
3159
+ echo ""
3160
+ echo -e "${BOLD}Available categories:${NC}"
3161
+ echo -e " ${GREEN}Installed:${NC} core, strategy, development, quality, operations, hooks, guardrails"
3162
+ echo -e " ${YELLOW}Optional:${NC} railway (Railway deployment skills), superpowers (advanced skills)"
3163
+ echo ""
3164
+ echo -e "${BOLD}Other commands:${NC}"
3165
+ echo -e " ${CYAN}claude${NC} Start Claude Code in this project"
3166
+
3167
+ if command -v clade &>/dev/null; then
3168
+ echo -e " ${CYAN}clade sync${NC} Translate toolkit to Cursor/Codex/Kiro/Gemini"
3169
+ fi
3170
+
3171
+ if command -v arth &>/dev/null; then
3172
+ echo -e " ${CYAN}arth setup .${NC} Full setup: toolkit + multi-engine + dashboard"
3173
+ fi
3174
+
3175
+ echo -e " ${CYAN}install.sh --status .${NC} See what's linked/overridden"
3176
+
3177
+ if $has_profile; then
3178
+ echo -e " ${CYAN}/calibrate rescan${NC} Re-scan project after major changes"
3179
+ fi
3180
+ fi
3181
+
3182
+ # JSON summary (last line, parseable by callers)
3183
+ if [ "$JSON_OUTPUT" = "true" ]; then
3184
+ emit_json_summary "sync" "$TARGET_DIR"
3185
+ fi