opencastle 0.6.0 → 0.7.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 (223) hide show
  1. package/README.md +5 -4
  2. package/dist/cli/adapters/claude-code.d.ts.map +1 -1
  3. package/dist/cli/adapters/claude-code.js +30 -3
  4. package/dist/cli/adapters/claude-code.js.map +1 -1
  5. package/dist/cli/adapters/cursor.d.ts.map +1 -1
  6. package/dist/cli/adapters/cursor.js +27 -3
  7. package/dist/cli/adapters/cursor.js.map +1 -1
  8. package/dist/cli/adapters/opencode.d.ts +20 -0
  9. package/dist/cli/adapters/opencode.d.ts.map +1 -0
  10. package/dist/cli/adapters/opencode.js +265 -0
  11. package/dist/cli/adapters/opencode.js.map +1 -0
  12. package/dist/cli/adapters/vscode.d.ts.map +1 -1
  13. package/dist/cli/adapters/vscode.js +37 -6
  14. package/dist/cli/adapters/vscode.js.map +1 -1
  15. package/dist/cli/copy.d.ts +12 -0
  16. package/dist/cli/copy.d.ts.map +1 -1
  17. package/dist/cli/copy.js +27 -0
  18. package/dist/cli/copy.js.map +1 -1
  19. package/dist/cli/detect.d.ts +1 -1
  20. package/dist/cli/detect.js +21 -15
  21. package/dist/cli/detect.js.map +1 -1
  22. package/dist/cli/init.d.ts.map +1 -1
  23. package/dist/cli/init.js +143 -94
  24. package/dist/cli/init.js.map +1 -1
  25. package/dist/cli/manifest.d.ts +1 -1
  26. package/dist/cli/manifest.d.ts.map +1 -1
  27. package/dist/cli/manifest.js +2 -1
  28. package/dist/cli/manifest.js.map +1 -1
  29. package/dist/cli/mcp.d.ts +6 -6
  30. package/dist/cli/mcp.d.ts.map +1 -1
  31. package/dist/cli/mcp.js +104 -33
  32. package/dist/cli/mcp.js.map +1 -1
  33. package/dist/cli/prompt.d.ts +19 -0
  34. package/dist/cli/prompt.d.ts.map +1 -1
  35. package/dist/cli/prompt.js +143 -0
  36. package/dist/cli/prompt.js.map +1 -1
  37. package/dist/cli/stack-config.d.ts +23 -0
  38. package/dist/cli/stack-config.d.ts.map +1 -1
  39. package/dist/cli/stack-config.js +128 -124
  40. package/dist/cli/stack-config.js.map +1 -1
  41. package/dist/cli/types.d.ts +26 -9
  42. package/dist/cli/types.d.ts.map +1 -1
  43. package/dist/cli/types.js +26 -1
  44. package/dist/cli/types.js.map +1 -1
  45. package/dist/cli/update.d.ts.map +1 -1
  46. package/dist/cli/update.js +60 -19
  47. package/dist/cli/update.js.map +1 -1
  48. package/dist/orchestrator/plugins/chrome-devtools/config.d.ts +3 -0
  49. package/dist/orchestrator/plugins/chrome-devtools/config.d.ts.map +1 -0
  50. package/dist/orchestrator/plugins/chrome-devtools/config.js +28 -0
  51. package/dist/orchestrator/plugins/chrome-devtools/config.js.map +1 -0
  52. package/dist/orchestrator/plugins/contentful/config.d.ts +3 -0
  53. package/dist/orchestrator/plugins/contentful/config.d.ts.map +1 -0
  54. package/dist/orchestrator/plugins/contentful/config.js +48 -0
  55. package/dist/orchestrator/plugins/contentful/config.js.map +1 -0
  56. package/dist/orchestrator/plugins/convex/config.d.ts +3 -0
  57. package/dist/orchestrator/plugins/convex/config.d.ts.map +1 -0
  58. package/dist/orchestrator/plugins/convex/config.js +32 -0
  59. package/dist/orchestrator/plugins/convex/config.js.map +1 -0
  60. package/dist/orchestrator/plugins/index.d.ts +28 -0
  61. package/dist/orchestrator/plugins/index.d.ts.map +1 -0
  62. package/dist/orchestrator/plugins/index.js +63 -0
  63. package/dist/orchestrator/plugins/index.js.map +1 -0
  64. package/dist/orchestrator/plugins/jira/config.d.ts +3 -0
  65. package/dist/orchestrator/plugins/jira/config.d.ts.map +1 -0
  66. package/dist/orchestrator/plugins/jira/config.js +29 -0
  67. package/dist/orchestrator/plugins/jira/config.js.map +1 -0
  68. package/dist/orchestrator/plugins/linear/config.d.ts +3 -0
  69. package/dist/orchestrator/plugins/linear/config.d.ts.map +1 -0
  70. package/dist/orchestrator/plugins/linear/config.js +33 -0
  71. package/dist/orchestrator/plugins/linear/config.js.map +1 -0
  72. package/dist/orchestrator/plugins/nx/config.d.ts +3 -0
  73. package/dist/orchestrator/plugins/nx/config.d.ts.map +1 -0
  74. package/dist/orchestrator/plugins/nx/config.js +28 -0
  75. package/dist/orchestrator/plugins/nx/config.js.map +1 -0
  76. package/dist/orchestrator/plugins/sanity/config.d.ts +3 -0
  77. package/dist/orchestrator/plugins/sanity/config.d.ts.map +1 -0
  78. package/dist/orchestrator/plugins/sanity/config.js +43 -0
  79. package/dist/orchestrator/plugins/sanity/config.js.map +1 -0
  80. package/dist/orchestrator/plugins/slack/config.d.ts +3 -0
  81. package/dist/orchestrator/plugins/slack/config.d.ts.map +1 -0
  82. package/dist/orchestrator/plugins/slack/config.js +34 -0
  83. package/dist/orchestrator/plugins/slack/config.js.map +1 -0
  84. package/dist/orchestrator/plugins/strapi/config.d.ts +3 -0
  85. package/dist/orchestrator/plugins/strapi/config.d.ts.map +1 -0
  86. package/dist/orchestrator/plugins/strapi/config.js +40 -0
  87. package/dist/orchestrator/plugins/strapi/config.js.map +1 -0
  88. package/dist/orchestrator/plugins/supabase/config.d.ts +3 -0
  89. package/dist/orchestrator/plugins/supabase/config.d.ts.map +1 -0
  90. package/dist/orchestrator/plugins/supabase/config.js +33 -0
  91. package/dist/orchestrator/plugins/supabase/config.js.map +1 -0
  92. package/dist/orchestrator/plugins/teams/config.d.ts +3 -0
  93. package/dist/orchestrator/plugins/teams/config.d.ts.map +1 -0
  94. package/dist/orchestrator/plugins/teams/config.js +43 -0
  95. package/dist/orchestrator/plugins/teams/config.js.map +1 -0
  96. package/dist/orchestrator/plugins/types.d.ts +61 -0
  97. package/dist/orchestrator/plugins/types.d.ts.map +1 -0
  98. package/dist/orchestrator/plugins/types.js +2 -0
  99. package/dist/orchestrator/plugins/types.js.map +1 -0
  100. package/dist/orchestrator/plugins/vercel/config.d.ts +3 -0
  101. package/dist/orchestrator/plugins/vercel/config.d.ts.map +1 -0
  102. package/dist/orchestrator/plugins/vercel/config.js +32 -0
  103. package/dist/orchestrator/plugins/vercel/config.js.map +1 -0
  104. package/package.json +1 -1
  105. package/src/cli/adapters/claude-code.ts +36 -4
  106. package/src/cli/adapters/cursor.ts +42 -4
  107. package/src/cli/adapters/opencode.ts +320 -0
  108. package/src/cli/adapters/vscode.ts +40 -8
  109. package/src/cli/copy.ts +32 -0
  110. package/src/cli/detect.ts +17 -17
  111. package/src/cli/init.ts +157 -99
  112. package/src/cli/manifest.ts +2 -1
  113. package/src/cli/mcp.ts +129 -50
  114. package/src/cli/prompt.ts +176 -0
  115. package/src/cli/stack-config.ts +174 -145
  116. package/src/cli/types.ts +39 -8
  117. package/src/cli/update.ts +71 -20
  118. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  119. package/src/orchestrator/agent-workflows/README.md +1 -1
  120. package/src/orchestrator/agent-workflows/bug-fix.md +12 -12
  121. package/src/orchestrator/agent-workflows/data-pipeline.md +21 -20
  122. package/src/orchestrator/agent-workflows/database-migration.md +11 -11
  123. package/src/orchestrator/agent-workflows/feature-implementation.md +10 -10
  124. package/src/orchestrator/agent-workflows/performance-optimization.md +6 -6
  125. package/src/orchestrator/agent-workflows/refactoring.md +10 -10
  126. package/src/orchestrator/agent-workflows/schema-changes.md +8 -8
  127. package/src/orchestrator/agent-workflows/security-audit.md +12 -12
  128. package/src/orchestrator/agent-workflows/shared-delivery-phase.md +5 -5
  129. package/src/orchestrator/agents/api-designer.agent.md +1 -1
  130. package/src/orchestrator/agents/architect.agent.md +2 -2
  131. package/src/orchestrator/agents/content-engineer.agent.md +3 -3
  132. package/src/orchestrator/agents/copywriter.agent.md +2 -2
  133. package/src/orchestrator/agents/data-expert.agent.md +6 -6
  134. package/src/orchestrator/agents/database-engineer.agent.md +3 -3
  135. package/src/orchestrator/agents/developer.agent.md +4 -4
  136. package/src/orchestrator/agents/devops-expert.agent.md +5 -5
  137. package/src/orchestrator/agents/documentation-writer.agent.md +1 -1
  138. package/src/orchestrator/agents/performance-expert.agent.md +2 -2
  139. package/src/orchestrator/agents/release-manager.agent.md +4 -4
  140. package/src/orchestrator/agents/researcher.agent.md +3 -3
  141. package/src/orchestrator/agents/reviewer.agent.md +1 -1
  142. package/src/orchestrator/agents/security-expert.agent.md +4 -4
  143. package/src/orchestrator/agents/seo-specialist.agent.md +2 -2
  144. package/src/orchestrator/agents/team-lead.agent.md +56 -38
  145. package/src/orchestrator/agents/testing-expert.agent.md +5 -5
  146. package/src/orchestrator/agents/ui-ux-expert.agent.md +6 -6
  147. package/src/orchestrator/copilot-instructions.md +1 -1
  148. package/src/orchestrator/customizations/AGENT-FAILURES.md +1 -1
  149. package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +12 -12
  150. package/src/orchestrator/customizations/DISPUTES.md +5 -5
  151. package/src/orchestrator/customizations/KNOWN-ISSUES.md +30 -0
  152. package/src/orchestrator/customizations/LESSONS-LEARNED.md +7 -7
  153. package/src/orchestrator/customizations/README.md +5 -2
  154. package/src/orchestrator/customizations/agents/agent-registry.md +1 -1
  155. package/src/orchestrator/customizations/agents/skill-matrix.md +12 -7
  156. package/src/orchestrator/customizations/logs/README.md +1 -1
  157. package/src/orchestrator/customizations/project/decisions.md +31 -0
  158. package/src/orchestrator/customizations/project/docs-structure.md +16 -5
  159. package/src/orchestrator/customizations/project/roadmap.md +24 -0
  160. package/src/orchestrator/customizations/project/tracker-config.md +1 -1
  161. package/src/orchestrator/customizations/stack/cms-config.md +1 -1
  162. package/src/orchestrator/customizations/stack/notifications-config.md +1 -1
  163. package/src/orchestrator/instructions/ai-optimization.instructions.md +2 -2
  164. package/src/orchestrator/instructions/general.instructions.md +102 -40
  165. package/src/orchestrator/{skills/browser-testing → plugins/chrome-devtools}/SKILL.md +1 -1
  166. package/src/orchestrator/plugins/chrome-devtools/config.ts +29 -0
  167. package/src/orchestrator/{skills/contentful-cms → plugins/contentful}/SKILL.md +1 -1
  168. package/src/orchestrator/plugins/contentful/config.ts +49 -0
  169. package/src/orchestrator/{skills/convex-database → plugins/convex}/SKILL.md +1 -1
  170. package/src/orchestrator/plugins/convex/config.ts +33 -0
  171. package/src/orchestrator/plugins/index.ts +85 -0
  172. package/src/orchestrator/{skills/jira-management → plugins/jira}/SKILL.md +3 -3
  173. package/src/orchestrator/plugins/jira/config.ts +30 -0
  174. package/src/orchestrator/{skills/task-management → plugins/linear}/SKILL.md +3 -3
  175. package/src/orchestrator/plugins/linear/config.ts +34 -0
  176. package/src/orchestrator/{skills/nx-workspace → plugins/nx}/SKILL.md +1 -1
  177. package/src/orchestrator/plugins/nx/config.ts +29 -0
  178. package/src/orchestrator/{skills/sanity-cms → plugins/sanity}/SKILL.md +1 -1
  179. package/src/orchestrator/plugins/sanity/config.ts +44 -0
  180. package/src/orchestrator/{skills/slack-notifications → plugins/slack}/SKILL.md +2 -2
  181. package/src/orchestrator/plugins/slack/config.ts +35 -0
  182. package/src/orchestrator/{skills/strapi-cms → plugins/strapi}/SKILL.md +1 -1
  183. package/src/orchestrator/plugins/strapi/config.ts +41 -0
  184. package/src/orchestrator/{skills/supabase-database → plugins/supabase}/SKILL.md +1 -1
  185. package/src/orchestrator/plugins/supabase/config.ts +34 -0
  186. package/src/orchestrator/{skills/teams-notifications → plugins/teams}/SKILL.md +2 -2
  187. package/src/orchestrator/plugins/teams/config.ts +44 -0
  188. package/src/orchestrator/plugins/types.ts +79 -0
  189. package/src/orchestrator/plugins/vercel/config.ts +33 -0
  190. package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +8 -8
  191. package/src/orchestrator/prompts/brainstorm.prompt.md +3 -3
  192. package/src/orchestrator/prompts/bug-fix.prompt.md +18 -18
  193. package/src/orchestrator/prompts/create-skill.prompt.md +50 -32
  194. package/src/orchestrator/prompts/generate-task-spec.prompt.md +3 -3
  195. package/src/orchestrator/prompts/implement-feature.prompt.md +26 -26
  196. package/src/orchestrator/prompts/metrics-report.prompt.md +11 -11
  197. package/src/orchestrator/prompts/quick-refinement.prompt.md +16 -16
  198. package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +2 -2
  199. package/src/orchestrator/skills/accessibility-standards/SKILL.md +1 -1
  200. package/src/orchestrator/skills/agent-hooks/SKILL.md +27 -18
  201. package/src/orchestrator/skills/agent-memory/SKILL.md +7 -7
  202. package/src/orchestrator/skills/api-patterns/SKILL.md +6 -6
  203. package/src/orchestrator/skills/code-commenting/SKILL.md +1 -1
  204. package/src/orchestrator/skills/context-map/SKILL.md +4 -4
  205. package/src/orchestrator/skills/data-engineering/SKILL.md +7 -4
  206. package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +2 -2
  207. package/src/orchestrator/skills/documentation-standards/SKILL.md +1 -1
  208. package/src/orchestrator/skills/fast-review/SKILL.md +3 -3
  209. package/src/orchestrator/skills/frontend-design/SKILL.md +1 -1
  210. package/src/orchestrator/skills/memory-merger/SKILL.md +8 -8
  211. package/src/orchestrator/skills/nextjs-patterns/SKILL.md +1 -1
  212. package/src/orchestrator/skills/panel-majority-vote/SKILL.md +2 -2
  213. package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +1 -1
  214. package/src/orchestrator/skills/performance-optimization/SKILL.md +1 -1
  215. package/src/orchestrator/skills/react-development/SKILL.md +3 -3
  216. package/src/orchestrator/skills/security-hardening/SKILL.md +27 -27
  217. package/src/orchestrator/skills/self-improvement/SKILL.md +14 -13
  218. package/src/orchestrator/skills/seo-patterns/SKILL.md +1 -1
  219. package/src/orchestrator/skills/session-checkpoints/SKILL.md +19 -19
  220. package/src/orchestrator/skills/team-lead-reference/SKILL.md +9 -9
  221. package/src/orchestrator/skills/testing-workflow/SKILL.md +13 -13
  222. package/src/orchestrator/skills/validation-gates/SKILL.md +8 -15
  223. package/src/orchestrator/mcp.json +0 -69
