opencastle 0.5.1 → 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.
- package/README.md +5 -4
- package/dist/cli/adapters/claude-code.d.ts +2 -2
- package/dist/cli/adapters/claude-code.d.ts.map +1 -1
- package/dist/cli/adapters/claude-code.js +31 -4
- package/dist/cli/adapters/claude-code.js.map +1 -1
- package/dist/cli/adapters/cursor.d.ts +2 -2
- package/dist/cli/adapters/cursor.d.ts.map +1 -1
- package/dist/cli/adapters/cursor.js +28 -4
- package/dist/cli/adapters/cursor.js.map +1 -1
- package/dist/cli/adapters/opencode.d.ts +20 -0
- package/dist/cli/adapters/opencode.d.ts.map +1 -0
- package/dist/cli/adapters/opencode.js +265 -0
- package/dist/cli/adapters/opencode.js.map +1 -0
- package/dist/cli/adapters/vscode.d.ts +2 -2
- package/dist/cli/adapters/vscode.d.ts.map +1 -1
- package/dist/cli/adapters/vscode.js +38 -7
- package/dist/cli/adapters/vscode.js.map +1 -1
- package/dist/cli/copy.d.ts +12 -0
- package/dist/cli/copy.d.ts.map +1 -1
- package/dist/cli/copy.js +27 -0
- package/dist/cli/copy.js.map +1 -1
- package/dist/cli/detect.d.ts +18 -0
- package/dist/cli/detect.d.ts.map +1 -0
- package/dist/cli/detect.js +434 -0
- package/dist/cli/detect.js.map +1 -0
- package/dist/cli/gitignore.d.ts.map +1 -1
- package/dist/cli/gitignore.js +0 -2
- package/dist/cli/gitignore.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +154 -91
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/manifest.d.ts +1 -1
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +2 -1
- package/dist/cli/manifest.js.map +1 -1
- package/dist/cli/mcp.d.ts +6 -6
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +105 -34
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/prompt.d.ts +22 -0
- package/dist/cli/prompt.d.ts.map +1 -1
- package/dist/cli/prompt.js +239 -0
- package/dist/cli/prompt.js.map +1 -1
- package/dist/cli/stack-config.d.ts +26 -3
- package/dist/cli/stack-config.d.ts.map +1 -1
- package/dist/cli/stack-config.js +140 -125
- package/dist/cli/stack-config.js.map +1 -1
- package/dist/cli/types.d.ts +46 -10
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js +26 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +66 -19
- package/dist/cli/update.js.map +1 -1
- package/dist/orchestrator/plugins/chrome-devtools/config.d.ts +3 -0
- package/dist/orchestrator/plugins/chrome-devtools/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/chrome-devtools/config.js +28 -0
- package/dist/orchestrator/plugins/chrome-devtools/config.js.map +1 -0
- package/dist/orchestrator/plugins/contentful/config.d.ts +3 -0
- package/dist/orchestrator/plugins/contentful/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/contentful/config.js +48 -0
- package/dist/orchestrator/plugins/contentful/config.js.map +1 -0
- package/dist/orchestrator/plugins/convex/config.d.ts +3 -0
- package/dist/orchestrator/plugins/convex/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/convex/config.js +32 -0
- package/dist/orchestrator/plugins/convex/config.js.map +1 -0
- package/dist/orchestrator/plugins/index.d.ts +28 -0
- package/dist/orchestrator/plugins/index.d.ts.map +1 -0
- package/dist/orchestrator/plugins/index.js +63 -0
- package/dist/orchestrator/plugins/index.js.map +1 -0
- package/dist/orchestrator/plugins/jira/config.d.ts +3 -0
- package/dist/orchestrator/plugins/jira/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/jira/config.js +29 -0
- package/dist/orchestrator/plugins/jira/config.js.map +1 -0
- package/dist/orchestrator/plugins/linear/config.d.ts +3 -0
- package/dist/orchestrator/plugins/linear/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/linear/config.js +33 -0
- package/dist/orchestrator/plugins/linear/config.js.map +1 -0
- package/dist/orchestrator/plugins/nx/config.d.ts +3 -0
- package/dist/orchestrator/plugins/nx/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/nx/config.js +28 -0
- package/dist/orchestrator/plugins/nx/config.js.map +1 -0
- package/dist/orchestrator/plugins/sanity/config.d.ts +3 -0
- package/dist/orchestrator/plugins/sanity/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/sanity/config.js +43 -0
- package/dist/orchestrator/plugins/sanity/config.js.map +1 -0
- package/dist/orchestrator/plugins/slack/config.d.ts +3 -0
- package/dist/orchestrator/plugins/slack/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/slack/config.js +34 -0
- package/dist/orchestrator/plugins/slack/config.js.map +1 -0
- package/dist/orchestrator/plugins/strapi/config.d.ts +3 -0
- package/dist/orchestrator/plugins/strapi/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/strapi/config.js +40 -0
- package/dist/orchestrator/plugins/strapi/config.js.map +1 -0
- package/dist/orchestrator/plugins/supabase/config.d.ts +3 -0
- package/dist/orchestrator/plugins/supabase/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/supabase/config.js +33 -0
- package/dist/orchestrator/plugins/supabase/config.js.map +1 -0
- package/dist/orchestrator/plugins/teams/config.d.ts +3 -0
- package/dist/orchestrator/plugins/teams/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/teams/config.js +43 -0
- package/dist/orchestrator/plugins/teams/config.js.map +1 -0
- package/dist/orchestrator/plugins/types.d.ts +61 -0
- package/dist/orchestrator/plugins/types.d.ts.map +1 -0
- package/dist/orchestrator/plugins/types.js +2 -0
- package/dist/orchestrator/plugins/types.js.map +1 -0
- package/dist/orchestrator/plugins/vercel/config.d.ts +3 -0
- package/dist/orchestrator/plugins/vercel/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/vercel/config.js +32 -0
- package/dist/orchestrator/plugins/vercel/config.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/adapters/claude-code.ts +40 -6
- package/src/cli/adapters/cursor.ts +46 -6
- package/src/cli/adapters/opencode.ts +320 -0
- package/src/cli/adapters/vscode.ts +43 -9
- package/src/cli/copy.ts +32 -0
- package/src/cli/detect.ts +483 -0
- package/src/cli/gitignore.ts +0 -3
- package/src/cli/init.ts +169 -96
- package/src/cli/manifest.ts +2 -1
- package/src/cli/mcp.ts +131 -51
- package/src/cli/prompt.ts +299 -0
- package/src/cli/stack-config.ts +187 -145
- package/src/cli/types.ts +60 -9
- package/src/cli/update.ts +78 -20
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/orchestrator/agent-workflows/README.md +1 -1
- package/src/orchestrator/agent-workflows/bug-fix.md +12 -12
- package/src/orchestrator/agent-workflows/data-pipeline.md +21 -20
- package/src/orchestrator/agent-workflows/database-migration.md +11 -11
- package/src/orchestrator/agent-workflows/feature-implementation.md +10 -10
- package/src/orchestrator/agent-workflows/performance-optimization.md +6 -6
- package/src/orchestrator/agent-workflows/refactoring.md +10 -10
- package/src/orchestrator/agent-workflows/schema-changes.md +8 -8
- package/src/orchestrator/agent-workflows/security-audit.md +12 -12
- package/src/orchestrator/agent-workflows/shared-delivery-phase.md +5 -5
- package/src/orchestrator/agents/api-designer.agent.md +2 -2
- package/src/orchestrator/agents/architect.agent.md +2 -2
- package/src/orchestrator/agents/content-engineer.agent.md +4 -4
- package/src/orchestrator/agents/copywriter.agent.md +2 -2
- package/src/orchestrator/agents/data-expert.agent.md +6 -6
- package/src/orchestrator/agents/database-engineer.agent.md +4 -4
- package/src/orchestrator/agents/developer.agent.md +5 -5
- package/src/orchestrator/agents/devops-expert.agent.md +5 -5
- package/src/orchestrator/agents/documentation-writer.agent.md +1 -1
- package/src/orchestrator/agents/performance-expert.agent.md +3 -3
- package/src/orchestrator/agents/release-manager.agent.md +4 -4
- package/src/orchestrator/agents/researcher.agent.md +19 -3
- package/src/orchestrator/agents/reviewer.agent.md +2 -4
- package/src/orchestrator/agents/security-expert.agent.md +4 -4
- package/src/orchestrator/agents/seo-specialist.agent.md +2 -2
- package/src/orchestrator/agents/team-lead.agent.md +97 -101
- package/src/orchestrator/agents/testing-expert.agent.md +5 -5
- package/src/orchestrator/agents/ui-ux-expert.agent.md +7 -7
- package/src/orchestrator/copilot-instructions.md +1 -1
- package/src/orchestrator/customizations/AGENT-FAILURES.md +1 -1
- package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +12 -12
- package/src/orchestrator/customizations/DISPUTES.md +5 -5
- package/src/orchestrator/customizations/KNOWN-ISSUES.md +30 -0
- package/src/orchestrator/customizations/LESSONS-LEARNED.md +7 -7
- package/src/orchestrator/customizations/README.md +5 -2
- package/src/orchestrator/customizations/agents/agent-registry.md +1 -1
- package/src/orchestrator/customizations/agents/skill-matrix.md +12 -7
- package/src/orchestrator/customizations/logs/README.md +1 -1
- package/src/orchestrator/customizations/project/decisions.md +31 -0
- package/src/orchestrator/customizations/project/docs-structure.md +16 -5
- package/src/orchestrator/customizations/project/roadmap.md +24 -0
- package/src/orchestrator/customizations/project/tracker-config.md +1 -1
- package/src/orchestrator/customizations/stack/cms-config.md +1 -1
- package/src/orchestrator/customizations/stack/notifications-config.md +1 -1
- package/src/orchestrator/instructions/ai-optimization.instructions.md +2 -2
- package/src/orchestrator/instructions/general.instructions.md +102 -40
- package/src/orchestrator/{skills/browser-testing → plugins/chrome-devtools}/SKILL.md +1 -1
- package/src/orchestrator/plugins/chrome-devtools/config.ts +29 -0
- package/src/orchestrator/{skills/contentful-cms → plugins/contentful}/SKILL.md +1 -1
- package/src/orchestrator/plugins/contentful/config.ts +49 -0
- package/src/orchestrator/{skills/convex-database → plugins/convex}/SKILL.md +1 -1
- package/src/orchestrator/plugins/convex/config.ts +33 -0
- package/src/orchestrator/plugins/index.ts +85 -0
- package/src/orchestrator/{skills/jira-management → plugins/jira}/SKILL.md +3 -3
- package/src/orchestrator/plugins/jira/config.ts +30 -0
- package/src/orchestrator/{skills/task-management → plugins/linear}/SKILL.md +3 -3
- package/src/orchestrator/plugins/linear/config.ts +34 -0
- package/src/orchestrator/{skills/nx-workspace → plugins/nx}/SKILL.md +1 -1
- package/src/orchestrator/plugins/nx/config.ts +29 -0
- package/src/orchestrator/{skills/sanity-cms → plugins/sanity}/SKILL.md +1 -1
- package/src/orchestrator/plugins/sanity/config.ts +44 -0
- package/src/orchestrator/{skills/slack-notifications → plugins/slack}/SKILL.md +2 -2
- package/src/orchestrator/plugins/slack/config.ts +35 -0
- package/src/orchestrator/{skills/strapi-cms → plugins/strapi}/SKILL.md +1 -1
- package/src/orchestrator/plugins/strapi/config.ts +41 -0
- package/src/orchestrator/{skills/supabase-database → plugins/supabase}/SKILL.md +1 -1
- package/src/orchestrator/plugins/supabase/config.ts +34 -0
- package/src/orchestrator/{skills/teams-notifications → plugins/teams}/SKILL.md +2 -2
- package/src/orchestrator/plugins/teams/config.ts +44 -0
- package/src/orchestrator/plugins/types.ts +79 -0
- package/src/orchestrator/plugins/vercel/config.ts +33 -0
- package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +59 -12
- package/src/orchestrator/prompts/brainstorm.prompt.md +3 -3
- package/src/orchestrator/prompts/bug-fix.prompt.md +18 -18
- package/src/orchestrator/prompts/create-skill.prompt.md +50 -32
- package/src/orchestrator/prompts/generate-task-spec.prompt.md +3 -3
- package/src/orchestrator/prompts/implement-feature.prompt.md +26 -26
- package/src/orchestrator/prompts/metrics-report.prompt.md +11 -11
- package/src/orchestrator/prompts/quick-refinement.prompt.md +16 -16
- package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +2 -2
- package/src/orchestrator/skills/accessibility-standards/SKILL.md +1 -1
- package/src/orchestrator/skills/agent-hooks/SKILL.md +27 -18
- package/src/orchestrator/skills/agent-memory/SKILL.md +7 -7
- package/src/orchestrator/skills/api-patterns/SKILL.md +6 -6
- package/src/orchestrator/skills/code-commenting/SKILL.md +1 -1
- package/src/orchestrator/skills/context-map/SKILL.md +4 -4
- package/src/orchestrator/skills/data-engineering/SKILL.md +7 -4
- package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +2 -2
- package/src/orchestrator/skills/documentation-standards/SKILL.md +1 -1
- package/src/orchestrator/skills/fast-review/SKILL.md +3 -3
- package/src/orchestrator/skills/frontend-design/SKILL.md +1 -1
- package/src/orchestrator/skills/memory-merger/SKILL.md +8 -8
- package/src/orchestrator/skills/nextjs-patterns/SKILL.md +1 -1
- package/src/orchestrator/skills/panel-majority-vote/SKILL.md +2 -2
- package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +1 -1
- package/src/orchestrator/skills/performance-optimization/SKILL.md +1 -1
- package/src/orchestrator/skills/react-development/SKILL.md +3 -3
- package/src/orchestrator/skills/security-hardening/SKILL.md +27 -27
- package/src/orchestrator/skills/self-improvement/SKILL.md +14 -13
- package/src/orchestrator/skills/seo-patterns/SKILL.md +1 -1
- package/src/orchestrator/skills/session-checkpoints/SKILL.md +19 -19
- package/src/orchestrator/skills/team-lead-reference/SKILL.md +9 -9
- package/src/orchestrator/skills/testing-workflow/SKILL.md +13 -13
- package/src/orchestrator/skills/validation-gates/SKILL.md +8 -15
- package/src/orchestrator/mcp.json +0 -61
package/src/cli/init.ts
CHANGED
|
@@ -1,18 +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 {
|
|
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
|
|
9
|
+
import { TECH_PLUGINS, TEAM_PLUGINS } from '../orchestrator/plugins/index.js'
|
|
10
|
+
import { detectRepoInfo, mergeStackIntoRepoInfo, formatRepoInfo } from './detect.js'
|
|
11
|
+
import type { CliContext, IdeAdapter, IdeChoice, TechTool, TeamTool, StackConfig } from './types.js'
|
|
10
12
|
|
|
11
13
|
const ADAPTERS: Record<string, () => Promise<IdeAdapter>> = {
|
|
12
14
|
vscode: () => import('./adapters/vscode.js') as Promise<IdeAdapter>,
|
|
13
15
|
cursor: () => import('./adapters/cursor.js') as Promise<IdeAdapter>,
|
|
14
16
|
'claude-code': () =>
|
|
15
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',
|
|
16
28
|
}
|
|
17
29
|
|
|
18
30
|
export default async function init({ pkgRoot, args }: CliContext): Promise<void> {
|
|
@@ -24,7 +36,7 @@ export default async function init({ pkgRoot, args }: CliContext): Promise<void>
|
|
|
24
36
|
let isReinit = false
|
|
25
37
|
if (existing) {
|
|
26
38
|
const proceed = await confirm(
|
|
27
|
-
`OpenCastle already installed (v${existing.version}
|
|
39
|
+
`OpenCastle already installed (v${existing.version}). Re-initialize?`,
|
|
28
40
|
false
|
|
29
41
|
)
|
|
30
42
|
if (!proceed) {
|
|
@@ -38,78 +50,117 @@ export default async function init({ pkgRoot, args }: CliContext): Promise<void>
|
|
|
38
50
|
await readFile(resolve(pkgRoot, 'package.json'), 'utf8')
|
|
39
51
|
) as { version: string }
|
|
40
52
|
|
|
41
|
-
console.log(`\n 🏰 OpenCastle v${pkg.version}`)
|
|
53
|
+
console.log(`\n 🏰 ${c.bold('OpenCastle')} ${c.dim(`v${pkg.version}`)}`)
|
|
42
54
|
console.log(
|
|
43
|
-
'
|
|
55
|
+
` ${c.dim('Multi-agent orchestration framework for AI coding assistants')}\n`
|
|
44
56
|
)
|
|
45
57
|
|
|
46
|
-
// ──
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
{
|
|
54
|
-
|
|
55
|
-
hint: '.cursorrules & .cursor/rules/*.mdc',
|
|
56
|
-
value: 'cursor',
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
label: 'Claude Code',
|
|
60
|
-
hint: 'CLAUDE.md & .claude/ commands, skills',
|
|
61
|
-
value: 'claude-code',
|
|
62
|
-
},
|
|
63
|
-
])
|
|
58
|
+
// ── Repo research ───────────────────────────────────────────────
|
|
59
|
+
console.log(` ${c.dim('Scanning repository...')}`)
|
|
60
|
+
const repoInfo = await detectRepoInfo(projectRoot)
|
|
61
|
+
const summary = formatRepoInfo(repoInfo)
|
|
62
|
+
if (summary) {
|
|
63
|
+
console.log(` ${c.green('Detected:')}\n` + summary + '\n')
|
|
64
|
+
} else {
|
|
65
|
+
console.log(` ${c.dim('No tooling detected (empty project?)')}\n`)
|
|
66
|
+
}
|
|
64
67
|
|
|
65
|
-
// ──
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
}
|
|
72
98
|
|
|
73
|
-
// ──
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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] : []),
|
|
78
106
|
])
|
|
79
107
|
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
)
|
|
86
117
|
|
|
87
|
-
// ──
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
+
)
|
|
93
128
|
|
|
94
|
-
const stack: StackConfig = {
|
|
129
|
+
const stack: StackConfig = {
|
|
130
|
+
ides: ides as IdeChoice[],
|
|
131
|
+
techTools: techTools as TechTool[],
|
|
132
|
+
teamTools: teamTools as TeamTool[],
|
|
133
|
+
}
|
|
95
134
|
|
|
96
|
-
|
|
97
|
-
|
|
135
|
+
// ── Merge user choices into detected info ────────────────────
|
|
136
|
+
const combinedRepoInfo = mergeStackIntoRepoInfo(repoInfo, stack)
|
|
137
|
+
|
|
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()
|
|
98
147
|
|
|
99
148
|
// ── Dry run ─────────────────────────────────────────────────────
|
|
100
149
|
if (dryRun) {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
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
|
+
}
|
|
109
160
|
}
|
|
110
|
-
console.log(` + .opencastle.json`)
|
|
111
|
-
console.log(` + .gitignore (OpenCastle entries)`)
|
|
112
|
-
console.log(
|
|
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`)
|
|
113
164
|
closePrompts()
|
|
114
165
|
return
|
|
115
166
|
}
|
|
@@ -125,11 +176,12 @@ export default async function init({ pkgRoot, args }: CliContext): Promise<void>
|
|
|
125
176
|
await unlink(fullPath)
|
|
126
177
|
}
|
|
127
178
|
}
|
|
128
|
-
// Remove MCP
|
|
179
|
+
// Remove MCP configs so they get regenerated with new stack
|
|
129
180
|
const mcpCandidates = [
|
|
130
181
|
'.vscode/mcp.json',
|
|
131
182
|
'.cursor/mcp.json',
|
|
132
183
|
'.claude/mcp.json',
|
|
184
|
+
'opencode.json',
|
|
133
185
|
]
|
|
134
186
|
for (const mcpPath of mcpCandidates) {
|
|
135
187
|
const fullPath = resolve(projectRoot, mcpPath)
|
|
@@ -139,69 +191,90 @@ export default async function init({ pkgRoot, args }: CliContext): Promise<void>
|
|
|
139
191
|
}
|
|
140
192
|
}
|
|
141
193
|
|
|
142
|
-
// ── Run
|
|
143
|
-
|
|
144
|
-
|
|
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
|
+
}
|
|
145
209
|
|
|
146
210
|
// ── Write manifest ──────────────────────────────────────────────
|
|
147
|
-
const manifest = createManifest(pkg.version,
|
|
148
|
-
manifest.managedPaths =
|
|
211
|
+
const manifest = createManifest(pkg.version, ides[0], ides)
|
|
212
|
+
manifest.managedPaths = allManagedPaths
|
|
149
213
|
manifest.stack = stack
|
|
214
|
+
manifest.repoInfo = combinedRepoInfo
|
|
150
215
|
await writeManifest(projectRoot, manifest)
|
|
151
216
|
|
|
152
217
|
// ── Update .gitignore ───────────────────────────────────────────
|
|
153
|
-
const
|
|
154
|
-
const gitignoreResult = await updateGitignore(projectRoot, managedPaths)
|
|
218
|
+
const gitignoreResult = await updateGitignore(projectRoot, allManagedPaths)
|
|
155
219
|
|
|
156
220
|
// ── Summary ─────────────────────────────────────────────────────
|
|
157
|
-
|
|
158
|
-
const skipped = results.skipped.length
|
|
159
|
-
|
|
160
|
-
console.log(` ✓ Created ${created} files`)
|
|
221
|
+
console.log(` ${c.green('✓')} Created ${c.bold(String(totalCreated))} files`)
|
|
161
222
|
if (gitignoreResult === 'created') {
|
|
162
|
-
console.log('
|
|
223
|
+
console.log(` ${c.green('✓')} Created .gitignore with OpenCastle entries`)
|
|
163
224
|
} else if (gitignoreResult === 'updated') {
|
|
164
|
-
console.log('
|
|
225
|
+
console.log(` ${c.green('✓')} Updated .gitignore with OpenCastle entries`)
|
|
165
226
|
}
|
|
166
|
-
if (
|
|
167
|
-
console.log(` → Skipped ${
|
|
227
|
+
if (totalSkipped > 0) {
|
|
228
|
+
console.log(` ${c.dim('→')} Skipped ${totalSkipped} existing files`)
|
|
168
229
|
}
|
|
169
230
|
|
|
170
231
|
// ── Env var notice ──────────────────────────────────────────────
|
|
171
|
-
const envVars = getRequiredMcpEnvVars(stack)
|
|
232
|
+
const envVars = getRequiredMcpEnvVars(stack, combinedRepoInfo)
|
|
172
233
|
if (envVars.length > 0) {
|
|
173
|
-
console.log(`\n ⚠ Required environment variables for MCP servers:\n`)
|
|
234
|
+
console.log(`\n ${c.yellow('⚠')} Required environment variables for MCP servers:\n`)
|
|
174
235
|
for (const { envVar, hint } of envVars) {
|
|
175
|
-
console.log(` ${envVar}`)
|
|
176
|
-
console.log(` └ ${hint}\n`)
|
|
236
|
+
console.log(` ${c.bold(envVar)}`)
|
|
237
|
+
console.log(` ${c.dim('└')} ${c.dim(hint)}\n`)
|
|
177
238
|
}
|
|
178
239
|
}
|
|
179
240
|
|
|
180
241
|
// ── OAuth setup guides ────────────────────────────────────────
|
|
181
|
-
if (
|
|
182
|
-
console.log(` 📖 Slack MCP requires a Slack App with a bot token.`)
|
|
183
|
-
console.log(` Setup guide: https://www.opencastle.dev/guides/slack
|
|
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`)
|
|
184
245
|
}
|
|
185
246
|
|
|
186
|
-
console.log(`\n Next steps
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
+
}
|
|
195
264
|
}
|
|
265
|
+
|
|
196
266
|
if (envVars.length > 0) {
|
|
267
|
+
step++
|
|
197
268
|
console.log(
|
|
198
|
-
`
|
|
269
|
+
` ${step}. Set the environment variable${envVars.length > 1 ? 's' : ''} listed above`
|
|
199
270
|
)
|
|
200
271
|
}
|
|
272
|
+
step++
|
|
201
273
|
console.log(
|
|
202
|
-
` ${
|
|
274
|
+
` ${step}. Run the ${c.cyan('"Bootstrap Customizations"')} prompt to configure for your project`
|
|
203
275
|
)
|
|
204
|
-
|
|
276
|
+
step++
|
|
277
|
+
console.log(` ${step}. Commit the customizations/ folder to your repository`)
|
|
205
278
|
console.log()
|
|
206
279
|
|
|
207
280
|
closePrompts()
|
package/src/cli/manifest.ts
CHANGED
|
@@ -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,91 +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
|
|
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
|
-
*
|
|
12
|
-
*
|
|
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
|
-
*
|
|
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
|
-
stack?: StackConfig
|
|
99
|
+
stack?: StackConfig,
|
|
100
|
+
repoInfo?: RepoInfo,
|
|
101
|
+
ide?: IdeChoice
|
|
25
102
|
): Promise<ScaffoldResult> {
|
|
26
103
|
const destPath = resolve(projectRoot, destRelPath);
|
|
27
104
|
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const template = JSON.parse(content) as {
|
|
33
|
-
servers: Record<string, unknown>;
|
|
34
|
-
inputs?: Array<{ id: string; [key: string]: unknown }>;
|
|
35
|
-
};
|
|
105
|
+
// Build server list from plugin configs
|
|
106
|
+
const servers: Record<string, VsCodeServer> = {};
|
|
107
|
+
let inputs: McpInput[] = [];
|
|
36
108
|
|
|
37
|
-
// Filter servers based on stack config
|
|
38
109
|
if (stack) {
|
|
39
|
-
const included = getIncludedMcpServers(stack);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
template.inputs = template.inputs.filter(
|
|
48
|
-
(input) => serverJson.includes(`\${input:${input.id}}`)
|
|
49
|
-
);
|
|
50
|
-
if (template.inputs.length === 0) {
|
|
51
|
-
delete template.inputs;
|
|
110
|
+
const included = getIncludedMcpServers(stack, repoInfo);
|
|
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
|
+
}
|
|
52
118
|
}
|
|
53
119
|
}
|
|
54
120
|
}
|
|
55
121
|
|
|
122
|
+
// Transform to IDE-specific format
|
|
123
|
+
const resolvedIde = ide ?? 'vscode';
|
|
124
|
+
const output = transformMcpForIde(resolvedIde, servers, inputs.length > 0 ? inputs : undefined);
|
|
125
|
+
|
|
56
126
|
if (existsSync(destPath)) {
|
|
57
127
|
// Merge: add missing servers without overwriting existing ones
|
|
58
128
|
const existingContent = await readFile(destPath, 'utf8');
|
|
59
|
-
const existing = JSON.parse(existingContent) as
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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] = {};
|
|
67
140
|
}
|
|
68
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
|
+
|
|
69
145
|
let added = 0;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
146
|
+
if (newServers) {
|
|
147
|
+
for (const [key, value] of Object.entries(newServers)) {
|
|
148
|
+
if (!(key in existingServers)) {
|
|
149
|
+
existingServers[key] = value;
|
|
150
|
+
added++;
|
|
151
|
+
}
|
|
74
152
|
}
|
|
75
153
|
}
|
|
76
154
|
|
|
77
|
-
//
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
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) {
|
|
84
161
|
if (!existingIds.has(input.id)) {
|
|
85
|
-
|
|
162
|
+
existingInputs.push(input);
|
|
86
163
|
added++;
|
|
87
164
|
}
|
|
88
165
|
}
|
|
166
|
+
if (existingInputs.length > 0) {
|
|
167
|
+
existing.inputs = existingInputs;
|
|
168
|
+
}
|
|
89
169
|
}
|
|
90
170
|
|
|
91
171
|
if (added === 0) {
|
|
@@ -97,7 +177,7 @@ export async function scaffoldMcpConfig(
|
|
|
97
177
|
}
|
|
98
178
|
|
|
99
179
|
await mkdir(dirname(destPath), { recursive: true });
|
|
100
|
-
await writeFile(destPath, JSON.stringify(
|
|
180
|
+
await writeFile(destPath, JSON.stringify(output, null, 2) + '\n');
|
|
101
181
|
|
|
102
182
|
return { path: destPath, action: 'created' };
|
|
103
183
|
}
|