slackhive 0.1.37 → 0.1.39

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 (542) hide show
  1. package/.dockerignore +14 -0
  2. package/.env.example +44 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.yml +65 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.yml +38 -0
  6. package/.github/PULL_REQUEST_TEMPLATE.md +27 -0
  7. package/.github/dependabot.yml +20 -0
  8. package/.github/workflows/audit.yml +149 -0
  9. package/.github/workflows/ci.yml +135 -0
  10. package/CHANGELOG.md +52 -0
  11. package/CODE_OF_CONDUCT.md +37 -0
  12. package/CONTRIBUTING.md +204 -0
  13. package/LICENSE +21 -0
  14. package/README.md +19 -0
  15. package/SECURITY.md +47 -0
  16. package/apps/runner/Dockerfile +33 -0
  17. package/apps/runner/dist/__tests__/channel-restrictions.test.d.ts +8 -0
  18. package/apps/runner/dist/__tests__/channel-restrictions.test.js +63 -0
  19. package/apps/runner/dist/__tests__/channel-restrictions.test.js.map +1 -0
  20. package/apps/runner/dist/__tests__/claude-handler-resolve.test.d.ts +20 -0
  21. package/apps/runner/dist/__tests__/claude-handler-resolve.test.js +178 -0
  22. package/apps/runner/dist/__tests__/claude-handler-resolve.test.js.map +1 -0
  23. package/apps/runner/dist/__tests__/compile-claude-md.test.d.ts +13 -0
  24. package/apps/runner/dist/__tests__/compile-claude-md.test.js +144 -0
  25. package/apps/runner/dist/__tests__/compile-claude-md.test.js.map +1 -0
  26. package/apps/runner/dist/__tests__/memory-sync.test.d.ts +11 -0
  27. package/apps/runner/dist/__tests__/memory-sync.test.js +56 -0
  28. package/apps/runner/dist/__tests__/memory-sync.test.js.map +1 -0
  29. package/apps/runner/dist/__tests__/slack-file-support.test.d.ts +9 -0
  30. package/apps/runner/dist/__tests__/slack-file-support.test.js +271 -0
  31. package/apps/runner/dist/__tests__/slack-file-support.test.js.map +1 -0
  32. package/apps/runner/dist/__tests__/slack-formatting.test.d.ts +12 -0
  33. package/apps/runner/dist/__tests__/slack-formatting.test.js +400 -0
  34. package/apps/runner/dist/__tests__/slack-formatting.test.js.map +1 -0
  35. package/apps/runner/dist/__tests__/thread-context.test.d.ts +12 -0
  36. package/apps/runner/dist/__tests__/thread-context.test.js +182 -0
  37. package/apps/runner/dist/__tests__/thread-context.test.js.map +1 -0
  38. package/apps/runner/dist/agent-runner.d.ts +118 -0
  39. package/apps/runner/dist/agent-runner.js +352 -0
  40. package/apps/runner/dist/agent-runner.js.map +1 -0
  41. package/apps/runner/dist/claude-handler.d.ts +122 -0
  42. package/apps/runner/dist/claude-handler.js +402 -0
  43. package/apps/runner/dist/claude-handler.js.map +1 -0
  44. package/apps/runner/dist/compile-claude-md.d.ts +59 -0
  45. package/apps/runner/dist/compile-claude-md.js +291 -0
  46. package/apps/runner/dist/compile-claude-md.js.map +1 -0
  47. package/apps/runner/dist/correction-handler.d.ts +46 -0
  48. package/apps/runner/dist/correction-handler.js +162 -0
  49. package/apps/runner/dist/correction-handler.js.map +1 -0
  50. package/apps/runner/dist/correction-manager.d.ts +53 -0
  51. package/apps/runner/dist/correction-manager.js +241 -0
  52. package/apps/runner/dist/correction-manager.js.map +1 -0
  53. package/apps/runner/dist/db.d.ts +193 -0
  54. package/apps/runner/dist/db.js +492 -0
  55. package/apps/runner/dist/db.js.map +1 -0
  56. package/apps/runner/dist/index.d.ts +9 -0
  57. package/apps/runner/dist/index.js +43 -0
  58. package/apps/runner/dist/index.js.map +1 -0
  59. package/apps/runner/dist/job-scheduler.d.ts +57 -0
  60. package/apps/runner/dist/job-scheduler.js +150 -0
  61. package/apps/runner/dist/job-scheduler.js.map +1 -0
  62. package/apps/runner/dist/logger.d.ts +32 -0
  63. package/apps/runner/dist/logger.js +52 -0
  64. package/apps/runner/dist/logger.js.map +1 -0
  65. package/apps/runner/dist/mcp-process-manager.d.ts +38 -0
  66. package/apps/runner/dist/mcp-process-manager.js +189 -0
  67. package/apps/runner/dist/mcp-process-manager.js.map +1 -0
  68. package/apps/runner/dist/memory-mcp.d.ts +14 -0
  69. package/apps/runner/dist/memory-mcp.js +88 -0
  70. package/apps/runner/dist/memory-mcp.js.map +1 -0
  71. package/apps/runner/dist/memory-watcher.d.ts +78 -0
  72. package/apps/runner/dist/memory-watcher.js +220 -0
  73. package/apps/runner/dist/memory-watcher.js.map +1 -0
  74. package/apps/runner/dist/slack-handler.d.ts +120 -0
  75. package/apps/runner/dist/slack-handler.js +843 -0
  76. package/apps/runner/dist/slack-handler.js.map +1 -0
  77. package/apps/runner/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  78. package/apps/runner/package.json +42 -0
  79. package/apps/runner/src/__tests__/channel-restrictions.test.ts +75 -0
  80. package/apps/runner/src/__tests__/claude-handler-resolve.test.ts +160 -0
  81. package/apps/runner/src/__tests__/compile-claude-md.test.ts +139 -0
  82. package/apps/runner/src/__tests__/memory-sync.test.ts +59 -0
  83. package/apps/runner/src/__tests__/slack-file-support.test.ts +376 -0
  84. package/apps/runner/src/__tests__/slack-formatting.test.ts +495 -0
  85. package/apps/runner/src/__tests__/thread-context.test.ts +215 -0
  86. package/apps/runner/src/agent-runner.ts +397 -0
  87. package/apps/runner/src/claude-handler.ts +475 -0
  88. package/apps/runner/src/compile-claude-md.ts +283 -0
  89. package/apps/runner/src/correction-handler.ts +191 -0
  90. package/apps/runner/src/correction-manager.ts +285 -0
  91. package/apps/runner/src/db.ts +604 -0
  92. package/apps/runner/src/index.ts +46 -0
  93. package/apps/runner/src/job-scheduler.ts +165 -0
  94. package/apps/runner/src/logger.ts +49 -0
  95. package/apps/runner/src/mcp-process-manager.ts +195 -0
  96. package/apps/runner/src/memory-mcp.ts +85 -0
  97. package/apps/runner/src/memory-watcher.ts +215 -0
  98. package/apps/runner/src/slack-handler.ts +929 -0
  99. package/apps/runner/tsconfig.json +17 -0
  100. package/apps/runner/vitest.config.mts +17 -0
  101. package/apps/web/.eslintrc.json +3 -0
  102. package/apps/web/.next/app-build-manifest.json +323 -0
  103. package/apps/web/.next/app-path-routes-manifest.json +46 -0
  104. package/apps/web/.next/build-manifest.json +33 -0
  105. package/apps/web/.next/cache/.previewinfo +1 -0
  106. package/apps/web/.next/cache/.rscinfo +1 -0
  107. package/apps/web/.next/cache/webpack/client-production/0.pack +0 -0
  108. package/apps/web/.next/cache/webpack/client-production/1.pack +0 -0
  109. package/apps/web/.next/cache/webpack/client-production/2.pack +0 -0
  110. package/apps/web/.next/cache/webpack/client-production/3.pack +0 -0
  111. package/apps/web/.next/cache/webpack/client-production/4.pack +0 -0
  112. package/apps/web/.next/cache/webpack/client-production/index.pack +0 -0
  113. package/apps/web/.next/cache/webpack/client-production/index.pack.old +0 -0
  114. package/apps/web/.next/cache/webpack/edge-server-production/0.pack +0 -0
  115. package/apps/web/.next/cache/webpack/edge-server-production/1.pack +0 -0
  116. package/apps/web/.next/cache/webpack/edge-server-production/index.pack +0 -0
  117. package/apps/web/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  118. package/apps/web/.next/cache/webpack/server-production/0.pack +0 -0
  119. package/apps/web/.next/cache/webpack/server-production/1.pack +0 -0
  120. package/apps/web/.next/cache/webpack/server-production/2.pack +0 -0
  121. package/apps/web/.next/cache/webpack/server-production/index.pack +0 -0
  122. package/apps/web/.next/cache/webpack/server-production/index.pack.old +0 -0
  123. package/apps/web/.next/diagnostics/build-diagnostics.json +6 -0
  124. package/apps/web/.next/diagnostics/framework.json +1 -0
  125. package/apps/web/.next/package.json +1 -0
  126. package/apps/web/.next/react-loadable-manifest.json +1 -0
  127. package/apps/web/.next/server/app/_not-found/page.js +2 -0
  128. package/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -0
  129. package/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  130. package/apps/web/.next/server/app/agents/[slug]/page.js +4 -0
  131. package/apps/web/.next/server/app/agents/[slug]/page.js.nft.json +1 -0
  132. package/apps/web/.next/server/app/agents/[slug]/page_client-reference-manifest.js +1 -0
  133. package/apps/web/.next/server/app/agents/new/page.js +2 -0
  134. package/apps/web/.next/server/app/agents/new/page.js.nft.json +1 -0
  135. package/apps/web/.next/server/app/agents/new/page_client-reference-manifest.js +1 -0
  136. package/apps/web/.next/server/app/api/agents/[id]/access/route.js +1 -0
  137. package/apps/web/.next/server/app/api/agents/[id]/access/route.js.nft.json +1 -0
  138. package/apps/web/.next/server/app/api/agents/[id]/access/route_client-reference-manifest.js +1 -0
  139. package/apps/web/.next/server/app/api/agents/[id]/claude-md/route.js +6 -0
  140. package/apps/web/.next/server/app/api/agents/[id]/claude-md/route.js.nft.json +1 -0
  141. package/apps/web/.next/server/app/api/agents/[id]/claude-md/route_client-reference-manifest.js +1 -0
  142. package/apps/web/.next/server/app/api/agents/[id]/logs/route.js +3 -0
  143. package/apps/web/.next/server/app/api/agents/[id]/logs/route.js.nft.json +1 -0
  144. package/apps/web/.next/server/app/api/agents/[id]/logs/route_client-reference-manifest.js +1 -0
  145. package/apps/web/.next/server/app/api/agents/[id]/manifest/route.js +1 -0
  146. package/apps/web/.next/server/app/api/agents/[id]/manifest/route.js.nft.json +1 -0
  147. package/apps/web/.next/server/app/api/agents/[id]/manifest/route_client-reference-manifest.js +1 -0
  148. package/apps/web/.next/server/app/api/agents/[id]/mcps/route.js +1 -0
  149. package/apps/web/.next/server/app/api/agents/[id]/mcps/route.js.nft.json +1 -0
  150. package/apps/web/.next/server/app/api/agents/[id]/mcps/route_client-reference-manifest.js +1 -0
  151. package/apps/web/.next/server/app/api/agents/[id]/memories/[memId]/route.js +1 -0
  152. package/apps/web/.next/server/app/api/agents/[id]/memories/[memId]/route.js.nft.json +1 -0
  153. package/apps/web/.next/server/app/api/agents/[id]/memories/[memId]/route_client-reference-manifest.js +1 -0
  154. package/apps/web/.next/server/app/api/agents/[id]/memories/route.js +1 -0
  155. package/apps/web/.next/server/app/api/agents/[id]/memories/route.js.nft.json +1 -0
  156. package/apps/web/.next/server/app/api/agents/[id]/memories/route_client-reference-manifest.js +1 -0
  157. package/apps/web/.next/server/app/api/agents/[id]/permissions/route.js +1 -0
  158. package/apps/web/.next/server/app/api/agents/[id]/permissions/route.js.nft.json +1 -0
  159. package/apps/web/.next/server/app/api/agents/[id]/permissions/route_client-reference-manifest.js +1 -0
  160. package/apps/web/.next/server/app/api/agents/[id]/reload/route.js +1 -0
  161. package/apps/web/.next/server/app/api/agents/[id]/reload/route.js.nft.json +1 -0
  162. package/apps/web/.next/server/app/api/agents/[id]/reload/route_client-reference-manifest.js +1 -0
  163. package/apps/web/.next/server/app/api/agents/[id]/restrictions/route.js +1 -0
  164. package/apps/web/.next/server/app/api/agents/[id]/restrictions/route.js.nft.json +1 -0
  165. package/apps/web/.next/server/app/api/agents/[id]/restrictions/route_client-reference-manifest.js +1 -0
  166. package/apps/web/.next/server/app/api/agents/[id]/route.js +33 -0
  167. package/apps/web/.next/server/app/api/agents/[id]/route.js.nft.json +1 -0
  168. package/apps/web/.next/server/app/api/agents/[id]/route_client-reference-manifest.js +1 -0
  169. package/apps/web/.next/server/app/api/agents/[id]/skills/[skillId]/route.js +1 -0
  170. package/apps/web/.next/server/app/api/agents/[id]/skills/[skillId]/route.js.nft.json +1 -0
  171. package/apps/web/.next/server/app/api/agents/[id]/skills/[skillId]/route_client-reference-manifest.js +1 -0
  172. package/apps/web/.next/server/app/api/agents/[id]/skills/route.js +1 -0
  173. package/apps/web/.next/server/app/api/agents/[id]/skills/route.js.nft.json +1 -0
  174. package/apps/web/.next/server/app/api/agents/[id]/skills/route_client-reference-manifest.js +1 -0
  175. package/apps/web/.next/server/app/api/agents/[id]/slack-info/route.js +1 -0
  176. package/apps/web/.next/server/app/api/agents/[id]/slack-info/route.js.nft.json +1 -0
  177. package/apps/web/.next/server/app/api/agents/[id]/slack-info/route_client-reference-manifest.js +1 -0
  178. package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/restore/route.js +1 -0
  179. package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/restore/route.js.nft.json +1 -0
  180. package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/restore/route_client-reference-manifest.js +1 -0
  181. package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/route.js +1 -0
  182. package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/route.js.nft.json +1 -0
  183. package/apps/web/.next/server/app/api/agents/[id]/snapshots/[sid]/route_client-reference-manifest.js +1 -0
  184. package/apps/web/.next/server/app/api/agents/[id]/snapshots/route.js +1 -0
  185. package/apps/web/.next/server/app/api/agents/[id]/snapshots/route.js.nft.json +1 -0
  186. package/apps/web/.next/server/app/api/agents/[id]/snapshots/route_client-reference-manifest.js +1 -0
  187. package/apps/web/.next/server/app/api/agents/[id]/start/route.js +1 -0
  188. package/apps/web/.next/server/app/api/agents/[id]/start/route.js.nft.json +1 -0
  189. package/apps/web/.next/server/app/api/agents/[id]/start/route_client-reference-manifest.js +1 -0
  190. package/apps/web/.next/server/app/api/agents/[id]/stop/route.js +1 -0
  191. package/apps/web/.next/server/app/api/agents/[id]/stop/route.js.nft.json +1 -0
  192. package/apps/web/.next/server/app/api/agents/[id]/stop/route_client-reference-manifest.js +1 -0
  193. package/apps/web/.next/server/app/api/agents/route.js +91 -0
  194. package/apps/web/.next/server/app/api/agents/route.js.nft.json +1 -0
  195. package/apps/web/.next/server/app/api/agents/route_client-reference-manifest.js +1 -0
  196. package/apps/web/.next/server/app/api/auth/login/route.js +1 -0
  197. package/apps/web/.next/server/app/api/auth/login/route.js.nft.json +1 -0
  198. package/apps/web/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -0
  199. package/apps/web/.next/server/app/api/auth/logout/route.js +1 -0
  200. package/apps/web/.next/server/app/api/auth/logout/route.js.nft.json +1 -0
  201. package/apps/web/.next/server/app/api/auth/logout/route_client-reference-manifest.js +1 -0
  202. package/apps/web/.next/server/app/api/auth/me/route.js +1 -0
  203. package/apps/web/.next/server/app/api/auth/me/route.js.nft.json +1 -0
  204. package/apps/web/.next/server/app/api/auth/me/route_client-reference-manifest.js +1 -0
  205. package/apps/web/.next/server/app/api/auth/users/[id]/route.js +1 -0
  206. package/apps/web/.next/server/app/api/auth/users/[id]/route.js.nft.json +1 -0
  207. package/apps/web/.next/server/app/api/auth/users/[id]/route_client-reference-manifest.js +1 -0
  208. package/apps/web/.next/server/app/api/auth/users/route.js +1 -0
  209. package/apps/web/.next/server/app/api/auth/users/route.js.nft.json +1 -0
  210. package/apps/web/.next/server/app/api/auth/users/route_client-reference-manifest.js +1 -0
  211. package/apps/web/.next/server/app/api/env-vars/[key]/route.js +1 -0
  212. package/apps/web/.next/server/app/api/env-vars/[key]/route.js.nft.json +1 -0
  213. package/apps/web/.next/server/app/api/env-vars/[key]/route_client-reference-manifest.js +1 -0
  214. package/apps/web/.next/server/app/api/env-vars/route.js +1 -0
  215. package/apps/web/.next/server/app/api/env-vars/route.js.nft.json +1 -0
  216. package/apps/web/.next/server/app/api/env-vars/route_client-reference-manifest.js +1 -0
  217. package/apps/web/.next/server/app/api/jobs/[id]/route.js +1 -0
  218. package/apps/web/.next/server/app/api/jobs/[id]/route.js.nft.json +1 -0
  219. package/apps/web/.next/server/app/api/jobs/[id]/route_client-reference-manifest.js +1 -0
  220. package/apps/web/.next/server/app/api/jobs/[id]/runs/route.js +1 -0
  221. package/apps/web/.next/server/app/api/jobs/[id]/runs/route.js.nft.json +1 -0
  222. package/apps/web/.next/server/app/api/jobs/[id]/runs/route_client-reference-manifest.js +1 -0
  223. package/apps/web/.next/server/app/api/jobs/route.js +1 -0
  224. package/apps/web/.next/server/app/api/jobs/route.js.nft.json +1 -0
  225. package/apps/web/.next/server/app/api/jobs/route_client-reference-manifest.js +1 -0
  226. package/apps/web/.next/server/app/api/mcps/[id]/route.js +1 -0
  227. package/apps/web/.next/server/app/api/mcps/[id]/route.js.nft.json +1 -0
  228. package/apps/web/.next/server/app/api/mcps/[id]/route_client-reference-manifest.js +1 -0
  229. package/apps/web/.next/server/app/api/mcps/[id]/test/route.js +1 -0
  230. package/apps/web/.next/server/app/api/mcps/[id]/test/route.js.nft.json +1 -0
  231. package/apps/web/.next/server/app/api/mcps/[id]/test/route_client-reference-manifest.js +1 -0
  232. package/apps/web/.next/server/app/api/mcps/route.js +1 -0
  233. package/apps/web/.next/server/app/api/mcps/route.js.nft.json +1 -0
  234. package/apps/web/.next/server/app/api/mcps/route_client-reference-manifest.js +1 -0
  235. package/apps/web/.next/server/app/api/settings/route.js +1 -0
  236. package/apps/web/.next/server/app/api/settings/route.js.nft.json +1 -0
  237. package/apps/web/.next/server/app/api/settings/route_client-reference-manifest.js +1 -0
  238. package/apps/web/.next/server/app/icon.svg/route.js +1 -0
  239. package/apps/web/.next/server/app/icon.svg/route.js.nft.json +1 -0
  240. package/apps/web/.next/server/app/jobs/page.js +2 -0
  241. package/apps/web/.next/server/app/jobs/page.js.nft.json +1 -0
  242. package/apps/web/.next/server/app/jobs/page_client-reference-manifest.js +1 -0
  243. package/apps/web/.next/server/app/login/page.js +2 -0
  244. package/apps/web/.next/server/app/login/page.js.nft.json +1 -0
  245. package/apps/web/.next/server/app/login/page_client-reference-manifest.js +1 -0
  246. package/apps/web/.next/server/app/page.js +2 -0
  247. package/apps/web/.next/server/app/page.js.nft.json +1 -0
  248. package/apps/web/.next/server/app/page_client-reference-manifest.js +1 -0
  249. package/apps/web/.next/server/app/settings/env-vars/page.js +2 -0
  250. package/apps/web/.next/server/app/settings/env-vars/page.js.nft.json +1 -0
  251. package/apps/web/.next/server/app/settings/env-vars/page_client-reference-manifest.js +1 -0
  252. package/apps/web/.next/server/app/settings/mcps/page.js +2 -0
  253. package/apps/web/.next/server/app/settings/mcps/page.js.nft.json +1 -0
  254. package/apps/web/.next/server/app/settings/mcps/page_client-reference-manifest.js +1 -0
  255. package/apps/web/.next/server/app/settings/page.js +2 -0
  256. package/apps/web/.next/server/app/settings/page.js.nft.json +1 -0
  257. package/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -0
  258. package/apps/web/.next/server/app-paths-manifest.json +46 -0
  259. package/apps/web/.next/server/chunks/1157.js +9 -0
  260. package/apps/web/.next/server/chunks/2287.js +1 -0
  261. package/apps/web/.next/server/chunks/3444.js +1 -0
  262. package/apps/web/.next/server/chunks/383.js +6 -0
  263. package/apps/web/.next/server/chunks/4012.js +58 -0
  264. package/apps/web/.next/server/chunks/6791.js +1 -0
  265. package/apps/web/.next/server/chunks/7171.js +1 -0
  266. package/apps/web/.next/server/chunks/8819.js +22 -0
  267. package/apps/web/.next/server/edge-runtime-webpack.js +2 -0
  268. package/apps/web/.next/server/edge-runtime-webpack.js.map +1 -0
  269. package/apps/web/.next/server/interception-route-rewrite-manifest.js +1 -0
  270. package/apps/web/.next/server/middleware-build-manifest.js +1 -0
  271. package/apps/web/.next/server/middleware-manifest.json +32 -0
  272. package/apps/web/.next/server/middleware-react-loadable-manifest.js +1 -0
  273. package/apps/web/.next/server/next-font-manifest.js +1 -0
  274. package/apps/web/.next/server/next-font-manifest.json +1 -0
  275. package/apps/web/.next/server/pages/_app.js +1 -0
  276. package/apps/web/.next/server/pages/_app.js.nft.json +1 -0
  277. package/apps/web/.next/server/pages/_document.js +1 -0
  278. package/apps/web/.next/server/pages/_document.js.nft.json +1 -0
  279. package/apps/web/.next/server/pages/_error.js +19 -0
  280. package/apps/web/.next/server/pages/_error.js.nft.json +1 -0
  281. package/apps/web/.next/server/pages-manifest.json +5 -0
  282. package/apps/web/.next/server/server-reference-manifest.js +1 -0
  283. package/apps/web/.next/server/server-reference-manifest.json +1 -0
  284. package/apps/web/.next/server/src/middleware.js +14 -0
  285. package/apps/web/.next/server/src/middleware.js.map +1 -0
  286. package/apps/web/.next/server/webpack-runtime.js +1 -0
  287. package/apps/web/.next/static/chunks/18-90b700ea37b686a2.js +1 -0
  288. package/apps/web/.next/static/chunks/87c73c54-24122e7b92478d00.js +1 -0
  289. package/apps/web/.next/static/chunks/9664-af80478aa73ba424.js +1 -0
  290. package/apps/web/.next/static/chunks/app/_not-found/page-b9cee17ed89ca24a.js +1 -0
  291. package/apps/web/.next/static/chunks/app/agents/[slug]/page-18369fc3fe1a9a7b.js +1 -0
  292. package/apps/web/.next/static/chunks/app/agents/new/page-bf11cf8901c7e2cd.js +1 -0
  293. package/apps/web/.next/static/chunks/app/api/agents/[id]/access/route-07f0f73ac9839899.js +1 -0
  294. package/apps/web/.next/static/chunks/app/api/agents/[id]/claude-md/route-07f0f73ac9839899.js +1 -0
  295. package/apps/web/.next/static/chunks/app/api/agents/[id]/logs/route-07f0f73ac9839899.js +1 -0
  296. package/apps/web/.next/static/chunks/app/api/agents/[id]/manifest/route-07f0f73ac9839899.js +1 -0
  297. package/apps/web/.next/static/chunks/app/api/agents/[id]/mcps/route-07f0f73ac9839899.js +1 -0
  298. package/apps/web/.next/static/chunks/app/api/agents/[id]/memories/[memId]/route-07f0f73ac9839899.js +1 -0
  299. package/apps/web/.next/static/chunks/app/api/agents/[id]/memories/route-07f0f73ac9839899.js +1 -0
  300. package/apps/web/.next/static/chunks/app/api/agents/[id]/permissions/route-07f0f73ac9839899.js +1 -0
  301. package/apps/web/.next/static/chunks/app/api/agents/[id]/reload/route-07f0f73ac9839899.js +1 -0
  302. package/apps/web/.next/static/chunks/app/api/agents/[id]/restrictions/route-07f0f73ac9839899.js +1 -0
  303. package/apps/web/.next/static/chunks/app/api/agents/[id]/route-07f0f73ac9839899.js +1 -0
  304. package/apps/web/.next/static/chunks/app/api/agents/[id]/skills/[skillId]/route-07f0f73ac9839899.js +1 -0
  305. package/apps/web/.next/static/chunks/app/api/agents/[id]/skills/route-07f0f73ac9839899.js +1 -0
  306. package/apps/web/.next/static/chunks/app/api/agents/[id]/slack-info/route-07f0f73ac9839899.js +1 -0
  307. package/apps/web/.next/static/chunks/app/api/agents/[id]/snapshots/[sid]/restore/route-07f0f73ac9839899.js +1 -0
  308. package/apps/web/.next/static/chunks/app/api/agents/[id]/snapshots/[sid]/route-07f0f73ac9839899.js +1 -0
  309. package/apps/web/.next/static/chunks/app/api/agents/[id]/snapshots/route-07f0f73ac9839899.js +1 -0
  310. package/apps/web/.next/static/chunks/app/api/agents/[id]/start/route-07f0f73ac9839899.js +1 -0
  311. package/apps/web/.next/static/chunks/app/api/agents/[id]/stop/route-07f0f73ac9839899.js +1 -0
  312. package/apps/web/.next/static/chunks/app/api/agents/route-07f0f73ac9839899.js +1 -0
  313. package/apps/web/.next/static/chunks/app/api/auth/login/route-07f0f73ac9839899.js +1 -0
  314. package/apps/web/.next/static/chunks/app/api/auth/logout/route-07f0f73ac9839899.js +1 -0
  315. package/apps/web/.next/static/chunks/app/api/auth/me/route-07f0f73ac9839899.js +1 -0
  316. package/apps/web/.next/static/chunks/app/api/auth/users/[id]/route-07f0f73ac9839899.js +1 -0
  317. package/apps/web/.next/static/chunks/app/api/auth/users/route-07f0f73ac9839899.js +1 -0
  318. package/apps/web/.next/static/chunks/app/api/env-vars/[key]/route-07f0f73ac9839899.js +1 -0
  319. package/apps/web/.next/static/chunks/app/api/env-vars/route-07f0f73ac9839899.js +1 -0
  320. package/apps/web/.next/static/chunks/app/api/jobs/[id]/route-07f0f73ac9839899.js +1 -0
  321. package/apps/web/.next/static/chunks/app/api/jobs/[id]/runs/route-07f0f73ac9839899.js +1 -0
  322. package/apps/web/.next/static/chunks/app/api/jobs/route-07f0f73ac9839899.js +1 -0
  323. package/apps/web/.next/static/chunks/app/api/mcps/[id]/route-07f0f73ac9839899.js +1 -0
  324. package/apps/web/.next/static/chunks/app/api/mcps/[id]/test/route-07f0f73ac9839899.js +1 -0
  325. package/apps/web/.next/static/chunks/app/api/mcps/route-07f0f73ac9839899.js +1 -0
  326. package/apps/web/.next/static/chunks/app/api/settings/route-07f0f73ac9839899.js +1 -0
  327. package/apps/web/.next/static/chunks/app/jobs/page-f5aa89a47c50efd8.js +1 -0
  328. package/apps/web/.next/static/chunks/app/layout-2079f4964aa7314e.js +1 -0
  329. package/apps/web/.next/static/chunks/app/login/layout-07f0f73ac9839899.js +1 -0
  330. package/apps/web/.next/static/chunks/app/login/page-aa259283dc38e8f9.js +1 -0
  331. package/apps/web/.next/static/chunks/app/page-e83437b608104dff.js +1 -0
  332. package/apps/web/.next/static/chunks/app/settings/env-vars/page-06479dbdfb78b76b.js +1 -0
  333. package/apps/web/.next/static/chunks/app/settings/mcps/page-75650686ed6490c7.js +1 -0
  334. package/apps/web/.next/static/chunks/app/settings/page-e1e62fc41ff6cddd.js +1 -0
  335. package/apps/web/.next/static/chunks/framework-811407f832a33072.js +1 -0
  336. package/apps/web/.next/static/chunks/main-3f1cddbdd67b1546.js +1 -0
  337. package/apps/web/.next/static/chunks/main-app-cebd8a6a5ccbf72d.js +1 -0
  338. package/apps/web/.next/static/chunks/pages/_app-50fa07b56b2d29ac.js +1 -0
  339. package/apps/web/.next/static/chunks/pages/_error-fed8688bdd23f211.js +1 -0
  340. package/apps/web/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  341. package/apps/web/.next/static/chunks/webpack-6c05566dba553c97.js +1 -0
  342. package/apps/web/.next/static/css/15371687405525e2.css +5 -0
  343. package/apps/web/.next/static/ikfNbLhuw7jntn35bz0lk/_buildManifest.js +1 -0
  344. package/apps/web/.next/static/ikfNbLhuw7jntn35bz0lk/_ssgManifest.js +1 -0
  345. package/apps/web/.next/trace +5 -0
  346. package/apps/web/.next/types/app/agents/[slug]/page.ts +84 -0
  347. package/apps/web/.next/types/app/agents/new/page.ts +84 -0
  348. package/apps/web/.next/types/app/api/agents/[id]/access/route.ts +347 -0
  349. package/apps/web/.next/types/app/api/agents/[id]/claude-md/route.ts +347 -0
  350. package/apps/web/.next/types/app/api/agents/[id]/logs/route.ts +347 -0
  351. package/apps/web/.next/types/app/api/agents/[id]/manifest/route.ts +347 -0
  352. package/apps/web/.next/types/app/api/agents/[id]/mcps/route.ts +347 -0
  353. package/apps/web/.next/types/app/api/agents/[id]/memories/[memId]/route.ts +347 -0
  354. package/apps/web/.next/types/app/api/agents/[id]/memories/route.ts +347 -0
  355. package/apps/web/.next/types/app/api/agents/[id]/permissions/route.ts +347 -0
  356. package/apps/web/.next/types/app/api/agents/[id]/reload/route.ts +347 -0
  357. package/apps/web/.next/types/app/api/agents/[id]/restrictions/route.ts +347 -0
  358. package/apps/web/.next/types/app/api/agents/[id]/route.ts +347 -0
  359. package/apps/web/.next/types/app/api/agents/[id]/skills/[skillId]/route.ts +347 -0
  360. package/apps/web/.next/types/app/api/agents/[id]/skills/route.ts +347 -0
  361. package/apps/web/.next/types/app/api/agents/[id]/slack-info/route.ts +347 -0
  362. package/apps/web/.next/types/app/api/agents/[id]/snapshots/[sid]/restore/route.ts +347 -0
  363. package/apps/web/.next/types/app/api/agents/[id]/snapshots/[sid]/route.ts +347 -0
  364. package/apps/web/.next/types/app/api/agents/[id]/snapshots/route.ts +347 -0
  365. package/apps/web/.next/types/app/api/agents/[id]/start/route.ts +347 -0
  366. package/apps/web/.next/types/app/api/agents/[id]/stop/route.ts +347 -0
  367. package/apps/web/.next/types/app/api/agents/route.ts +347 -0
  368. package/apps/web/.next/types/app/api/auth/login/route.ts +347 -0
  369. package/apps/web/.next/types/app/api/auth/logout/route.ts +347 -0
  370. package/apps/web/.next/types/app/api/auth/me/route.ts +347 -0
  371. package/apps/web/.next/types/app/api/auth/users/[id]/route.ts +347 -0
  372. package/apps/web/.next/types/app/api/auth/users/route.ts +347 -0
  373. package/apps/web/.next/types/app/api/env-vars/[key]/route.ts +347 -0
  374. package/apps/web/.next/types/app/api/env-vars/route.ts +347 -0
  375. package/apps/web/.next/types/app/api/jobs/[id]/route.ts +347 -0
  376. package/apps/web/.next/types/app/api/jobs/[id]/runs/route.ts +347 -0
  377. package/apps/web/.next/types/app/api/jobs/route.ts +347 -0
  378. package/apps/web/.next/types/app/api/mcps/[id]/route.ts +347 -0
  379. package/apps/web/.next/types/app/api/mcps/[id]/test/route.ts +347 -0
  380. package/apps/web/.next/types/app/api/mcps/route.ts +347 -0
  381. package/apps/web/.next/types/app/api/settings/route.ts +347 -0
  382. package/apps/web/.next/types/app/jobs/page.ts +84 -0
  383. package/apps/web/.next/types/app/login/layout.ts +84 -0
  384. package/apps/web/.next/types/app/login/page.ts +84 -0
  385. package/apps/web/.next/types/app/page.ts +84 -0
  386. package/apps/web/.next/types/app/settings/env-vars/page.ts +84 -0
  387. package/apps/web/.next/types/app/settings/mcps/page.ts +84 -0
  388. package/apps/web/.next/types/app/settings/page.ts +84 -0
  389. package/apps/web/.next/types/cache-life.d.ts +141 -0
  390. package/apps/web/.next/types/package.json +1 -0
  391. package/apps/web/.next/types/routes.d.ts +114 -0
  392. package/apps/web/.next/types/validator.ts +448 -0
  393. package/apps/web/Dockerfile +37 -0
  394. package/apps/web/next-env.d.ts +6 -0
  395. package/apps/web/next.config.js +6 -0
  396. package/apps/web/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  397. package/apps/web/package.json +48 -0
  398. package/apps/web/postcss.config.js +3 -0
  399. package/apps/web/public/logo.svg +17 -0
  400. package/apps/web/src/app/agents/[slug]/page.tsx +2235 -0
  401. package/apps/web/src/app/agents/new/page.tsx +1161 -0
  402. package/apps/web/src/app/api/agents/[id]/access/route.ts +76 -0
  403. package/apps/web/src/app/api/agents/[id]/claude-md/route.ts +111 -0
  404. package/apps/web/src/app/api/agents/[id]/logs/route.ts +84 -0
  405. package/apps/web/src/app/api/agents/[id]/manifest/route.ts +32 -0
  406. package/apps/web/src/app/api/agents/[id]/mcps/route.ts +73 -0
  407. package/apps/web/src/app/api/agents/[id]/memories/[memId]/route.ts +31 -0
  408. package/apps/web/src/app/api/agents/[id]/memories/route.ts +56 -0
  409. package/apps/web/src/app/api/agents/[id]/permissions/route.ts +74 -0
  410. package/apps/web/src/app/api/agents/[id]/reload/route.ts +33 -0
  411. package/apps/web/src/app/api/agents/[id]/restrictions/route.ts +85 -0
  412. package/apps/web/src/app/api/agents/[id]/route.ts +81 -0
  413. package/apps/web/src/app/api/agents/[id]/skills/[skillId]/route.ts +52 -0
  414. package/apps/web/src/app/api/agents/[id]/skills/route.ts +80 -0
  415. package/apps/web/src/app/api/agents/[id]/slack-info/route.ts +38 -0
  416. package/apps/web/src/app/api/agents/[id]/snapshots/[sid]/restore/route.ts +61 -0
  417. package/apps/web/src/app/api/agents/[id]/snapshots/[sid]/route.ts +53 -0
  418. package/apps/web/src/app/api/agents/[id]/snapshots/route.ts +84 -0
  419. package/apps/web/src/app/api/agents/[id]/start/route.ts +35 -0
  420. package/apps/web/src/app/api/agents/[id]/stop/route.ts +35 -0
  421. package/apps/web/src/app/api/agents/route.ts +99 -0
  422. package/apps/web/src/app/api/auth/login/route.ts +39 -0
  423. package/apps/web/src/app/api/auth/logout/route.ts +21 -0
  424. package/apps/web/src/app/api/auth/me/route.ts +24 -0
  425. package/apps/web/src/app/api/auth/users/[id]/route.ts +48 -0
  426. package/apps/web/src/app/api/auth/users/route.ts +63 -0
  427. package/apps/web/src/app/api/env-vars/[key]/route.ts +66 -0
  428. package/apps/web/src/app/api/env-vars/route.ts +59 -0
  429. package/apps/web/src/app/api/jobs/[id]/route.ts +51 -0
  430. package/apps/web/src/app/api/jobs/[id]/runs/route.ts +24 -0
  431. package/apps/web/src/app/api/jobs/route.ts +42 -0
  432. package/apps/web/src/app/api/mcps/[id]/route.ts +60 -0
  433. package/apps/web/src/app/api/mcps/[id]/test/route.ts +195 -0
  434. package/apps/web/src/app/api/mcps/route.ts +72 -0
  435. package/apps/web/src/app/api/settings/route.ts +42 -0
  436. package/apps/web/src/app/globals.css +124 -0
  437. package/apps/web/src/app/icon.svg +17 -0
  438. package/apps/web/src/app/jobs/page.tsx +543 -0
  439. package/apps/web/src/app/layout-shell.tsx +89 -0
  440. package/apps/web/src/app/layout.tsx +18 -0
  441. package/apps/web/src/app/login/layout.tsx +9 -0
  442. package/apps/web/src/app/login/page.tsx +150 -0
  443. package/apps/web/src/app/page.tsx +573 -0
  444. package/apps/web/src/app/settings/env-vars/page.tsx +216 -0
  445. package/apps/web/src/app/settings/mcps/page.tsx +763 -0
  446. package/apps/web/src/app/settings/page.tsx +528 -0
  447. package/apps/web/src/app/sidebar.tsx +345 -0
  448. package/apps/web/src/lib/__tests__/api-guard.test.ts +189 -0
  449. package/apps/web/src/lib/__tests__/auth.test.ts +262 -0
  450. package/apps/web/src/lib/__tests__/boss-registry.test.ts +323 -0
  451. package/apps/web/src/lib/__tests__/compile.test.ts +161 -0
  452. package/apps/web/src/lib/__tests__/db-agent-hierarchy.test.ts +136 -0
  453. package/apps/web/src/lib/__tests__/db-env-vars.test.ts +216 -0
  454. package/apps/web/src/lib/__tests__/db-restrictions.test.ts +117 -0
  455. package/apps/web/src/lib/__tests__/db.integration.test.ts +271 -0
  456. package/apps/web/src/lib/__tests__/diff.test.ts +102 -0
  457. package/apps/web/src/lib/__tests__/mcp-mask.test.ts +274 -0
  458. package/apps/web/src/lib/__tests__/skill-templates.test.ts +237 -0
  459. package/apps/web/src/lib/__tests__/slack-manifest.test.ts +105 -0
  460. package/apps/web/src/lib/api-guard.ts +68 -0
  461. package/apps/web/src/lib/auth-context.tsx +71 -0
  462. package/apps/web/src/lib/auth.ts +128 -0
  463. package/apps/web/src/lib/boss-registry.ts +90 -0
  464. package/apps/web/src/lib/compile.ts +51 -0
  465. package/apps/web/src/lib/db.ts +1196 -0
  466. package/apps/web/src/lib/diff.ts +43 -0
  467. package/apps/web/src/lib/mcp-mask.ts +91 -0
  468. package/apps/web/src/lib/portal.tsx +23 -0
  469. package/apps/web/src/lib/skill-templates.ts +148 -0
  470. package/apps/web/src/lib/slack-manifest.ts +85 -0
  471. package/apps/web/src/middleware.ts +68 -0
  472. package/apps/web/tailwind.config.js +6 -0
  473. package/apps/web/tsconfig.json +23 -0
  474. package/apps/web/vitest.config.mts +21 -0
  475. package/cli/.claude/settings.local.json +6 -0
  476. package/cli/README.md +281 -0
  477. package/cli/node_modules/.package-lock.json +427 -0
  478. package/cli/node_modules/commander/LICENSE +22 -0
  479. package/cli/node_modules/commander/Readme.md +1157 -0
  480. package/cli/node_modules/commander/esm.mjs +16 -0
  481. package/cli/node_modules/commander/index.js +24 -0
  482. package/cli/node_modules/commander/lib/argument.js +149 -0
  483. package/cli/node_modules/commander/lib/command.js +2509 -0
  484. package/cli/node_modules/commander/lib/error.js +39 -0
  485. package/cli/node_modules/commander/lib/help.js +520 -0
  486. package/cli/node_modules/commander/lib/option.js +330 -0
  487. package/cli/node_modules/commander/lib/suggestSimilar.js +101 -0
  488. package/cli/node_modules/commander/package-support.json +16 -0
  489. package/cli/node_modules/commander/package.json +84 -0
  490. package/cli/node_modules/commander/typings/esm.d.mts +3 -0
  491. package/cli/node_modules/commander/typings/index.d.ts +969 -0
  492. package/cli/package-lock.json +449 -0
  493. package/cli/package.json +44 -0
  494. package/cli/src/commands/init.ts +514 -0
  495. package/cli/src/commands/manage.ts +115 -0
  496. package/cli/src/index.ts +63 -0
  497. package/cli/tsconfig.json +14 -0
  498. package/docker-compose.yml +122 -0
  499. package/docs/agents/boss-agents.mdx +108 -0
  500. package/docs/agents/creating-agents.mdx +132 -0
  501. package/docs/agents/memory.mdx +113 -0
  502. package/docs/agents/tools.mdx +103 -0
  503. package/docs/configuration/env-vars.mdx +166 -0
  504. package/docs/configuration/mcp-servers.mdx +203 -0
  505. package/docs/configuration/slack-app.mdx +175 -0
  506. package/docs/docs.json +79 -0
  507. package/docs/favicon.svg +17 -0
  508. package/docs/features/history.mdx +60 -0
  509. package/docs/features/import-export.mdx +77 -0
  510. package/docs/features/logs.mdx +131 -0
  511. package/docs/features/multi-workspace.mdx +90 -0
  512. package/docs/features/scheduled-jobs.mdx +231 -0
  513. package/docs/features/users.mdx +92 -0
  514. package/docs/introduction.mdx +160 -0
  515. package/docs/logo/dark.svg +17 -0
  516. package/docs/logo/light.svg +17 -0
  517. package/docs/logo/wide-dark.svg +12 -0
  518. package/docs/logo/wide-light.svg +12 -0
  519. package/docs/quickstart.mdx +270 -0
  520. package/docs/self-hosting/docker.mdx +151 -0
  521. package/docs/self-hosting/production.mdx +176 -0
  522. package/package.json +20 -36
  523. package/packages/shared/dist/index.d.ts +8 -0
  524. package/packages/shared/dist/index.d.ts.map +1 -0
  525. package/packages/shared/dist/index.js +24 -0
  526. package/packages/shared/dist/index.js.map +1 -0
  527. package/packages/shared/dist/types.d.ts +584 -0
  528. package/packages/shared/dist/types.d.ts.map +1 -0
  529. package/packages/shared/dist/types.js +39 -0
  530. package/packages/shared/dist/types.js.map +1 -0
  531. package/packages/shared/package.json +15 -0
  532. package/packages/shared/src/db/schema.sql +354 -0
  533. package/packages/shared/src/index.ts +8 -0
  534. package/packages/shared/src/types.ts +683 -0
  535. package/packages/shared/tsconfig.json +17 -0
  536. package/scripts/dev.sh +45 -0
  537. /package/{dist → cli/dist}/commands/init.d.ts +0 -0
  538. /package/{dist → cli/dist}/commands/init.js +0 -0
  539. /package/{dist → cli/dist}/commands/manage.d.ts +0 -0
  540. /package/{dist → cli/dist}/commands/manage.js +0 -0
  541. /package/{dist → cli/dist}/index.d.ts +0 -0
  542. /package/{dist → cli/dist}/index.js +0 -0