package/src/cli/init.ts CHANGED
@@ -1,19 +1,30 @@
1
1
  import { resolve } from 'node:path'
2
2
  import { readFile, unlink } from 'node:fs/promises'
3
3
  import { existsSync } from 'node:fs'
4
- import { select, confirm, closePrompts } from './prompt.js'
4
+ import { multiselect, confirm, closePrompts, c } from './prompt.js'
5
5
  import { readManifest, writeManifest, createManifest } from './manifest.js'
6
6
  import { removeDirIfExists } from './copy.js'
7
7
  import { updateGitignore } from './gitignore.js'
8
8
  import { getRequiredMcpEnvVars } from './stack-config.js'
9
+ import { TECH_PLUGINS, TEAM_PLUGINS } from '../orchestrator/plugins/index.js'
9
10
  import { detectRepoInfo, mergeStackIntoRepoInfo, formatRepoInfo } from './detect.js'
10
- import type { CliContext, IdeAdapter, CmsChoice, DbChoice, PmChoice, NotifChoice, StackConfig } from './types.js'
11
+ import type { CliContext, IdeAdapter, IdeChoice, TechTool, TeamTool, StackConfig } from './types.js'
11
12
 
12
13
  const ADAPTERS: Record<string, () => Promise<IdeAdapter>> = {
13
14
  vscode: () => import('./adapters/vscode.js') as Promise<IdeAdapter>,
14
15
  cursor: () => import('./adapters/cursor.js') as Promise<IdeAdapter>,
15
16
  'claude-code': () =>
16
17
  import('./adapters/claude-code.js') as Promise<IdeAdapter>,
18
+ opencode: () =>
19
+ import('./adapters/opencode.js') as Promise<IdeAdapter>,
20
+ }
21
+
22
+ /** IDE display labels */
23
+ const IDE_DISPLAY: Record<IdeChoice, string> = {
24
+ vscode: 'VS Code',
25
+ cursor: 'Cursor',
26
+ 'claude-code': 'Claude Code',
27
+ opencode: 'OpenCode',
17
28
  }