@@ -0,0 +1,1161 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * @fileoverview 5-step agent onboarding wizard.
5
+ *
6
+ * Steps: Name & Role → Slack App → Credentials → Tools → Review
7
+ *
8
+ * Route: /agents/new
9
+ * @module web/app/agents/new
10
+ */
11
+
12
+ import React, { useState, useEffect, useRef } from 'react';
13
+ import { AlertTriangle, Eye, EyeOff } from 'lucide-react';
14
+ import { useRouter } from 'next/navigation';
15
+ import type { Agent, McpServer } from '@slackhive/shared';
16
+ import { generateSlackManifest } from '@/lib/slack-manifest';
17
+
18
+ // ─── State ────────────────────────────────────────────────────────────────────
19
+
20
+ interface AgentExportPayload {
21
+ version: number;
22
+ exportedAt?: string;
23
+ claudeMd: string;
24
+ skills: { category: string; filename: string; content: string; sortOrder: number }[];
25
+ }
26
+
27
+ interface WizardState {
28
+ name: string; slug: string; description: string; persona: string;
29
+ model: string; isBoss: boolean;
30
+ reportsToIds: string[];
31
+ slackBotToken: string; slackAppToken: string; slackSigningSecret: string;
32
+ mcpServerIds: string[];
33
+ skillTemplate: 'blank' | 'data-analyst' | 'writer' | 'developer';
34
+ importPayload: AgentExportPayload | null;
35
+ }
36
+
37
+ const INITIAL: WizardState = {
38
+ name: '', slug: '', description: '', persona: '',
39
+ model: 'claude-opus-4-6', isBoss: false,
40
+ reportsToIds: [],
41
+ slackBotToken: '', slackAppToken: '', slackSigningSecret: '',
42
+ mcpServerIds: [], skillTemplate: 'blank', importPayload: null,
43
+ };
44
+
45
+ const MODELS = [
46
+ { value: 'claude-opus-4-6', label: 'Opus 4.6', sub: 'Most capable' },
47
+ { value: 'claude-sonnet-4-6', label: 'Sonnet 4.6', sub: 'Balanced' },
48
+ { value: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5', sub: 'Fastest' },
49
+ ];
50
+
51
+ const TEMPLATES = [
52
+ { value: 'blank', label: 'Blank', desc: 'Minimal identity only' },
53
+ { value: 'data-analyst', label: 'Data Analyst', desc: 'SQL, Redshift, metrics' },
54
+ { value: 'writer', label: 'Writer', desc: 'Content & summaries' },
55
+ { value: 'developer', label: 'Developer', desc: 'Code review & dev' },
56
+ ];
57
+
58
+ // ─── Wizard ───────────────────────────────────────────────────────────────────
59
+
60
+ /**
61
+ * New agent onboarding wizard.
62
+ *
63
+ * @returns {JSX.Element}
64
+ */
65
+ export default function NewAgentWizard() {
66
+ const router = useRouter();
67
+ const [step, setStep] = useState(0);
68
+ const [state, setState] = useState<WizardState>(INITIAL);
69
+ const [catalog, setCatalog] = useState<McpServer[]>([]);
70
+ const [agents, setAgents] = useState<Agent[]>([]);
71
+ const [submitting, setSubmitting] = useState(false);
72
+ const [error, setError] = useState('');
73
+
74
+ const bosses = agents.filter(a => a.isBoss);
75
+
76
+ useEffect(() => {
77
+ fetch('/api/mcps').then(r => r.json()).then(setCatalog);
78
+ fetch('/api/agents').then(r => r.json()).then((a: Agent[]) => {
79
+ setAgents(a);
80
+ }).catch(() => {});
81
+ }, []);
82
+
83
+ const update = (patch: Partial<WizardState>) => setState(s => ({ ...s, ...patch }));
84
+
85
+ // Boss agents skip the MCPs & Skills step (their CLAUDE.md is auto-generated)
86
+ const totalSteps = state.isBoss ? 4 : 5;
87
+ const stepLabels = state.isBoss
88
+ ? ['Name & Role', 'Slack App', 'Credentials', 'Review']
89
+ : ['Name & Role', 'Slack App', 'Credentials', 'Tools', 'Review'];
90
+
91
+ const next = () => setStep(s => Math.min(s + 1, totalSteps - 1));
92
+ const back = () => setStep(s => Math.max(s - 1, 0));
93
+
94
+ const canNext = () => {
95
+ if (step === 0) return !!(state.name && state.slug);
96
+ if (step === 2) return !!(state.slackBotToken && state.slackAppToken && state.slackSigningSecret);
97
+ return true;
98
+ };
99
+
100
+ const submit = async () => {
101
+ setSubmitting(true); setError('');
102
+ try {
103
+ const r = await fetch('/api/agents', {
104
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
105
+ body: JSON.stringify({
106
+ ...state,
107
+ reportsTo: state.isBoss ? [] : state.reportsToIds,
108
+ }),
109
+ });
110
+ const data = await r.json();
111
+ if (!r.ok) { setError(data.error ?? 'Failed to create agent'); return; }
112
+
113
+ // Apply imported config if provided
114
+ if (state.importPayload) {
115
+ const { claudeMd, skills } = state.importPayload;
116
+ await fetch(`/api/agents/${data.id}/claude-md`, {
117
+ method: 'PUT', headers: { 'Content-Type': 'text/plain' }, body: claudeMd,
118
+ });
119
+ await Promise.all(skills.map(s =>
120
+ fetch(`/api/agents/${data.id}/skills`, {
121
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
122
+ body: JSON.stringify(s),
123
+ })
124
+ ));
125
+ }
126
+
127
+ router.push(`/agents/${data.slug}`);
128
+ } finally { setSubmitting(false); }
129
+ };
130
+
131
+ return (
132
+ <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
133
+ {/* Top bar */}
134
+ <div style={{
135
+ padding: '18px 36px', borderBottom: '1px solid var(--border)',
136
+ display: 'flex', alignItems: 'center', gap: 12,
137
+ background: 'var(--surface)',
138
+ }}>
139
+ <a href="/" style={{ color: 'var(--muted)', textDecoration: 'none', fontSize: 12 }}>
140
+ ← Agents
141
+ </a>
142
+ <span style={{ color: 'var(--border-2)' }}>/</span>
143
+ <span style={{ fontSize: 13, color: 'var(--text)', fontWeight: 500 }}>New Agent</span>
144
+ </div>
145
+
146
+ {/* Content */}
147
+ <div style={{ flex: 1, display: 'flex', padding: '40px 36px', gap: 36, maxWidth: 860, width: '100%' }}>
148
+
149
+ {/* ── Left: step nav ───────────────────────────────────────────── */}
150
+ <div style={{ width: 180, flexShrink: 0 }}>
151
+ <div style={{ position: 'sticky', top: 40 }}>
152
+ {stepLabels.map((label, i) => (
153
+ <div
154
+ key={i}
155
+ onClick={() => i < step && setStep(i)}
156
+ style={{
157
+ display: 'flex', alignItems: 'center', gap: 10,
158
+ padding: '9px 0', cursor: i < step ? 'pointer' : 'default',
159
+ }}
160
+ >
161
+ <div style={{
162
+ width: 26, height: 26, borderRadius: '50%', flexShrink: 0,
163
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
164
+ fontSize: i < step ? 11 : 12, fontWeight: 600,
165
+ background: i < step ? 'var(--accent)' :
166
+ i === step ? 'rgba(59,130,246,0.15)' :
167
+ 'var(--border)',
168
+ color: i < step ? '#fff' :
169
+ i === step ? 'var(--accent)' :
170
+ 'var(--subtle)',
171
+ border: i === step ? '1.5px solid var(--accent)' : '1.5px solid transparent',
172
+ transition: 'all 0.2s',
173
+ }}>
174
+ {i < step ? '✓' : i + 1}
175
+ </div>
176
+ <span style={{
177
+ fontSize: 13,
178
+ fontWeight: i === step ? 600 : 400,
179
+ color: i === step ? 'var(--text)' : i < step ? 'var(--muted)' : 'var(--subtle)',
180
+ transition: 'color 0.2s',
181
+ }}>{label}</span>
182
+ </div>
183
+ ))}
184
+ </div>
185
+ </div>
186
+
187
+ {/* ── Right: step panel ────────────────────────────────────────── */}
188
+ <div style={{ flex: 1, minWidth: 0 }}>
189
+ <div
190
+ style={{
191
+ background: 'var(--surface)',
192
+ borderRadius: 'var(--radius-lg)',
193
+ padding: '32px',
194
+ boxShadow: 'var(--shadow-card)',
195
+ }}
196
+ className="fade-up"
197
+ key={step}
198
+ >
199
+ {step === 0 && <Step1Identity state={state} update={update} bosses={bosses} />}
200
+ {step === 1 && <Step2SlackApp state={state} />}
201
+ {step === 2 && <Step3Tokens state={state} update={update} />}
202
+ {step === 3 && !state.isBoss && <Step4McpsSkills state={state} update={update} catalog={catalog} />}
203
+ {((step === 3 && state.isBoss) || step === 4) && <Step5Review state={state} update={update} catalog={catalog} agents={agents} />}
204
+ </div>
205
+
206
+ {error && (
207
+ <div style={{
208
+ marginTop: 12, background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)',
209
+ borderRadius: 8, padding: '10px 14px', fontSize: 13, color: '#f87171',
210
+ }}>{error}</div>
211
+ )}
212
+
213
+ {/* Nav buttons */}
214
+ <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 16 }}>
215
+ <button
216
+ onClick={back} disabled={step === 0}
217
+ style={{
218
+ background: 'transparent', color: step === 0 ? 'var(--subtle)' : 'var(--muted)',
219
+ border: '1.5px solid var(--border)', borderRadius: 'var(--radius)',
220
+ padding: '10px 20px', fontSize: 14, fontWeight: 500, cursor: step === 0 ? 'not-allowed' : 'pointer',
221
+ fontFamily: 'var(--font-sans)', transition: 'border-color 0.15s, color 0.15s',
222
+ }}
223
+ onMouseEnter={e => { if (step > 0) { (e.currentTarget as HTMLElement).style.color = 'var(--text)'; (e.currentTarget as HTMLElement).style.borderColor = 'var(--border-2)'; } }}
224
+ onMouseLeave={e => { (e.currentTarget as HTMLElement).style.color = 'var(--muted)'; (e.currentTarget as HTMLElement).style.borderColor = 'var(--border)'; }}
225
+ >← Back</button>
226
+
227
+ {step < totalSteps - 1 ? (
228
+ <button
229
+ onClick={next} disabled={!canNext()}
230
+ style={{
231
+ background: canNext() ? 'var(--accent)' : 'var(--border)',
232
+ color: '#fff', border: 'none', borderRadius: 'var(--radius)',
233
+ padding: '10px 24px', fontSize: 14, fontWeight: 600,
234
+ cursor: canNext() ? 'pointer' : 'not-allowed',
235
+ fontFamily: 'var(--font-sans)', transition: 'opacity 0.15s, transform 0.15s',
236
+ boxShadow: canNext() ? 'var(--shadow-sm)' : 'none',
237
+ letterSpacing: '-0.01em',
238
+ }}
239
+ onMouseEnter={e => { if (canNext()) { (e.currentTarget as HTMLElement).style.opacity = '0.88'; (e.currentTarget as HTMLElement).style.transform = 'translateY(-1px)'; } }}
240
+ onMouseLeave={e => { (e.currentTarget as HTMLElement).style.opacity = '1'; (e.currentTarget as HTMLElement).style.transform = 'translateY(0)'; }}
241
+ >Continue →</button>
242
+ ) : (
243
+ <button
244
+ onClick={submit}
245
+ disabled={submitting || !state.slackBotToken || !state.slackAppToken || !state.slackSigningSecret}
246
+ style={{
247
+ background: submitting ? 'var(--border)' : '#16a34a',
248
+ color: '#fff', border: 'none', borderRadius: 'var(--radius)',
249
+ padding: '10px 24px', fontSize: 14, fontWeight: 600,
250
+ cursor: submitting ? 'not-allowed' : 'pointer',
251
+ fontFamily: 'var(--font-sans)', transition: 'opacity 0.15s, transform 0.15s',
252
+ boxShadow: !submitting ? 'var(--shadow-sm)' : 'none',
253
+ letterSpacing: '-0.01em',
254
+ }}
255
+ onMouseEnter={e => { if (!submitting) { (e.currentTarget as HTMLElement).style.opacity = '0.88'; (e.currentTarget as HTMLElement).style.transform = 'translateY(-1px)'; } }}
256
+ onMouseLeave={e => { (e.currentTarget as HTMLElement).style.opacity = '1'; (e.currentTarget as HTMLElement).style.transform = 'translateY(0)'; }}
257
+ >{submitting ? 'Creating…' : 'Create Agent'}</button>
258
+ )}
259
+ </div>
260
+ </div>
261
+ </div>
262
+ </div>
263
+ );
264
+ }
265
+
266
+ // ─── Step 1: Identity ─────────────────────────────────────────────────────────
267
+
268
+ function Step1Identity({ state, update, bosses }: {
269
+ state: WizardState; update: (p: Partial<WizardState>) => void;
270
+ bosses: Agent[];
271
+ }) {
272
+ const autoSlug = (name: string) =>
273
+ name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
274
+
275
+ const toggleBoss = (id: string) => {
276
+ const next = state.reportsToIds.includes(id)
277
+ ? state.reportsToIds.filter(x => x !== id)
278
+ : [...state.reportsToIds, id];
279
+ update({ reportsToIds: next });
280
+ };
281
+
282
+ return (
283
+ <div>
284
+ <StepHeader step={1} title="Name your agent" desc="Give it a name and decide if it leads a team or works as a specialist." />
285
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14, marginBottom: 14 }}>
286
+ <Field label="Agent Name *" value={state.name} placeholder="e.g. GILFOYLE"
287
+ onChange={v => update({ name: v, slug: autoSlug(v) })} />
288
+ <Field label="Slug *" value={state.slug} placeholder="e.g. gilfoyle"
289
+ hint="Lowercase, hyphens only"
290
+ onChange={v => update({ slug: v.toLowerCase().replace(/[^a-z0-9-]/g, '') })} />
291
+ </div>
292
+ <Field label="Description" value={state.description} placeholder="Data warehouse NLQ, Redshift queries, business metrics"
293
+ hint="Used by the boss to decide when to delegate here."
294
+ onChange={v => update({ description: v })} />
295
+ <TextArea label="Persona" value={state.persona}
296
+ placeholder="You are GILFOYLE, a cynical but brilliant data engineer…"
297
+ hint="Injected into CLAUDE.md as the agent's identity and personality."
298
+ rows={3} onChange={v => update({ persona: v })} />
299
+
300
+ {/* Model selector */}
301
+ <div style={{ marginBottom: 14 }}>
302
+ <label style={{ display: 'block', fontSize: 12, fontWeight: 500, color: 'var(--muted)', marginBottom: 8 }}>
303
+ Model
304
+ </label>
305
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8 }}>
306
+ {MODELS.map(m => (
307
+ <label key={m.value} style={{
308
+ display: 'flex', flexDirection: 'column', gap: 2,
309
+ padding: '10px 12px', border: `1px solid ${state.model === m.value ? 'var(--accent)' : 'var(--border)'}`,
310
+ borderRadius: 8, cursor: 'pointer',
311
+ background: state.model === m.value ? 'rgba(59,130,246,0.08)' : 'transparent',
312
+ transition: 'border-color 0.15s, background 0.15s',
313
+ }}>
314
+ <input type="radio" name="model" value={m.value} checked={state.model === m.value}
315
+ onChange={() => update({ model: m.value })} style={{ display: 'none' }} />
316
+ <span style={{ fontSize: 12.5, fontWeight: 600, color: state.model === m.value ? 'var(--accent)' : 'var(--text)' }}>
317
+ {m.label}
318
+ </span>
319
+ <span style={{ fontSize: 11, color: 'var(--subtle)' }}>{m.sub}</span>
320
+ </label>
321
+ ))}
322
+ </div>
323
+ </div>
324
+
325
+ {/* Boss toggle */}
326
+ <div style={{ marginBottom: 14 }}>
327
+ <label style={{
328
+ display: 'flex', alignItems: 'center', gap: 10,
329
+ padding: '10px 14px', border: `1px solid ${state.isBoss ? 'var(--accent)' : 'var(--border)'}`,
330
+ borderRadius: 8, cursor: 'pointer',
331
+ background: state.isBoss ? 'rgba(59,130,246,0.08)' : 'transparent',
332
+ transition: 'all 0.15s',
333
+ }}>
334
+ <input type="checkbox" checked={state.isBoss}
335
+ onChange={e => update({ isBoss: e.target.checked, reportsToIds: [] })}
336
+ style={{ accentColor: 'var(--accent)', width: 14, height: 14 }} />
337
+ <div>
338
+ <div style={{ fontSize: 13, fontWeight: 600, color: state.isBoss ? 'var(--accent)' : 'var(--text)' }}>
339
+ This agent is a Boss
340
+ </div>
341
+ <div style={{ fontSize: 11.5, color: 'var(--subtle)' }}>
342
+ Boss agents orchestrate specialists. Their CLAUDE.md is auto-generated from the team.
343
+ </div>
344
+ </div>
345
+ </label>
346
+ </div>
347
+
348
+ {/* Reports to — multi-select boss agents (only shown for non-boss agents) */}
349
+ {!state.isBoss && (
350
+ <div style={{ marginBottom: 14 }}>
351
+ <label style={{ display: 'block', fontSize: 12, fontWeight: 500, color: 'var(--muted)', marginBottom: 6 }}>
352
+ Reports to
353
+ </label>
354
+ {bosses.length === 0 ? (
355
+ <div style={{
356
+ border: '1px dashed var(--border)', borderRadius: 8, padding: '12px 14px',
357
+ fontSize: 12.5, color: 'var(--subtle)',
358
+ }}>
359
+ No boss agents yet — create a boss first, or mark this agent as a boss above.
360
+ </div>
361
+ ) : (
362
+ <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
363
+ {bosses.map((boss, i) => (
364
+ <label key={boss.id} style={{
365
+ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 14px',
366
+ cursor: 'pointer',
367
+ borderBottom: i < bosses.length - 1 ? '1px solid var(--border)' : 'none',
368
+ background: state.reportsToIds.includes(boss.id) ? 'rgba(59,130,246,0.06)' : 'transparent',
369
+ transition: 'background 0.12s',
370
+ }}>
371
+ <input type="checkbox"
372
+ checked={state.reportsToIds.includes(boss.id)}
373
+ onChange={() => toggleBoss(boss.id)}
374
+ style={{ accentColor: 'var(--accent)', width: 14, height: 14 }} />
375
+ <span style={{ fontSize: 13, color: 'var(--text)' }}>{boss.name}</span>
376
+ </label>
377
+ ))}
378
+ </div>
379
+ )}
380
+ {state.reportsToIds.length > 0 && (
381
+ <p style={{ margin: '5px 0 0', fontSize: 11, color: 'var(--subtle)' }}>
382
+ This agent will appear in the team registry of {state.reportsToIds.length} boss{state.reportsToIds.length > 1 ? 'es' : ''}.
383
+ </p>
384
+ )}
385
+ </div>
386
+ )}
387
+
388
+ {/* Import config */}
389
+ </div>
390
+ );
391
+ }
392
+
393
+ // ─── Import config picker ─────────────────────────────────────────────────────
394
+
395
+ function ImportConfigPicker({ value, onChange, compact }: {
396
+ value: AgentExportPayload | null;
397
+ onChange: (p: AgentExportPayload | null) => void;
398
+ compact?: boolean;
399
+ }) {
400
+ const [error, setError] = useState('');
401
+ const ref = useRef<HTMLInputElement>(null);
402
+
403
+ const handleFile = (e: React.ChangeEvent<HTMLInputElement>) => {
404
+ const file = e.target.files?.[0];
405
+ if (!file) return;
406
+ e.target.value = '';
407
+ setError('');
408
+ const reader = new FileReader();
409
+ reader.onload = (ev) => {
410
+ try {
411
+ const data = JSON.parse(ev.target?.result as string);
412
+ if (typeof data.claudeMd !== 'string' || !Array.isArray(data.skills)) {
413
+ setError('Invalid file — must contain claudeMd (string) and skills (array)'); return;
414
+ }
415
+ if (typeof data.version !== 'number') {
416
+ setError('Invalid file — missing version field'); return;
417
+ }
418
+ onChange(data);
419
+ } catch {
420
+ setError('Could not parse file — must be valid JSON');
421
+ }
422
+ };
423
+ reader.readAsText(file);
424
+ };
425
+
426
+ if (compact) {
427
+ return (
428
+ <div>
429
+ {!value ? (
430
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
431
+ <div>
432
+ <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', marginBottom: 1 }}>Import config</div>
433
+ <div style={{ fontSize: 11.5, color: 'var(--subtle)' }}>Load CLAUDE.md + skills from an exported agent</div>
434
+ {error && <div style={{ fontSize: 11, color: 'var(--danger)', marginTop: 3 }}>{error}</div>}
435
+ </div>
436
+ <button type="button" onClick={() => ref.current?.click()} style={{
437
+ display: 'flex', alignItems: 'center', gap: 5, flexShrink: 0,
438
+ padding: '6px 12px', borderRadius: 6,
439
+ border: '1px solid var(--border-2)', background: '#fff',
440
+ fontSize: 12, fontWeight: 500, color: 'var(--muted)', cursor: 'pointer',
441
+ }}>
442
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="none">
443
+ <path d="M8 2v9M4 7l4 4 4-4M2 13h12" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
444
+ </svg>
445
+ Choose file
446
+ </button>
447
+ </div>
448
+ ) : (
449
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
450
+ <div>
451
+ <div style={{ fontSize: 13, fontWeight: 600, color: '#7c3aed' }}>
452
+ Config loaded
453
+ </div>
454
+ <div style={{ fontSize: 11.5, color: 'var(--muted)' }}>
455
+ {value.skills.length} skill{value.skills.length !== 1 ? 's' : ''} · CLAUDE.md included
456
+ </div>
457
+ </div>
458
+ <button type="button" onClick={() => { onChange(null); setError(''); }} style={{
459
+ background: 'none', border: 'none', cursor: 'pointer',
460
+ fontSize: 11.5, color: 'var(--muted)', padding: '3px 6px',
461
+ }}>Remove</button>
462
+ </div>
463
+ )}
464
+ <input ref={ref} type="file" accept=".json" style={{ display: 'none' }} onChange={handleFile} />
465
+ </div>
466
+ );
467
+ }
468
+
469
+ return (
470
+ <div style={{
471
+ marginTop: 20, padding: '14px 16px', borderRadius: 10,
472
+ border: `1.5px dashed ${value ? '#8b5cf6' : 'var(--border-2)'}`,
473
+ background: value ? 'rgba(139,92,246,0.03)' : 'var(--surface)',
474
+ transition: 'all 0.2s',
475
+ }}>
476
+ {!value ? (
477
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
478
+ <div>
479
+ <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--text)', marginBottom: 2 }}>
480
+ Import from existing config
481
+ </div>
482
+ <div style={{ fontSize: 12, color: 'var(--muted)' }}>
483
+ Load CLAUDE.md + skills from a previously exported agent
484
+ </div>
485
+ {error && <div style={{ fontSize: 11.5, color: 'var(--danger)', marginTop: 4 }}>{error}</div>}
486
+ </div>
487
+ <button type="button" onClick={() => ref.current?.click()} style={{
488
+ display: 'flex', alignItems: 'center', gap: 6, flexShrink: 0,
489
+ padding: '7px 14px', borderRadius: 7,
490
+ border: '1.5px solid var(--border-2)', background: '#fff',
491
+ fontSize: 12.5, fontWeight: 500, color: 'var(--muted)', cursor: 'pointer',
492
+ transition: 'all 0.15s',
493
+ }}
494
+ onMouseEnter={e => { (e.currentTarget as HTMLElement).style.borderColor = '#8b5cf6'; (e.currentTarget as HTMLElement).style.color = 'var(--text)'; }}
495
+ onMouseLeave={e => { (e.currentTarget as HTMLElement).style.borderColor = 'var(--border-2)'; (e.currentTarget as HTMLElement).style.color = 'var(--muted)'; }}
496
+ >
497
+ <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
498
+ <path d="M8 2v9M4 7l4 4 4-4M2 13h12" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/>
499
+ </svg>
500
+ Choose file
501
+ </button>
502
+ </div>
503
+ ) : (
504
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, justifyContent: 'space-between' }}>
505
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
506
+ <div style={{
507
+ width: 32, height: 32, borderRadius: 8, flexShrink: 0,
508
+ background: 'rgba(139,92,246,0.1)',
509
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
510
+ }}>
511
+ <svg width="15" height="15" viewBox="0 0 16 16" fill="none">
512
+ <path d="M9 2H4a1 1 0 00-1 1v10a1 1 0 001 1h8a1 1 0 001-1V6L9 2z" stroke="#8b5cf6" strokeWidth="1.4" strokeLinejoin="round"/>
513
+ <path d="M9 2v4h4" stroke="#8b5cf6" strokeWidth="1.4" strokeLinejoin="round"/>
514
+ </svg>
515
+ </div>
516
+ <div>
517
+ <div style={{ fontSize: 13, fontWeight: 500, color: 'var(--text)' }}>
518
+ Config file loaded
519
+ </div>
520
+ <div style={{ fontSize: 11.5, color: 'var(--muted)' }}>
521
+ {value.skills.length} skill{value.skills.length !== 1 ? 's' : ''} · CLAUDE.md included
522
+ {value.exportedAt && ` · ${new Date(value.exportedAt).toLocaleDateString()}`}
523
+ </div>
524
+ </div>
525
+ </div>
526
+ <button type="button" onClick={() => { onChange(null); setError(''); }} style={{
527
+ background: 'none', border: 'none', cursor: 'pointer',
528
+ fontSize: 11.5, color: 'var(--muted)', padding: '4px 8px', borderRadius: 5,
529
+ }}>Remove</button>
530
+ </div>
531
+ )}
532
+ <input ref={ref} type="file" accept=".json" style={{ display: 'none' }} onChange={handleFile} />
533
+ </div>
534
+ );
535
+ }
536
+
537
+ // ─── Step 2: Slack App ────────────────────────────────────────────────────────
538
+
539
+ function Step2SlackApp({ state }: { state: WizardState }) {
540
+ const [copied, setCopied] = useState(false);
541
+ const manifest = state.name
542
+ ? JSON.stringify(generateSlackManifest({ name: state.name, description: state.description, isBoss: state.isBoss }), null, 2)
543
+ : '{}';
544
+
545
+ const copy = () => { navigator.clipboard.writeText(manifest); setCopied(true); setTimeout(() => setCopied(false), 2000); };
546
+
547
+ return (
548
+ <div>
549
+ <StepHeader step={2} title="Create a Slack app" desc="Paste this manifest into Slack — it configures everything automatically in about 2 minutes." />
550
+
551
+ {/* Steps */}
552
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 0, marginBottom: 20 }}>
553
+ {([
554
+ {
555
+ n: 1,
556
+ title: 'Open api.slack.com/apps',
557
+ body: <>Click <Kbd>Create New App</Kbd> → <Kbd>From a manifest</Kbd> and select your workspace.</>,
558
+ },
559
+ {
560
+ n: 2,
561
+ title: 'Paste the manifest',
562
+ body: <>Switch to the <Kbd>JSON</Kbd> tab, paste the manifest below, click <Kbd>Next</Kbd> → <Kbd>Create</Kbd>.</>,
563
+ },
564
+ {
565
+ n: 3,
566
+ title: 'Install to workspace',
567
+ body: <>In the sidebar go to <Kbd>Install App</Kbd> → <Kbd>Install to Workspace</Kbd> → Allow. This generates your Bot Token — it won&apos;t appear until you install.</>,
568
+ },
569
+ ] as { n: number; title: string; body: React.ReactNode }[]).map(({ n, title, body }, i, arr) => (
570
+ <div key={n} style={{ display: 'flex', gap: 0 }}>
571
+ {/* Connector line + dot */}
572
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', width: 32, flexShrink: 0 }}>
573
+ <div style={{
574
+ width: 26, height: 26, borderRadius: '50%', flexShrink: 0,
575
+ background: 'var(--accent)', color: '#fff',
576
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
577
+ fontSize: 11, fontWeight: 700, zIndex: 1,
578
+ }}>{n}</div>
579
+ {i < arr.length - 1 && (
580
+ <div style={{ width: 2, flex: 1, background: 'var(--border)', minHeight: 20, marginTop: 4 }} />
581
+ )}
582
+ </div>
583
+ {/* Content */}
584
+ <div style={{ paddingLeft: 14, paddingBottom: i < arr.length - 1 ? 20 : 0, paddingTop: 3 }}>
585
+ <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', marginBottom: 3 }}>{title}</div>
586
+ <div style={{ fontSize: 12.5, color: 'var(--muted)', lineHeight: 1.65 }}>{body}</div>
587
+ </div>
588
+ </div>
589
+ ))}
590
+ </div>
591
+
592
+ {/* Manifest block */}
593
+ <div style={{ background: 'var(--surface-2)', border: '1.5px solid var(--border)', borderRadius: 'var(--radius)', overflow: 'hidden' }}>
594
+ <div style={{
595
+ display: 'flex', justifyContent: 'space-between', alignItems: 'center',
596
+ padding: '10px 16px', borderBottom: '1px solid var(--border)',
597
+ }}>
598
+ <span style={{ fontSize: 11.5, color: 'var(--muted)', fontFamily: 'var(--font-mono)' }}>
599
+ {state.name || 'agent'}-manifest.json
600
+ </span>
601
+ <button onClick={copy} style={{
602
+ background: copied ? '#dcfce7' : 'var(--accent)',
603
+ color: copied ? '#16a34a' : '#fff',
604
+ border: 'none', cursor: 'pointer', fontSize: 12, fontWeight: 600,
605
+ fontFamily: 'var(--font-sans)', padding: '5px 12px', borderRadius: 6,
606
+ transition: 'all 0.15s',
607
+ }}>{copied ? '✓ Copied!' : 'Copy manifest'}</button>
608
+ </div>
609
+ <pre style={{
610
+ margin: 0, padding: '16px', fontSize: 11.5, color: 'var(--accent)',
611
+ fontFamily: 'var(--font-mono)', overflow: 'auto', maxHeight: 260, lineHeight: 1.6,
612
+ }}>{manifest}</pre>
613
+ </div>
614
+ </div>
615
+ );
616
+ }
617
+
618
+ // ─── Step 3: Tokens ───────────────────────────────────────────────────────────
619
+
620
+ function Step3Tokens({ state, update }: { state: WizardState; update: (p: Partial<WizardState>) => void }) {
621
+ return (
622
+ <div>
623
+ <StepHeader step={3} title="Add your tokens" desc="Three values from your Slack app — paste each one below." />
624
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
625
+ <TokenCard
626
+ label="Bot Token"
627
+ prefix="xoxb-"
628
+ path={['OAuth & Permissions', 'Bot User OAuth Token']}
629
+ screenshot={<BotTokenScreenshot />}
630
+ placeholder="xoxb-..."
631
+ value={state.slackBotToken}
632
+ onChange={v => update({ slackBotToken: v })}
633
+ />
634
+ <TokenCard
635
+ label="App-Level Token"
636
+ prefix="xapp-"
637
+ path={['Basic Information', 'App-Level Tokens']}
638
+ note={<>Click <strong>Generate Token and Scopes</strong>, add scope <code style={{ fontFamily: 'var(--font-mono)', fontSize: 11, background: 'rgba(59,130,246,0.08)', color: 'var(--accent)', padding: '1px 6px', borderRadius: 4 }}>connections:write</code>, then generate.</>}
639
+ screenshot={<AppTokenScreenshot />}
640
+ placeholder="xapp-..."
641
+ value={state.slackAppToken}
642
+ onChange={v => update({ slackAppToken: v })}
643
+ />
644
+ <TokenCard
645
+ label="Signing Secret"
646
+ prefix=""
647
+ path={['Basic Information', 'App Credentials', 'Signing Secret']}
648
+ screenshot={<SigningSecretScreenshot />}
649
+ placeholder="abc123def..."
650
+ value={state.slackSigningSecret}
651
+ onChange={v => update({ slackSigningSecret: v })}
652
+ />
653
+ </div>
654
+ </div>
655
+ );
656
+ }
657
+
658
+ // ─── Token card ───────────────────────────────────────────────────────────────
659
+
660
+ function TokenCard({ label, prefix, path, note, screenshot, placeholder, value, onChange }: {
661
+ label: string; prefix: string;
662
+ path: string[];
663
+ note?: React.ReactNode;
664
+ screenshot?: React.ReactNode;
665
+ placeholder: string; value: string; onChange: (v: string) => void;
666
+ }) {
667
+ const [show, setShow] = useState(false);
668
+ const [showScreenshot, setShowScreenshot] = useState(false);
669
+ const isDirty = value.length > 0;
670
+ const isValid = prefix ? value.startsWith(prefix) : value.length >= 8;
671
+
672
+ return (
673
+ <div style={{
674
+ borderRadius: 'var(--radius)',
675
+ border: `1.5px solid ${isDirty ? (isValid ? '#86efac' : '#fca5a5') : 'var(--border)'}`,
676
+ background: '#fff',
677
+ overflow: 'hidden',
678
+ transition: 'border-color 0.2s',
679
+ }}>
680
+ {/* Header: label + breadcrumb */}
681
+ <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
682
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, minWidth: 0 }}>
683
+ <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)', flexShrink: 0 }}>{label}</span>
684
+ <div style={{ display: 'flex', alignItems: 'center', gap: 5, overflow: 'hidden' }}>
685
+ {path.map((p, i) => (
686
+ <React.Fragment key={i}>
687
+ {i > 0 && <span style={{ fontSize: 11, color: 'var(--border-2)' }}>›</span>}
688
+ <span style={{
689
+ fontSize: 11.5, whiteSpace: 'nowrap',
690
+ color: i === path.length - 1 ? 'var(--muted)' : 'var(--subtle)',
691
+ fontWeight: i === path.length - 1 ? 500 : 400,
692
+ }}>{p}</span>
693
+ </React.Fragment>
694
+ ))}
695
+ </div>
696
+ </div>
697
+ <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexShrink: 0 }}>
698
+ {screenshot && (
699
+ <button
700
+ type="button"
701
+ onClick={() => setShowScreenshot(s => !s)}
702
+ style={{
703
+ background: showScreenshot ? 'var(--surface-2)' : 'none',
704
+ border: '1px solid var(--border)', borderRadius: 5,
705
+ padding: '3px 8px', cursor: 'pointer',
706
+ color: 'var(--muted)', fontSize: 11, fontWeight: 500,
707
+ fontFamily: 'var(--font-sans)', transition: 'all 0.15s',
708
+ }}
709
+ >{showScreenshot ? 'Hide guide' : 'Where?'}</button>
710
+ )}
711
+ {isDirty && (
712
+ <span style={{ fontSize: 12, fontWeight: 700, color: isValid ? '#16a34a' : '#ef4444' }}>
713
+ {isValid ? '✓' : '✗'}
714
+ </span>
715
+ )}
716
+ </div>
717
+ </div>
718
+
719
+ {/* Screenshot guide */}
720
+ {showScreenshot && screenshot && (
721
+ <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--border)', background: 'var(--surface-2)' }}>
722
+ {screenshot}
723
+ </div>
724
+ )}
725
+
726
+ {/* Note */}
727
+ {note && (
728
+ <div style={{ padding: '8px 16px', borderBottom: '1px solid var(--border)', fontSize: 12, color: 'var(--muted)', background: 'var(--surface-2)', lineHeight: 1.6 }}>
729
+ {note}
730
+ </div>
731
+ )}
732
+
733
+ {/* Input */}
734
+ <div style={{ display: 'flex', alignItems: 'center', padding: '0 4px 0 16px', gap: 6 }}>
735
+ <input
736
+ type={show ? 'text' : 'password'}
737
+ value={value}
738
+ onChange={e => onChange(e.target.value)}
739
+ placeholder={placeholder}
740
+ style={{
741
+ flex: 1, background: 'transparent', border: 'none',
742
+ padding: '12px 0', color: 'var(--text)',
743
+ fontSize: 13, fontFamily: value ? 'var(--font-mono)' : 'var(--font-sans)',
744
+ outline: 'none',
745
+ }}
746
+ />
747
+ <button
748
+ type="button"
749
+ onClick={() => setShow(s => !s)}
750
+ style={{
751
+ background: 'none', border: 'none', cursor: 'pointer',
752
+ color: 'var(--subtle)', fontSize: 12, padding: '8px',
753
+ fontFamily: 'var(--font-sans)', lineHeight: 1,
754
+ }}
755
+ >{show ? <EyeOff size={14} /> : <Eye size={14} />}</button>
756
+ </div>
757
+ </div>
758
+ );
759
+ }
760
+
761
+ // ─── Slack UI screenshot mockups ──────────────────────────────────────────────
762
+
763
+ const slackStyles = {
764
+ wrap: {
765
+ borderRadius: 8, overflow: 'hidden', border: '1px solid #e0e0e0',
766
+ fontFamily: 'Lato, -apple-system, sans-serif', fontSize: 12,
767
+ } as React.CSSProperties,
768
+ chrome: {
769
+ background: '#f1f1f1', padding: '6px 10px',
770
+ borderBottom: '1px solid #ddd', display: 'flex', alignItems: 'center', gap: 8,
771
+ } as React.CSSProperties,
772
+ urlBar: {
773
+ flex: 1, background: '#fff', border: '1px solid #ddd', borderRadius: 4,
774
+ padding: '3px 8px', fontSize: 10.5, color: '#555',
775
+ fontFamily: 'monospace', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' as const,
776
+ } as React.CSSProperties,
777
+ body: { background: '#fff', padding: '14px 16px' } as React.CSSProperties,
778
+ sectionTitle: { fontSize: 12, fontWeight: 700, color: '#1d1c1d', marginBottom: 10 } as React.CSSProperties,
779
+ row: {
780
+ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
781
+ padding: '8px 10px', border: '1px solid #e8e8e8', borderRadius: 5,
782
+ } as React.CSSProperties,
783
+ label: { fontSize: 11.5, color: '#616061' } as React.CSSProperties,
784
+ token: { fontSize: 11, fontFamily: 'monospace', color: '#1d1c1d', letterSpacing: 1 } as React.CSSProperties,
785
+ greenBtn: {
786
+ background: '#007a5a', color: '#fff', border: 'none', borderRadius: 4,
787
+ padding: '5px 10px', fontSize: 11, fontWeight: 700, cursor: 'default',
788
+ boxShadow: '0 0 0 3px rgba(0,122,90,0.25)',
789
+ } as React.CSSProperties,
790
+ badge: {
791
+ background: '#e8f5f0', color: '#007a5a', border: '1px solid #b8dfd4',
792
+ borderRadius: 3, padding: '1px 6px', fontSize: 10.5, fontWeight: 600,
793
+ } as React.CSSProperties,
794
+ highlight: {
795
+ outline: '2.5px solid #007a5a', outlineOffset: 2, borderRadius: 4,
796
+ } as React.CSSProperties,
797
+ };
798
+
799
+ function ChromeBar({ url }: { url: string }) {
800
+ return (
801
+ <div style={slackStyles.chrome}>
802
+ <div style={{ display: 'flex', gap: 4 }}>
803
+ {['#ff5f57','#febc2e','#28c840'].map(c => (
804
+ <div key={c} style={{ width: 8, height: 8, borderRadius: '50%', background: c }} />
805
+ ))}
806
+ </div>
807
+ <div style={slackStyles.urlBar}>{url}</div>
808
+ </div>
809
+ );
810
+ }
811
+
812
+ function SlackLayout({ url, activeNav, children }: { url: string; activeNav: string; children: React.ReactNode }) {
813
+ const navItems = ['Basic Information', 'Socket Mode', 'Install App', 'OAuth & Permissions', 'Event Subscriptions', 'App Manifest'];
814
+ return (
815
+ <div style={slackStyles.wrap}>
816
+ <ChromeBar url={url} />
817
+ <div style={{ display: 'flex', background: '#fff' }}>
818
+ {/* Sidebar */}
819
+ <div style={{ width: 148, background: '#f8f8f8', borderRight: '1px solid #e0e0e0', padding: '10px 0', flexShrink: 0 }}>
820
+ {navItems.map(item => (
821
+ <div key={item} style={{
822
+ padding: '5px 12px', fontSize: 11, cursor: 'default',
823
+ background: item === activeNav ? '#e8e8e8' : 'transparent',
824
+ color: item === activeNav ? '#1d1c1d' : '#616061',
825
+ fontWeight: item === activeNav ? 700 : 400,
826
+ borderLeft: item === activeNav ? '2px solid #007a5a' : '2px solid transparent',
827
+ }}>{item}</div>
828
+ ))}
829
+ </div>
830
+ {/* Content */}
831
+ <div style={{ flex: 1, padding: '14px 16px', minWidth: 0 }}>{children}</div>
832
+ </div>
833
+ </div>
834
+ );
835
+ }
836
+
837
+ function BotTokenScreenshot() {
838
+ return (
839
+ <SlackLayout url="api.slack.com/apps/A.../oauth" activeNav="OAuth & Permissions">
840
+ {/* Install notice */}
841
+ <div style={{ background: '#fff8e6', border: '1px solid #f5c842', borderRadius: 5, padding: '7px 10px', marginBottom: 10, fontSize: 11, color: '#7a5c00', lineHeight: 1.5, display: 'flex', alignItems: 'flex-start', gap: 6 }}>
842
+ <AlertTriangle size={12} style={{ flexShrink: 0, marginTop: 1 }} />
843
+ <span>Token only appears after <strong>Install to Workspace</strong> (sidebar → Install App)</span>
844
+ </div>
845
+ <div style={slackStyles.sectionTitle}>OAuth Tokens</div>
846
+ <div style={{ border: '1px solid #e8e8e8', borderRadius: 5, overflow: 'hidden' }}>
847
+ <div style={{ padding: '8px 10px', background: '#fafafa', borderBottom: '1px solid #e8e8e8' }}>
848
+ <div style={{ fontSize: 11, color: '#616061', marginBottom: 1 }}>Bot User OAuth Token</div>
849
+ <div style={{ ...slackStyles.token, fontSize: 10.5 }}>xoxb-••••••••••••-••••••••••••-••••••••••••</div>
850
+ <div style={{ fontSize: 10, color: '#616061', marginTop: 2 }}>Access Level: Workspace</div>
851
+ </div>
852
+ <div style={{ padding: '7px 10px', display: 'flex', justifyContent: 'flex-end' }}>
853
+ <button style={{ ...slackStyles.greenBtn, ...slackStyles.highlight }}>Copy</button>
854
+ </div>
855
+ </div>
856
+ </SlackLayout>
857
+ );
858
+ }
859
+
860
+ function AppTokenScreenshot() {
861
+ return (
862
+ <SlackLayout url="api.slack.com/apps/A.../general" activeNav="Basic Information">
863
+ <div style={slackStyles.sectionTitle}>App-Level Tokens</div>
864
+ <div style={{ fontSize: 11, color: '#616061', marginBottom: 10, lineHeight: 1.5 }}>
865
+ App-level tokens represent your app across organizations.
866
+ </div>
867
+ <div style={{ marginBottom: 10 }}>
868
+ <button style={{ ...slackStyles.greenBtn, ...slackStyles.highlight }}>Generate Token and Scopes</button>
869
+ </div>
870
+ <div style={{ border: '1px solid #e8e8e8', borderRadius: 5, padding: '8px 10px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
871
+ <div>
872
+ <div style={{ fontSize: 11, fontWeight: 700, color: '#1d1c1d', marginBottom: 4 }}>SlackHive</div>
873
+ <span style={slackStyles.badge}>connections:write</span>
874
+ </div>
875
+ <div style={slackStyles.token}>xapp-••••••••••••</div>
876
+ </div>
877
+ </SlackLayout>
878
+ );
879
+ }
880
+
881
+ function SigningSecretScreenshot() {
882
+ return (
883
+ <SlackLayout url="api.slack.com/apps/A.../general" activeNav="Basic Information">
884
+ <div style={slackStyles.sectionTitle}>App Credentials</div>
885
+ <div style={{ fontSize: 11, color: '#616061', marginBottom: 10, lineHeight: 1.5 }}>
886
+ These credentials allow your app to access the Slack API. Keep them secret.
887
+ </div>
888
+ {[
889
+ { label: 'App ID', value: 'A0AN••••••9', mono: true },
890
+ { label: 'Client ID', value: '9186••••••••••••••••', mono: true },
891
+ { label: 'Client Secret', value: '••••••••••', mono: true },
892
+ ].map(row => (
893
+ <div key={row.label} style={{ display: 'flex', justifyContent: 'space-between', padding: '5px 0', borderBottom: '1px solid #f0f0f0', fontSize: 11, color: '#616061' }}>
894
+ <span>{row.label}</span>
895
+ <span style={{ fontFamily: 'monospace', color: '#1d1c1d' }}>{row.value}</span>
896
+ </div>
897
+ ))}
898
+ {/* Signing Secret highlighted */}
899
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 10px', marginTop: 6, border: '1px solid #e8e8e8', borderRadius: 5, ...slackStyles.highlight }}>
900
+ <div>
901
+ <div style={{ fontSize: 11, fontWeight: 700, color: '#1d1c1d', marginBottom: 2 }}>Signing Secret</div>
902
+ <div style={slackStyles.token}>••••••••••••••••••••••••</div>
903
+ </div>
904
+ <button style={slackStyles.greenBtn}>Show</button>
905
+ </div>
906
+ </SlackLayout>
907
+ );
908
+ }
909
+
910
+ // ─── Kbd helper ───────────────────────────────────────────────────────────────
911
+
912
+ function Kbd({ children }: { children: React.ReactNode }) {
913
+ return (
914
+ <kbd style={{
915
+ display: 'inline-block', fontFamily: 'var(--font-sans)', fontSize: 11.5, fontWeight: 600,
916
+ background: 'var(--surface-3)', color: 'var(--text)',
917
+ border: '1px solid var(--border)', borderRadius: 5,
918
+ padding: '1px 7px', lineHeight: 1.7, whiteSpace: 'nowrap',
919
+ }}>{children}</kbd>
920
+ );
921
+ }
922
+
923
+ // ─── Step 4: MCPs & Skills ────────────────────────────────────────────────────
924
+
925
+ function Step4McpsSkills({
926
+ state, update, catalog,
927
+ }: { state: WizardState; update: (p: Partial<WizardState>) => void; catalog: McpServer[] }) {
928
+ const toggle = (id: string) => {
929
+ const ids = state.mcpServerIds.includes(id)
930
+ ? state.mcpServerIds.filter(x => x !== id)
931
+ : [...state.mcpServerIds, id];
932
+ update({ mcpServerIds: ids });
933
+ };
934
+
935
+ return (
936
+ <div>
937
+ <StepHeader step={4} title="Tools & Skills" desc="Attach MCP servers and pick a starting skill template. You can change these anytime." />
938
+
939
+ {/* MCP list */}
940
+ <div style={{ marginBottom: 22 }}>
941
+ <label style={{ display: 'block', fontSize: 12, fontWeight: 500, color: 'var(--muted)', marginBottom: 8, letterSpacing: '0.06em', textTransform: 'uppercase' }}>
942
+ MCP Servers
943
+ </label>
944
+ {catalog.length === 0 ? (
945
+ <div style={{
946
+ border: '1px dashed var(--border)', borderRadius: 8, padding: '16px',
947
+ fontSize: 12.5, color: 'var(--subtle)', textAlign: 'center',
948
+ }}>
949
+ No MCP servers yet — you can add them after creation in <strong style={{ color: 'var(--muted)' }}>Settings → MCP Catalog</strong>
950
+ </div>
951
+ ) : (
952
+ <div style={{ border: '1px solid var(--border)', borderRadius: 10, overflow: 'hidden' }}>
953
+ {catalog.map((mcp, i) => (
954
+ <label key={mcp.id} style={{
955
+ display: 'flex', alignItems: 'center', gap: 12, padding: '11px 14px',
956
+ cursor: mcp.enabled ? 'pointer' : 'not-allowed',
957
+ borderBottom: i < catalog.length - 1 ? '1px solid var(--border)' : 'none',
958
+ opacity: mcp.enabled ? 1 : 0.4, transition: 'background 0.12s',
959
+ }}
960
+ onMouseEnter={e => { if (mcp.enabled) (e.currentTarget as HTMLElement).style.background = 'rgba(255,255,255,0.03)'; }}
961
+ onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = 'transparent'; }}
962
+ >
963
+ <input type="checkbox" checked={state.mcpServerIds.includes(mcp.id)}
964
+ onChange={() => toggle(mcp.id)} disabled={!mcp.enabled}
965
+ style={{ accentColor: 'var(--accent)', width: 14, height: 14 }} />
966
+ <div>
967
+ <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
968
+ <span style={{ fontSize: 13, fontWeight: 500, color: 'var(--text)' }}>{mcp.name}</span>
969
+ <span style={{
970
+ fontSize: 10.5, fontFamily: 'var(--font-mono)',
971
+ color: 'var(--muted)', background: 'var(--border)', padding: '1px 6px', borderRadius: 4,
972
+ }}>{mcp.type}</span>
973
+ </div>
974
+ {mcp.description && <p style={{ margin: 0, fontSize: 11.5, color: 'var(--subtle)' }}>{mcp.description}</p>}
975
+ </div>
976
+ </label>
977
+ ))}
978
+ </div>
979
+ )}
980
+ </div>
981
+
982
+ {/* Template grid */}
983
+ <div>
984
+ <label style={{ display: 'block', fontSize: 12, fontWeight: 500, color: 'var(--muted)', marginBottom: 8, letterSpacing: '0.06em', textTransform: 'uppercase' }}>
985
+ Skill Template
986
+ </label>
987
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
988
+ {TEMPLATES.map(t => {
989
+ const active = state.skillTemplate === t.value && !state.importPayload;
990
+ return (
991
+ <label key={t.value} style={{
992
+ padding: '12px 14px', border: `1px solid ${active ? 'var(--accent)' : 'var(--border)'}`,
993
+ borderRadius: 8, cursor: 'pointer',
994
+ background: active ? 'rgba(59,130,246,0.08)' : 'transparent',
995
+ transition: 'all 0.15s',
996
+ }}>
997
+ <input type="radio" name="template" value={t.value} checked={active}
998
+ onChange={() => update({ skillTemplate: t.value as WizardState['skillTemplate'], importPayload: null })}
999
+ style={{ display: 'none' }} />
1000
+ <div style={{ fontSize: 13, fontWeight: 600, color: active ? 'var(--accent)' : 'var(--text)', marginBottom: 2 }}>
1001
+ {t.label}
1002
+ </div>
1003
+ <div style={{ fontSize: 11.5, color: 'var(--subtle)' }}>{t.desc}</div>
1004
+ </label>
1005
+ );
1006
+ })}
1007
+
1008
+ {/* Import tile */}
1009
+ {(() => {
1010
+ const active = !!state.importPayload;
1011
+ return (
1012
+ <div style={{
1013
+ padding: '12px 14px',
1014
+ border: `1px solid ${active ? '#8b5cf6' : 'var(--border)'}`,
1015
+ borderRadius: 8,
1016
+ background: active ? 'rgba(139,92,246,0.08)' : 'transparent',
1017
+ transition: 'all 0.15s',
1018
+ gridColumn: 'span 2',
1019
+ }}>
1020
+ <ImportConfigPicker
1021
+ value={state.importPayload}
1022
+ onChange={payload => update({ importPayload: payload })}
1023
+ compact
1024
+ />
1025
+ </div>
1026
+ );
1027
+ })()}
1028
+ </div>
1029
+ </div>
1030
+ </div>
1031
+ );
1032
+ }
1033
+
1034
+ // ─── Step 5: Review ───────────────────────────────────────────────────────────
1035
+
1036
+ function Step5Review({ state, update, catalog, agents }: { state: WizardState; update: (p: Partial<WizardState>) => void; catalog: McpServer[]; agents: Agent[] }) {
1037
+ const assignedBosses = agents.filter(a => state.reportsToIds.includes(a.id));
1038
+ const assignedMcps = catalog.filter(m => state.mcpServerIds.includes(m.id));
1039
+ const template = state.importPayload
1040
+ ? `Import (${state.importPayload.skills.length} skills)`
1041
+ : (TEMPLATES.find(t => t.value === state.skillTemplate)?.label ?? state.skillTemplate);
1042
+
1043
+ const reportsToValue = state.isBoss
1044
+ ? '—'
1045
+ : assignedBosses.length > 0
1046
+ ? assignedBosses.map(b => b.name).join(', ')
1047
+ : 'None';
1048
+
1049
+ return (
1050
+ <div>
1051
+ <StepHeader step={state.isBoss ? 4 : 5} title="Looks good?" desc="Review the details below, then hit Create Agent to launch." />
1052
+
1053
+ <div style={{
1054
+ background: 'var(--surface-2)', border: '1px solid var(--border)',
1055
+ borderRadius: 10, overflow: 'hidden', marginBottom: 18,
1056
+ }}>
1057
+ {[
1058
+ { label: 'Name', value: state.name, mono: false },
1059
+ { label: 'Slug', value: `@${state.slug}`, mono: true },
1060
+ { label: 'Model', value: state.model, mono: true },
1061
+ { label: 'Role', value: state.isBoss ? 'Boss (orchestrator)' : 'Specialist', mono: false },
1062
+ { label: 'Reports to', value: reportsToValue, mono: false },
1063
+ { label: 'Description', value: state.description || '—', mono: false },
1064
+ { label: 'Bot Token', value: `${state.slackBotToken.slice(0, 12)}…`, mono: true },
1065
+ { label: 'App Token', value: `${state.slackAppToken.slice(0, 12)}…`, mono: true },
1066
+ { label: 'Signing Secret',value: '••••••••', mono: true },
1067
+ ...(!state.isBoss ? [
1068
+ { label: 'MCPs', value: assignedMcps.length > 0 ? assignedMcps.map(m => m.name).join(', ') : 'None', mono: false },
1069
+ { label: 'Skill Template',value: template, mono: false },
1070
+ ] : [
1071
+ { label: 'Skills', value: 'Auto-generated from team registry', mono: false },
1072
+ ]),
1073
+ ].map((row, i, arr) => (
1074
+ <div key={row.label} style={{
1075
+ display: 'flex', alignItems: 'baseline', gap: 12,
1076
+ padding: '10px 16px',
1077
+ borderBottom: i < arr.length - 1 ? '1px solid var(--border)' : 'none',
1078
+ }}>
1079
+ <span style={{ width: 130, flexShrink: 0, fontSize: 12, color: 'var(--subtle)' }}>{row.label}</span>
1080
+ <span style={{
1081
+ fontSize: 13, color: 'var(--text)',
1082
+ fontFamily: row.mono ? 'var(--font-mono)' : 'var(--font-sans)',
1083
+ }}>{row.value}</span>
1084
+ </div>
1085
+ ))}
1086
+ </div>
1087
+
1088
+ <div style={{
1089
+ background: '#f0fdf4', border: '1px solid #bbf7d0',
1090
+ borderRadius: 'var(--radius)', padding: '14px 16px', fontSize: 13, color: '#15803d', lineHeight: 1.65,
1091
+ }}>
1092
+ Once created, the runner picks up the agent automatically and connects to Slack.
1093
+ Manage skills, MCPs, and channel permissions from the agent detail page.
1094
+ </div>
1095
+ </div>
1096
+ );
1097
+ }
1098
+
1099
+ // ─── Shared primitives ────────────────────────────────────────────────────────
1100
+
1101
+ function StepHeader({ step, title, desc }: { step: number; title: string; desc: string }) {
1102
+ return (
1103
+ <div style={{ marginBottom: 22 }}>
1104
+ <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--accent)', letterSpacing: '0.08em', textTransform: 'uppercase', marginBottom: 4 }}>
1105
+ Step {step}
1106
+ </div>
1107
+ <h2 style={{ margin: '0 0 4px', fontSize: 18, fontWeight: 700, color: 'var(--text)', letterSpacing: '-0.02em' }}>
1108
+ {title}
1109
+ </h2>
1110
+ <p style={{ margin: 0, fontSize: 13, color: 'var(--muted)' }}>{desc}</p>
1111
+ </div>
1112
+ );
1113
+ }
1114
+
1115
+ function Field({ label, value, onChange, placeholder, hint, type = 'text' }: {
1116
+ label: string; value: string; onChange: (v: string) => void;
1117
+ placeholder?: string; hint?: string; type?: string;
1118
+ }) {
1119
+ return (
1120
+ <div style={{ marginBottom: 14 }}>
1121
+ <label style={{ display: 'block', fontSize: 12, fontWeight: 500, color: 'var(--muted)', marginBottom: 5 }}>
1122
+ {label}
1123
+ </label>
1124
+ <input type={type} value={value} onChange={e => onChange(e.target.value)} placeholder={placeholder}
1125
+ style={{
1126
+ width: '100%', background: 'var(--surface-2)', border: '1.5px solid var(--border)',
1127
+ borderRadius: 'var(--radius)', padding: '10px 14px', color: 'var(--text)',
1128
+ fontSize: 14, fontFamily: 'var(--font-sans)', outline: 'none',
1129
+ transition: 'border-color 0.15s',
1130
+ }}
1131
+ onFocus={e => (e.currentTarget.style.borderColor = 'var(--accent)')}
1132
+ onBlur={e => (e.currentTarget.style.borderColor = 'var(--border)')}
1133
+ />
1134
+ {hint && <p style={{ margin: '5px 0 0', fontSize: 12, color: 'var(--subtle)' }}>{hint}</p>}
1135
+ </div>
1136
+ );
1137
+ }
1138
+
1139
+ function TextArea({ label, value, onChange, placeholder, hint, rows = 3 }: {
1140
+ label: string; value: string; onChange: (v: string) => void;
1141
+ placeholder?: string; hint?: string; rows?: number;
1142
+ }) {
1143
+ return (
1144
+ <div style={{ marginBottom: 14 }}>
1145
+ <label style={{ display: 'block', fontSize: 12, fontWeight: 500, color: 'var(--muted)', marginBottom: 5 }}>
1146
+ {label}
1147
+ </label>
1148
+ <textarea value={value} onChange={e => onChange(e.target.value)} placeholder={placeholder} rows={rows}
1149
+ style={{
1150
+ width: '100%', background: 'var(--surface-2)', border: '1.5px solid var(--border)',
1151
+ borderRadius: 'var(--radius)', padding: '10px 14px', color: 'var(--text)',
1152
+ fontSize: 14, fontFamily: 'var(--font-sans)', outline: 'none', resize: 'vertical',
1153
+ transition: 'border-color 0.15s', lineHeight: 1.55,
1154
+ }}
1155
+ onFocus={e => (e.currentTarget.style.borderColor = 'var(--accent)')}
1156
+ onBlur={e => (e.currentTarget.style.borderColor = 'var(--border)')}
1157
+ />
1158
+ {hint && <p style={{ margin: '5px 0 0', fontSize: 12, color: 'var(--subtle)' }}>{hint}</p>}
1159
+ </div>
1160
+ );
1161
+ }