18
29
 
19
30
  export default async function init({ pkgRoot, args }: CliContext): Promise<void> {
@@ -25,7 +36,7 @@ export default async function init({ pkgRoot, args }: CliContext): Promise<void>
25
36
  let isReinit = false
26
37
  if (existing) {
27
38
  const proceed = await confirm(
28
- `OpenCastle already installed (v${existing.version}, ${existing.ide}). Re-initialize?`,
39
+ `OpenCastle already installed (v${existing.version}). Re-initialize?`,
29
40
  false
30
41
  )
31
42
  if (!proceed) {
@@ -39,91 +50,117 @@ export default async function init({ pkgRoot, args }: CliContext): Promise<void>
39
50
  await readFile(resolve(pkgRoot, 'package.json'), 'utf8')
40
51
  ) as { version: string }
41
52
 
42
- console.log(`\n 🏰 OpenCastle v${pkg.version}`)
53
+ console.log(`\n 🏰 ${c.bold('OpenCastle')} ${c.dim(`v${pkg.version}`)}`)
43
54
  console.log(
44
- ' Multi-agent orchestration framework for AI coding assistants\n'
55
+ ` ${c.dim('Multi-agent orchestration framework for AI coding assistants')}\n`
45
56
  )
46
57
 
47
58
  // ── Repo research ───────────────────────────────────────────────
48
- console.log(' Scanning repository...')
59
+ console.log(` ${c.dim('Scanning repository...')}`)
49
60
  const repoInfo = await detectRepoInfo(projectRoot)
50
61
  const summary = formatRepoInfo(repoInfo)
51
62
  if (summary) {
52
- console.log(' Detected:\n' + summary + '\n')
63
+ console.log(` ${c.green('Detected:')}\n` + summary + '\n')
53
64
  } else {
54
- console.log(' No tooling detected (empty project?)\n')
65
+ console.log(` ${c.dim('No tooling detected (empty project?)')}\n`)
55
66
  }
56
67
 
57
- // ── IDE selection ───────────────────────────────────────────────
58
- const ide = await select('Which IDE are you using?', [
59
- {
60
- label: 'VS Code',
61
- hint: 'GitHub Copilot .github/ agents, instructions, skills',
62
- value: 'vscode',
63
- },
64
- {
65
- label: 'Cursor',
66
- hint: '.cursorrules & .cursor/rules/*.mdc',
67
- value: 'cursor',
68
- },
69
- {
70
- label: 'Claude Code',
71
- hint: 'CLAUDE.md & .claude/ commands, skills',
72
- value: 'claude-code',
73
- },
74
- ])
75
-
76
- // ── CMS selection ───────────────────────────────────────────────
77
- const cms = await select('Which CMS are you using?', [
78
- { label: 'Sanity', hint: 'GROQ queries, real-time collaboration', value: 'sanity' },
79
- { label: 'Contentful', hint: 'GraphQL / REST API, structured content', value: 'contentful' },
80
- { label: 'Strapi', hint: 'Open-source headless CMS', value: 'strapi' },
81
- { label: 'None', hint: 'No CMS — skip CMS skills and agents', value: 'none' },
82
- ])
68
+ // ── IDEs (multiselect, at least 1) ─────────────────────────────
69
+ console.log(` ${c.bold('── IDEs ──────────────────────────────────────')}`)
70
+ let ides: string[] = []
71
+ while (ides.length === 0) {
72
+ ides = await multiselect('Which IDEs do you use?', [
73
+ {
74
+ label: 'VS Code',
75
+ hint: 'GitHub Copilot agents, instructions, skills',
76
+ value: 'vscode',
77
+ },
78
+ {
79
+ label: 'Cursor',
80
+ hint: '.cursorrules & .cursor/rules/*.mdc',
81
+ value: 'cursor',
82
+ },
83
+ {
84
+ label: 'Claude Code',
85
+ hint: 'CLAUDE.md & .claude/ commands, skills',
86
+ value: 'claude-code',
87
+ },
88
+ {
89
+ label: 'OpenCode',
90
+ hint: 'AGENTS.md & opencode.json',
91
+ value: 'opencode',
92
+ },
93
+ ])
94
+ if (ides.length === 0) {
95
+ console.log(` ${c.yellow('Please select at least one IDE.')}`)
96
+ }
97
+ }
83
98
 
84
- // ── Database selection ──────────────────────────────────────────
85
- const db = await select('Which database are you using?', [
86
- { label: 'Supabase', hint: 'Postgres + Auth + RLS + Edge Functions', value: 'supabase' },
87
- { label: 'Convex', hint: 'Reactive backend with real-time sync', value: 'convex' },
88
- { label: 'None', hint: 'No database — skip DB skills and agents', value: 'none' },
99
+ // ── Tech Tools (multiselect, 0-N) ──────────────────────────────
100
+ // Pre-select tools already detected in the repo
101
+ const detectedTools = new Set([
102
+ ...(repoInfo.cms ?? []),
103
+ ...(repoInfo.databases ?? []),
104
+ ...(repoInfo.deployment ?? []),
105
+ ...(repoInfo.monorepo ? [repoInfo.monorepo] : []),
89
106
  ])
90
107
 
91
- // ── Project management selection ────────────────────────────────
92
- const pm = await select('Which project management tool are you using?', [
93
- { label: 'Linear', hint: 'Issue tracking with MCP integration', value: 'linear' },
94
- { label: 'Jira', hint: 'Atlassian issue tracking via Rovo MCP', value: 'jira' },
95
- { label: 'None', hint: 'No project management — skip PM skills', value: 'none' },
96
- ])
108
+ console.log(` ${c.bold('── Tech Tools ────────────────────────────────')}`)
109
+ const techTools = await multiselect('Which tools does your project use?',
110
+ TECH_PLUGINS.map((p) => ({
111
+ label: p.label,
112
+ hint: p.hint,
113
+ value: p.id,
114
+ ...((p.preselected || detectedTools.has(p.id)) && { selected: true }),
115
+ }))
116
+ )
97
117
 
98
- // ── Notifications selection ────────────────────────────────────
99
- const notifications = await select('Which notifications tool are you using?', [
100
- { label: 'Slack', hint: 'Agent notifications and bi-directional communication', value: 'slack' },
101
- { label: 'Microsoft Teams', hint: 'Agent notifications via Teams channels', value: 'teams' },
102
- { label: 'None', hint: 'No notifications — skip messaging skills', value: 'none' },
103
- ])
118
+ // ── Team Tools (multiselect, 0-N) ──────────────────────────────
119
+ console.log(` ${c.bold('── Team Tools ────────────────────────────────')}`)
120
+ const teamTools = await multiselect('Which team tools do you use?',
121
+ TEAM_PLUGINS.map((p) => ({
122
+ label: p.label,
123
+ hint: p.hint,
124
+ value: p.id,
125
+ ...(p.preselected && { selected: true }),
126
+ }))
127
+ )
104
128
 
105
- const stack: StackConfig = { cms: cms as CmsChoice, db: db as DbChoice, pm: pm as PmChoice, notifications: notifications as NotifChoice }
129
+ const stack: StackConfig = {
130
+ ides: ides as IdeChoice[],
131
+ techTools: techTools as TechTool[],
132
+ teamTools: teamTools as TeamTool[],
133
+ }
106
134
 
107
135
  // ── Merge user choices into detected info ────────────────────
108
136
  const combinedRepoInfo = mergeStackIntoRepoInfo(repoInfo, stack)
109
137
 
110
- console.log(`\n Installing for ${ide}...`)
111
- console.log(` Stack: CMS=${stack.cms}, DB=${stack.db}, PM=${stack.pm}, Notifications=${stack.notifications}\n`)
138
+ const ideNames = ides.map((id) => IDE_DISPLAY[id as IdeChoice]).join(', ')
139
+ console.log(`\n Installing for ${c.cyan(ideNames)}...`)
140
+ if (techTools.length > 0) {
141
+ console.log(` Tech: ${c.green(techTools.join(', '))}`)
142
+ }
143
+ if (teamTools.length > 0) {
144
+ console.log(` Team: ${c.green(teamTools.join(', '))}`)
145
+ }
146
+ console.log()
112
147
 
113
148
  // ── Dry run ─────────────────────────────────────────────────────
114
149
  if (dryRun) {
115
- const adapter = await ADAPTERS[ide]()
116
- const managed = adapter.getManagedPaths()
117
- console.log(' [dry-run] Files that would be created:\n')
118
- for (const p of managed.framework) {
119
- console.log(` + ${p}`)
120
- }
121
- for (const p of managed.customizable) {
122
- console.log(` + ${p}`)
150
+ for (const ide of ides) {
151
+ const adapter = await ADAPTERS[ide]()
152
+ const managed = adapter.getManagedPaths()
153
+ console.log(` ${c.dim(`[dry-run] ${IDE_DISPLAY[ide as IdeChoice]} files:`)}\n`)
154
+ for (const p of managed.framework) {
155
+ console.log(` ${c.green('+')} ${p}`)
156
+ }
157
+ for (const p of managed.customizable) {
158
+ console.log(` ${c.green('+')} ${p}`)
159
+ }
123
160
  }
124
- console.log(` + .opencastle.json`)
125
- console.log(` + .gitignore (OpenCastle entries)`)
126
- console.log('\n No files were written.\n')
161
+ console.log(` ${c.green('+')} .opencastle.json`)
162
+ console.log(` ${c.green('+')} .gitignore (OpenCastle entries)`)
163
+ console.log(`\n ${c.dim('No files were written.')}\n`)
127
164
  closePrompts()
128
165
  return
129
166
  }
@@ -139,11 +176,12 @@ export default async function init({ pkgRoot, args }: CliContext): Promise<void>
139
176
  await unlink(fullPath)
140
177
  }
141
178
  }
142
- // Remove MCP config so it gets regenerated with new stack
179
+ // Remove MCP configs so they get regenerated with new stack
143
180
  const mcpCandidates = [
144
181
  '.vscode/mcp.json',
145
182
  '.cursor/mcp.json',
146
183
  '.claude/mcp.json',
184
+ 'opencode.json',
147
185
  ]
148
186
  for (const mcpPath of mcpCandidates) {
149
187
  const fullPath = resolve(projectRoot, mcpPath)
@@ -153,70 +191,90 @@ export default async function init({ pkgRoot, args }: CliContext): Promise<void>
153
191
  }
154
192
  }
155
193
 
156
- // ── Run adapter ─────────────────────────────────────────────────
157
- const adapter = await ADAPTERS[ide]()
158
- const results = await adapter.install(pkgRoot, projectRoot, stack, combinedRepoInfo)
194
+ // ── Run adapters for each selected IDE ──────────────────────────
195
+ let totalCreated = 0
196
+ let totalSkipped = 0
197
+ const allManagedPaths = { framework: [] as string[], customizable: [] as string[] }
198
+
199
+ for (const ide of ides) {
200
+ const adapter = await ADAPTERS[ide]()
201
+ const results = await adapter.install(pkgRoot, projectRoot, stack, combinedRepoInfo)
202
+ totalCreated += results.created.length
203
+ totalSkipped += results.skipped.length
204
+
205
+ const managed = adapter.getManagedPaths()
206
+ allManagedPaths.framework.push(...managed.framework)
207
+ allManagedPaths.customizable.push(...managed.customizable)
208
+ }
159
209
 
160
210
  // ── Write manifest ──────────────────────────────────────────────
161
- const manifest = createManifest(pkg.version, ide)
162
- manifest.managedPaths = adapter.getManagedPaths()
211
+ const manifest = createManifest(pkg.version, ides[0], ides)
212
+ manifest.managedPaths = allManagedPaths
163
213
  manifest.stack = stack
164
214
  manifest.repoInfo = combinedRepoInfo
165
215
  await writeManifest(projectRoot, manifest)
166
216
 
167
217
  // ── Update .gitignore ───────────────────────────────────────────
168
- const managedPaths = adapter.getManagedPaths()
169
- const gitignoreResult = await updateGitignore(projectRoot, managedPaths)
218
+ const gitignoreResult = await updateGitignore(projectRoot, allManagedPaths)
170
219
 
171
220
  // ── Summary ─────────────────────────────────────────────────────
172
- const created = results.created.length
173
- const skipped = results.skipped.length
174
-
175
- console.log(` ✓ Created ${created} files`)
221
+ console.log(` ${c.green('✓')} Created ${c.bold(String(totalCreated))} files`)
176
222
  if (gitignoreResult === 'created') {
177
- console.log(' ✓ Created .gitignore with OpenCastle entries')
223
+ console.log(` ${c.green('✓')} Created .gitignore with OpenCastle entries`)
178
224
  } else if (gitignoreResult === 'updated') {
179
- console.log(' ✓ Updated .gitignore with OpenCastle entries')
225
+ console.log(` ${c.green('✓')} Updated .gitignore with OpenCastle entries`)
180
226
  }
181
- if (skipped > 0) {
182
- console.log(` → Skipped ${skipped} existing files`)
227
+ if (totalSkipped > 0) {
228
+ console.log(` ${c.dim('')} Skipped ${totalSkipped} existing files`)
183
229
  }
184
230
 
185
231
  // ── Env var notice ──────────────────────────────────────────────
186
232
  const envVars = getRequiredMcpEnvVars(stack, combinedRepoInfo)
187
233
  if (envVars.length > 0) {
188
- console.log(`\n ⚠ Required environment variables for MCP servers:\n`)
234
+ console.log(`\n ${c.yellow('')} Required environment variables for MCP servers:\n`)
189
235
  for (const { envVar, hint } of envVars) {
190
- console.log(` ${envVar}`)
191
- console.log(` └ ${hint}\n`)
236
+ console.log(` ${c.bold(envVar)}`)
237
+ console.log(` ${c.dim('')} ${c.dim(hint)}\n`)
192
238
  }
193
239
  }
194
240
 
195
241
  // ── OAuth setup guides ────────────────────────────────────────
196
- if (stack.notifications === 'slack') {
197
- console.log(` 📖 Slack MCP requires a Slack App with a bot token.`)
198
- console.log(` Setup guide: https://www.opencastle.dev/guides/slack-mcp-setup\n`)
242
+ if (teamTools.includes('slack')) {
243
+ console.log(` ${c.cyan('📖')} Slack MCP requires a Slack App with a bot token.`)
244
+ console.log(` Setup guide: ${c.cyan('https://www.opencastle.dev/guides/plugins#slack')}\n`)
199
245
  }
200
246
 
201
- console.log(`\n Next steps:`)
202
- if (ide === 'vscode') {
203
- console.log(
204
- ' 0. Reload VS Code window (Cmd+Shift+P "Developer: Reload Window") to pick up agents'
205
- )
206
- } else if (ide === 'cursor') {
207
- console.log(
208
- ' 0. Reload Cursor window to pick up the new rule files'
209
- )
247
+ console.log(`\n ${c.bold('Next steps:')}`)
248
+
249
+ let step = 0
250
+ // Reload window messages for relevant IDEs
251
+ const needsReload = ides.filter((id) => ['vscode', 'cursor'].includes(id))
252
+ if (needsReload.length > 0) {
253
+ step++
254
+ if (needsReload.includes('vscode')) {
255
+ console.log(
256
+ ` ${step}. ${c.yellow('Reload VS Code window')} (Cmd+Shift+P → "Developer: Reload Window")`
257
+ )
258
+ }
259
+ if (needsReload.includes('cursor')) {
260
+ console.log(
261
+ ` ${step}. ${c.yellow('Reload Cursor window')} to pick up the new rule files`
262
+ )
263
+ }
210
264
  }
265
+
211
266
  if (envVars.length > 0) {
267
+ step++
212
268
  console.log(
213
- ` 1. Set the environment variable${envVars.length > 1 ? 's' : ''} listed above`
269
+ ` ${step}. Set the environment variable${envVars.length > 1 ? 's' : ''} listed above`
214
270
  )
215
271
  }
272
+ step++
216
273
  console.log(
217
- ` ${envVars.length > 0 ? '2' : '1'}. Run the "Bootstrap Customizations" prompt to configure for your project`
274
+ ` ${step}. Run the ${c.cyan('"Bootstrap Customizations"')} prompt to configure for your project`
218
275
  )
219
- console.log(` ${envVars.length > 0 ? '3' : '2'}. Commit the customizations/ folder to your repository`)
276
+ step++
277
+ console.log(` ${step}. Commit the customizations/ folder to your repository`)
220
278
  console.log()
221
279
 
222
280
  closePrompts()
@@ -35,10 +35,11 @@ export async function writeManifest(
35
35
  /**
36
36
  * Create a fresh manifest object.
37
37
  */
38
- export function createManifest(version: string, ide: string): Manifest {
38
+ export function createManifest(version: string, ide: string, ides?: string[]): Manifest {
39
39
  return {
40
40
  version,
41
41
  ide,
42
+ ides: ides ?? [ide],
42
43
  installedAt: new Date().toISOString(),
43
44
  updatedAt: new Date().toISOString(),
44
45
  };
package/src/cli/mcp.ts CHANGED
@@ -1,92 +1,171 @@
1
1
  import { resolve, dirname } from 'node:path';
2
2
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
3
  import { existsSync } from 'node:fs';
4
- import { getOrchestratorRoot } from './copy.js';
5
4
  import { getIncludedMcpServers } from './stack-config.js';
6
- import type { ScaffoldResult, StackConfig, RepoInfo } from './types.js';
5
+ import { PLUGINS } from '../orchestrator/plugins/index.js';
6
+ import type { McpInput } from '../orchestrator/plugins/types.js';
7
+ import type { ScaffoldResult, StackConfig, RepoInfo, IdeChoice } from './types.js';
8
+
9
+ // ── IDE-specific MCP format transformation ────────────────────
10
+
11
+ interface VsCodeServer {
12
+ type: 'stdio' | 'http';
13
+ command?: string;
14
+ args?: string[];
15
+ url?: string;
16
+ env?: Record<string, string>;
17
+ envFile?: string;
18
+ }
19
+
20
+ /**
21
+ * Transform a VS Code–format MCP config into the format
22
+ * expected by the given IDE.
23
+ */
24
+ function transformMcpForIde(
25
+ ide: IdeChoice,
26
+ servers: Record<string, VsCodeServer>,
27
+ inputs?: McpInput[]
28
+ ): Record<string, unknown> {
29
+ switch (ide) {
30
+ case 'cursor':
31
+ case 'claude-code': {
32
+ // mcpServers format — no 'type' field
33
+ const mcpServers: Record<string, unknown> = {};
34
+ for (const [name, server] of Object.entries(servers)) {
35
+ if (server.type === 'stdio') {
36
+ mcpServers[name] = {
37
+ command: server.command,
38
+ args: server.args,
39
+ ...(server.env && { env: server.env }),
40
+ };
41
+ } else if (server.type === 'http') {
42
+ // Strip VS Code ${input:...} placeholders for non-VS Code IDEs
43
+ let url = server.url ?? '';
44
+ url = url.replace(/\$\{input:\w+\}/g, 'REPLACE_ME');
45
+ mcpServers[name] = { url };
46
+ }
47
+ }
48
+ return { mcpServers };
49
+ }
50
+
51
+ case 'opencode': {
52
+ // OpenCode format — type: "local"/"remote", command as array
53
+ const mcp: Record<string, unknown> = {};
54
+ for (const [name, server] of Object.entries(servers)) {
55
+ if (server.type === 'stdio') {
56
+ mcp[name] = {
57
+ type: 'local',
58
+ command: [server.command, ...(server.args ?? [])],
59
+ ...(server.env && { environment: server.env }),
60
+ };
61
+ } else if (server.type === 'http') {
62
+ let url = server.url ?? '';
63
+ url = url.replace(/\$\{input:\w+\}/g, 'REPLACE_ME');
64
+ mcp[name] = {
65
+ type: 'remote',
66
+ url,
67
+ };
68
+ }
69
+ }
70
+ return { mcp };
71
+ }
72
+
73
+ default: {
74
+ // VS Code — return as-is (keep type, inputs, envFile)
75
+ const result: Record<string, unknown> = { servers };
76
+ if (inputs && inputs.length > 0) {
77
+ result.inputs = inputs;
78
+ }
79
+ return result;
80
+ }
81
+ }
82
+ }
7
83
 
8
84
  /**
9
85
  * Scaffold or merge the MCP server config into the target project.
10
86
  *
11
- * Reads the template from `opencastle/src/orchestrator/mcp.json`,
12
- * writes it to `<projectRoot>/<destRelPath>` (e.g. `.vscode/mcp.json`).
87
+ * Builds the server list from plugin configs based on the user's
88
+ * stack selection. Writes to `<projectRoot>/<destRelPath>`
89
+ * (e.g. `.vscode/mcp.json`).
13
90
  *
14
- * When a StackConfig is provided, only servers relevant to the chosen
15
- * CMS/DB stack (plus core servers) are included.
91
+ * The output format is adapted to match the target IDE's expectations.
16
92
  *
17
93
  * If the file already exists, missing servers are merged in without
18
94
  * overwriting any existing server configs.
19
95
  */
20
96
  export async function scaffoldMcpConfig(
21
- pkgRoot: string,
22
97
  projectRoot: string,
23
98
  destRelPath: string,
24
99
  stack?: StackConfig,
25
- repoInfo?: RepoInfo
100
+ repoInfo?: RepoInfo,
101
+ ide?: IdeChoice
26
102
  ): Promise<ScaffoldResult> {
27
103
  const destPath = resolve(projectRoot, destRelPath);
28
104
 
29
- const srcRoot = getOrchestratorRoot(pkgRoot);
30
- const templatePath = resolve(srcRoot, 'mcp.json');
31
- const content = await readFile(templatePath, 'utf8');
32
-
33
- const template = JSON.parse(content) as {
34
- servers: Record<string, unknown>;
35
- inputs?: Array<{ id: string; [key: string]: unknown }>;
36
- };
105
+ // Build server list from plugin configs
106
+ const servers: Record<string, VsCodeServer> = {};
107
+ let inputs: McpInput[] = [];
37
108
 
38
- // Filter servers based on stack config
39
109
  if (stack) {
40
110
  const included = getIncludedMcpServers(stack, repoInfo);
41
- template.servers = Object.fromEntries(
42
- Object.entries(template.servers).filter(([key]) => included.has(key))
43
- );
44
-
45
- // Filter inputs to only include those referenced by included servers
46
- if (template.inputs) {
47
- const serverJson = JSON.stringify(template.servers);
48
- template.inputs = template.inputs.filter(
49
- (input) => serverJson.includes(`\${input:${input.id}}`)
50
- );
51
- if (template.inputs.length === 0) {
52
- delete template.inputs;
111
+
112
+ for (const plugin of Object.values(PLUGINS)) {
113
+ if (plugin.mcpServerKey && included.has(plugin.mcpServerKey)) {
114
+ servers[plugin.mcpServerKey] = plugin.mcpConfig as VsCodeServer;
115
+ if (plugin.mcpInputs) {
116
+ inputs.push(...plugin.mcpInputs);
117
+ }
53
118
  }
54
119
  }
55
120
  }
56
121
 
122
+ // Transform to IDE-specific format
123
+ const resolvedIde = ide ?? 'vscode';
124
+ const output = transformMcpForIde(resolvedIde, servers, inputs.length > 0 ? inputs : undefined);
125
+
57
126
  if (existsSync(destPath)) {
58
127
  // Merge: add missing servers without overwriting existing ones
59
128
  const existingContent = await readFile(destPath, 'utf8');
60
- const existing = JSON.parse(existingContent) as {
61
- servers?: Record<string, unknown>;
62
- inputs?: Array<{ id: string; [key: string]: unknown }>;
63
- [key: string]: unknown;
64
- };
65
-
66
- if (!existing.servers) {
67
- existing.servers = {};
129
+ const existing = JSON.parse(existingContent) as Record<string, unknown>;
130
+
131
+ // Determine the server container key for this IDE
132
+ const containerKey = resolvedIde === 'opencode'
133
+ ? 'mcp'
134
+ : resolvedIde === 'vscode'
135
+ ? 'servers'
136
+ : 'mcpServers';
137
+
138
+ if (!existing[containerKey]) {
139
+ existing[containerKey] = {};
68
140
  }
69
141
 
142
+ const existingServers = existing[containerKey] as Record<string, unknown>;
143
+ const newServers = (output as Record<string, unknown>)[containerKey] as Record<string, unknown> | undefined;
144
+
70
145
  let added = 0;
71
- for (const [key, value] of Object.entries(template.servers)) {
72
- if (!(key in existing.servers)) {
73
- existing.servers[key] = value;
74
- added++;
146
+ if (newServers) {
147
+ for (const [key, value] of Object.entries(newServers)) {
148
+ if (!(key in existingServers)) {
149
+ existingServers[key] = value;
150
+ added++;
151
+ }
75
152
  }
76
153
  }
77
154
 
78
- // Merge inputs: add missing input definitions
79
- if (template.inputs && template.inputs.length > 0) {
80
- if (!existing.inputs) {
81
- existing.inputs = [];
82
- }
83
- const existingIds = new Set(existing.inputs.map((i) => i.id));
84
- for (const input of template.inputs) {
155
+ // For VS Code: merge inputs
156
+ if (resolvedIde === 'vscode' && output.inputs) {
157
+ const existingInputs = (existing.inputs as McpInput[]) ?? [];
158
+ const existingIds = new Set(existingInputs.map((i) => i.id));
159
+ const newInputs = output.inputs as McpInput[];
160
+ for (const input of newInputs) {
85
161
  if (!existingIds.has(input.id)) {
86
- existing.inputs.push(input);
162
+ existingInputs.push(input);
87
163
  added++;
88
164
  }
89
165
  }
166
+ if (existingInputs.length > 0) {
167
+ existing.inputs = existingInputs;
168
+ }
90
169
  }
91
170
 
92
171
  if (added === 0) {
@@ -98,7 +177,7 @@ export async function scaffoldMcpConfig(
98
177
  }
99
178
 
100
179
  await mkdir(dirname(destPath), { recursive: true });
101
- await writeFile(destPath, JSON.stringify(template, null, 2) + '\n');
180
+ await writeFile(destPath, JSON.stringify(output, null, 2) + '\n');
102
181
 
103
182
  return { path: destPath, action: 'created' };
104
183
  }