opencastle 0.1.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 (224) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +215 -0
  3. package/bin/cli.mjs +69 -0
  4. package/dist/cli/adapters/claude-code.d.ts +22 -0
  5. package/dist/cli/adapters/claude-code.d.ts.map +1 -0
  6. package/dist/cli/adapters/claude-code.js +237 -0
  7. package/dist/cli/adapters/claude-code.js.map +1 -0
  8. package/dist/cli/adapters/cursor.d.ts +20 -0
  9. package/dist/cli/adapters/cursor.d.ts.map +1 -0
  10. package/dist/cli/adapters/cursor.js +231 -0
  11. package/dist/cli/adapters/cursor.js.map +1 -0
  12. package/dist/cli/adapters/vscode.d.ts +20 -0
  13. package/dist/cli/adapters/vscode.d.ts.map +1 -0
  14. package/dist/cli/adapters/vscode.js +132 -0
  15. package/dist/cli/adapters/vscode.js.map +1 -0
  16. package/dist/cli/copy.d.ts +14 -0
  17. package/dist/cli/copy.d.ts.map +1 -0
  18. package/dist/cli/copy.js +62 -0
  19. package/dist/cli/copy.js.map +1 -0
  20. package/dist/cli/dashboard.d.ts +3 -0
  21. package/dist/cli/dashboard.d.ts.map +1 -0
  22. package/dist/cli/dashboard.js +183 -0
  23. package/dist/cli/dashboard.js.map +1 -0
  24. package/dist/cli/diff.d.ts +3 -0
  25. package/dist/cli/diff.d.ts.map +1 -0
  26. package/dist/cli/diff.js +27 -0
  27. package/dist/cli/diff.js.map +1 -0
  28. package/dist/cli/eject.d.ts +3 -0
  29. package/dist/cli/eject.d.ts.map +1 -0
  30. package/dist/cli/eject.js +27 -0
  31. package/dist/cli/eject.js.map +1 -0
  32. package/dist/cli/init.d.ts +3 -0
  33. package/dist/cli/init.d.ts.map +1 -0
  34. package/dist/cli/init.js +92 -0
  35. package/dist/cli/init.js.map +1 -0
  36. package/dist/cli/manifest.d.ts +14 -0
  37. package/dist/cli/manifest.d.ts.map +1 -0
  38. package/dist/cli/manifest.js +34 -0
  39. package/dist/cli/manifest.js.map +1 -0
  40. package/dist/cli/mcp.d.ts +14 -0
  41. package/dist/cli/mcp.d.ts.map +1 -0
  42. package/dist/cli/mcp.js +35 -0
  43. package/dist/cli/mcp.js.map +1 -0
  44. package/dist/cli/prompt.d.ts +12 -0
  45. package/dist/cli/prompt.d.ts.map +1 -0
  46. package/dist/cli/prompt.js +104 -0
  47. package/dist/cli/prompt.js.map +1 -0
  48. package/dist/cli/run/adapters/claude-code.d.ts +16 -0
  49. package/dist/cli/run/adapters/claude-code.d.ts.map +1 -0
  50. package/dist/cli/run/adapters/claude-code.js +82 -0
  51. package/dist/cli/run/adapters/claude-code.js.map +1 -0
  52. package/dist/cli/run/adapters/copilot.d.ts +16 -0
  53. package/dist/cli/run/adapters/copilot.d.ts.map +1 -0
  54. package/dist/cli/run/adapters/copilot.js +84 -0
  55. package/dist/cli/run/adapters/copilot.js.map +1 -0
  56. package/dist/cli/run/adapters/cursor.d.ts +16 -0
  57. package/dist/cli/run/adapters/cursor.d.ts.map +1 -0
  58. package/dist/cli/run/adapters/cursor.js +81 -0
  59. package/dist/cli/run/adapters/cursor.js.map +1 -0
  60. package/dist/cli/run/adapters/index.d.ts +14 -0
  61. package/dist/cli/run/adapters/index.d.ts.map +1 -0
  62. package/dist/cli/run/adapters/index.js +35 -0
  63. package/dist/cli/run/adapters/index.js.map +1 -0
  64. package/dist/cli/run/executor.d.ts +15 -0
  65. package/dist/cli/run/executor.d.ts.map +1 -0
  66. package/dist/cli/run/executor.js +249 -0
  67. package/dist/cli/run/executor.js.map +1 -0
  68. package/dist/cli/run/reporter.d.ts +10 -0
  69. package/dist/cli/run/reporter.d.ts.map +1 -0
  70. package/dist/cli/run/reporter.js +112 -0
  71. package/dist/cli/run/reporter.js.map +1 -0
  72. package/dist/cli/run/schema.d.ts +28 -0
  73. package/dist/cli/run/schema.d.ts.map +1 -0
  74. package/dist/cli/run/schema.js +511 -0
  75. package/dist/cli/run/schema.js.map +1 -0
  76. package/dist/cli/run.d.ts +6 -0
  77. package/dist/cli/run.d.ts.map +1 -0
  78. package/dist/cli/run.js +123 -0
  79. package/dist/cli/run.js.map +1 -0
  80. package/dist/cli/stack-config.d.ts +12 -0
  81. package/dist/cli/stack-config.d.ts.map +1 -0
  82. package/dist/cli/stack-config.js +146 -0
  83. package/dist/cli/stack-config.js.map +1 -0
  84. package/dist/cli/types.d.ts +169 -0
  85. package/dist/cli/types.d.ts.map +1 -0
  86. package/dist/cli/types.js +2 -0
  87. package/dist/cli/types.js.map +1 -0
  88. package/dist/cli/update.d.ts +3 -0
  89. package/dist/cli/update.d.ts.map +1 -0
  90. package/dist/cli/update.js +50 -0
  91. package/dist/cli/update.js.map +1 -0
  92. package/package.json +48 -0
  93. package/src/cli/adapters/claude-code.ts +287 -0
  94. package/src/cli/adapters/cursor.ts +377 -0
  95. package/src/cli/adapters/vscode.ts +168 -0
  96. package/src/cli/copy.ts +79 -0
  97. package/src/cli/dashboard.ts +225 -0
  98. package/src/cli/diff.ts +44 -0
  99. package/src/cli/eject.ts +39 -0
  100. package/src/cli/init.ts +120 -0
  101. package/src/cli/manifest.ts +45 -0
  102. package/src/cli/mcp.ts +49 -0
  103. package/src/cli/prompt.ts +115 -0
  104. package/src/cli/run/adapters/claude-code.ts +95 -0
  105. package/src/cli/run/adapters/copilot.ts +97 -0
  106. package/src/cli/run/adapters/cursor.ts +94 -0
  107. package/src/cli/run/adapters/index.ts +40 -0
  108. package/src/cli/run/executor.ts +292 -0
  109. package/src/cli/run/reporter.ts +129 -0
  110. package/src/cli/run/schema.ts +595 -0
  111. package/src/cli/run.ts +137 -0
  112. package/src/cli/stack-config.ts +180 -0
  113. package/src/cli/types.ts +207 -0
  114. package/src/cli/update.ts +75 -0
  115. package/src/dashboard/astro.config.mjs +6 -0
  116. package/src/dashboard/package-lock.json +5455 -0
  117. package/src/dashboard/package.json +14 -0
  118. package/src/dashboard/public/data/delegations.ndjson +35 -0
  119. package/src/dashboard/public/data/panels.ndjson +13 -0
  120. package/src/dashboard/public/data/sessions.ndjson +50 -0
  121. package/src/dashboard/public/icon-192.png +0 -0
  122. package/src/dashboard/scripts/generate-seed-data.ts +355 -0
  123. package/src/dashboard/src/layouts/Layout.astro +25 -0
  124. package/src/dashboard/src/pages/index.astro +1070 -0
  125. package/src/dashboard/src/styles/dashboard.css +1078 -0
  126. package/src/dashboard/tsconfig.json +6 -0
  127. package/src/orchestrator/agent-workflows/README.md +22 -0
  128. package/src/orchestrator/agent-workflows/bug-fix.md +128 -0
  129. package/src/orchestrator/agent-workflows/data-pipeline.md +145 -0
  130. package/src/orchestrator/agent-workflows/database-migration.md +159 -0
  131. package/src/orchestrator/agent-workflows/feature-implementation.md +223 -0
  132. package/src/orchestrator/agent-workflows/performance-optimization.md +125 -0
  133. package/src/orchestrator/agent-workflows/refactoring.md +142 -0
  134. package/src/orchestrator/agent-workflows/schema-changes.md +164 -0
  135. package/src/orchestrator/agent-workflows/security-audit.md +148 -0
  136. package/src/orchestrator/agent-workflows/shared-delivery-phase.md +33 -0
  137. package/src/orchestrator/agents/api-designer.agent.md +68 -0
  138. package/src/orchestrator/agents/architect.agent.md +129 -0
  139. package/src/orchestrator/agents/content-engineer.agent.md +57 -0
  140. package/src/orchestrator/agents/copywriter.agent.md +95 -0
  141. package/src/orchestrator/agents/data-expert.agent.md +63 -0
  142. package/src/orchestrator/agents/database-engineer.agent.md +62 -0
  143. package/src/orchestrator/agents/developer.agent.md +66 -0
  144. package/src/orchestrator/agents/devops-expert.agent.md +57 -0
  145. package/src/orchestrator/agents/documentation-writer.agent.md +60 -0
  146. package/src/orchestrator/agents/performance-expert.agent.md +58 -0
  147. package/src/orchestrator/agents/release-manager.agent.md +72 -0
  148. package/src/orchestrator/agents/researcher.agent.md +145 -0
  149. package/src/orchestrator/agents/reviewer.agent.md +62 -0
  150. package/src/orchestrator/agents/security-expert.agent.md +64 -0
  151. package/src/orchestrator/agents/seo-specialist.agent.md +67 -0
  152. package/src/orchestrator/agents/team-lead.agent.md +644 -0
  153. package/src/orchestrator/agents/testing-expert.agent.md +85 -0
  154. package/src/orchestrator/agents/ui-ux-expert.agent.md +63 -0
  155. package/src/orchestrator/copilot-instructions.md +3 -0
  156. package/src/orchestrator/customizations/AGENT-EXPERTISE.md +325 -0
  157. package/src/orchestrator/customizations/AGENT-FAILURES.md +69 -0
  158. package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +58 -0
  159. package/src/orchestrator/customizations/DISPUTES.md +162 -0
  160. package/src/orchestrator/customizations/KNOWLEDGE-GRAPH.md +10 -0
  161. package/src/orchestrator/customizations/LESSONS-LEARNED.md +70 -0
  162. package/src/orchestrator/customizations/README.md +59 -0
  163. package/src/orchestrator/customizations/agents/agent-registry.md +46 -0
  164. package/src/orchestrator/customizations/agents/skill-matrix.md +142 -0
  165. package/src/orchestrator/customizations/logs/README.md +181 -0
  166. package/src/orchestrator/customizations/logs/delegations.ndjson +1 -0
  167. package/src/orchestrator/customizations/logs/panels.ndjson +1 -0
  168. package/src/orchestrator/customizations/logs/sessions.ndjson +1 -0
  169. package/src/orchestrator/customizations/project/docs-structure.md +23 -0
  170. package/src/orchestrator/customizations/project/tracker-config.md +45 -0
  171. package/src/orchestrator/customizations/project.instructions.md +64 -0
  172. package/src/orchestrator/customizations/stack/api-config.md +37 -0
  173. package/src/orchestrator/customizations/stack/cms-config.md +26 -0
  174. package/src/orchestrator/customizations/stack/data-pipeline-config.md +41 -0
  175. package/src/orchestrator/customizations/stack/database-config.md +44 -0
  176. package/src/orchestrator/customizations/stack/deployment-config.md +45 -0
  177. package/src/orchestrator/customizations/stack/testing-config.md +56 -0
  178. package/src/orchestrator/instructions/ai-optimization.instructions.md +143 -0
  179. package/src/orchestrator/instructions/general.instructions.md +194 -0
  180. package/src/orchestrator/mcp.json +55 -0
  181. package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +235 -0
  182. package/src/orchestrator/prompts/brainstorm.prompt.md +115 -0
  183. package/src/orchestrator/prompts/bug-fix.prompt.md +141 -0
  184. package/src/orchestrator/prompts/create-skill.prompt.md +103 -0
  185. package/src/orchestrator/prompts/generate-task-spec.prompt.md +154 -0
  186. package/src/orchestrator/prompts/implement-feature.prompt.md +124 -0
  187. package/src/orchestrator/prompts/metrics-report.prompt.md +142 -0
  188. package/src/orchestrator/prompts/quick-refinement.prompt.md +137 -0
  189. package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +100 -0
  190. package/src/orchestrator/skills/accessibility-standards/SKILL.md +164 -0
  191. package/src/orchestrator/skills/agent-hooks/SKILL.md +147 -0
  192. package/src/orchestrator/skills/agent-memory/SKILL.md +144 -0
  193. package/src/orchestrator/skills/api-patterns/SKILL.md +106 -0
  194. package/src/orchestrator/skills/browser-testing/SKILL.md +203 -0
  195. package/src/orchestrator/skills/code-commenting/SKILL.md +133 -0
  196. package/src/orchestrator/skills/contentful-cms/SKILL.md +43 -0
  197. package/src/orchestrator/skills/context-map/SKILL.md +135 -0
  198. package/src/orchestrator/skills/convex-database/SKILL.md +80 -0
  199. package/src/orchestrator/skills/data-engineering/SKILL.md +99 -0
  200. package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +49 -0
  201. package/src/orchestrator/skills/documentation-standards/SKILL.md +85 -0
  202. package/src/orchestrator/skills/fast-review/SKILL.md +327 -0
  203. package/src/orchestrator/skills/frontend-design/SKILL.md +42 -0
  204. package/src/orchestrator/skills/jira-management/SKILL.md +168 -0
  205. package/src/orchestrator/skills/memory-merger/SKILL.md +123 -0
  206. package/src/orchestrator/skills/nextjs-patterns/SKILL.md +75 -0
  207. package/src/orchestrator/skills/nx-workspace/SKILL.md +192 -0
  208. package/src/orchestrator/skills/panel-majority-vote/SKILL.md +184 -0
  209. package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +38 -0
  210. package/src/orchestrator/skills/performance-optimization/SKILL.md +101 -0
  211. package/src/orchestrator/skills/react-development/SKILL.md +117 -0
  212. package/src/orchestrator/skills/sanity-cms/SKILL.md +18 -0
  213. package/src/orchestrator/skills/security-hardening/SKILL.md +118 -0
  214. package/src/orchestrator/skills/self-improvement/SKILL.md +137 -0
  215. package/src/orchestrator/skills/seo-patterns/SKILL.md +40 -0
  216. package/src/orchestrator/skills/session-checkpoints/SKILL.md +205 -0
  217. package/src/orchestrator/skills/slack-notifications/SKILL.md +211 -0
  218. package/src/orchestrator/skills/strapi-cms/SKILL.md +43 -0
  219. package/src/orchestrator/skills/supabase-database/SKILL.md +24 -0
  220. package/src/orchestrator/skills/task-management/SKILL.md +143 -0
  221. package/src/orchestrator/skills/team-lead-reference/SKILL.md +317 -0
  222. package/src/orchestrator/skills/teams-notifications/SKILL.md +249 -0
  223. package/src/orchestrator/skills/testing-workflow/SKILL.md +134 -0
  224. package/src/orchestrator/skills/validation-gates/SKILL.md +100 -0
@@ -0,0 +1,168 @@
1
+ import { resolve } from 'node:path'
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises'
3
+ import { existsSync } from 'node:fs'
4
+ import { copyDir, getOrchestratorRoot, removeDirIfExists } from '../copy.js'
5
+ import { scaffoldMcpConfig } from '../mcp.js'
6
+ import { getExcludedSkills, getExcludedAgents, getCustomizationsTransform } from '../stack-config.js'
7
+ import type { CopyResults, ManagedPaths, StackConfig } from '../types.js'
8
+
9
+ /**
10
+ * VS Code / GitHub Copilot adapter.
11
+ *
12
+ * This is the **native format** — the orchestrator source files map 1:1.
13
+ *
14
+ * copilot-instructions.md → .github/copilot-instructions.md
15
+ * agents/ → .github/agents/
16
+ * instructions/ → .github/instructions/
17
+ * skills/ → .github/skills/
18
+ * agent-workflows/ → .github/agent-workflows/
19
+ * prompts/ → .github/prompts/
20
+ * customizations/ → .github/customizations/ (scaffolded once)
21
+ */
22
+
23
+ export const IDE_ID = 'vscode'
24
+ export const IDE_LABEL = 'VS Code (GitHub Copilot)'
25
+
26
+ /** Directories whose contents are framework-managed (overwritten on update). */
27
+ const FRAMEWORK_DIRS = [
28
+ 'agents',
29
+ 'instructions',
30
+ 'skills',
31
+ 'agent-workflows',
32
+ 'prompts',
33
+ ]
34
+
35
+ /** Directories scaffolded once and never overwritten. */
36
+ const CUSTOMIZABLE_DIRS = ['customizations']
37
+
38
+ export async function install(
39
+ pkgRoot: string,
40
+ projectRoot: string,
41
+ stack?: StackConfig
42
+ ): Promise<CopyResults> {
43
+ const srcRoot = getOrchestratorRoot(pkgRoot)
44
+ const destRoot = resolve(projectRoot, '.github')
45
+
46
+ await mkdir(destRoot, { recursive: true })
47
+
48
+ const results: CopyResults = { copied: [], skipped: [], created: [] }
49
+
50
+ const excludedSkills = stack ? getExcludedSkills(stack) : new Set<string>()
51
+ const excludedAgents = stack ? getExcludedAgents(stack) : new Set<string>()
52
+
53
+ // copilot-instructions.md
54
+ const copilotSrc = resolve(srcRoot, 'copilot-instructions.md')
55
+ const copilotDest = resolve(destRoot, 'copilot-instructions.md')
56
+ if (!existsSync(copilotDest)) {
57
+ await writeFile(copilotDest, await readFile(copilotSrc, 'utf8'))
58
+ results.created.push(copilotDest)
59
+ } else {
60
+ results.skipped.push(copilotDest)
61
+ }
62
+
63
+ // Framework directories
64
+ for (const dir of FRAMEWORK_DIRS) {
65
+ const srcDir = resolve(srcRoot, dir)
66
+ if (!existsSync(srcDir)) continue
67
+ const destDir = resolve(destRoot, dir)
68
+
69
+ // Build filter based on directory type
70
+ let filter: ((_name: string, _srcPath: string) => boolean) | undefined
71
+ if (dir === 'skills') {
72
+ filter = (name) => !excludedSkills.has(name)
73
+ } else if (dir === 'agents') {
74
+ filter = (name) => !excludedAgents.has(name)
75
+ }
76
+
77
+ const sub = await copyDir(srcDir, destDir, { filter })
78
+ results.copied.push(...sub.copied)
79
+ results.skipped.push(...sub.skipped)
80
+ results.created.push(...sub.created)
81
+ }
82
+
83
+ // Customization templates (scaffold once)
84
+ const custTransform = stack ? getCustomizationsTransform(stack) : undefined
85
+ for (const dir of CUSTOMIZABLE_DIRS) {
86
+ const srcDir = resolve(srcRoot, dir)
87
+ if (!existsSync(srcDir)) continue
88
+ const destDir = resolve(destRoot, dir)
89
+ const sub = await copyDir(srcDir, destDir, { transform: custTransform })
90
+ results.copied.push(...sub.copied)
91
+ results.skipped.push(...sub.skipped)
92
+ results.created.push(...sub.created)
93
+ }
94
+
95
+ // MCP server config → .vscode/mcp.json (scaffold once)
96
+ const mcpResult = await scaffoldMcpConfig(
97
+ pkgRoot,
98
+ projectRoot,
99
+ '.vscode/mcp.json',
100
+ stack
101
+ )
102
+ results[mcpResult.action].push(mcpResult.path)
103
+
104
+ return results
105
+ }
106
+
107
+ export async function update(
108
+ pkgRoot: string,
109
+ projectRoot: string,
110
+ stack?: StackConfig
111
+ ): Promise<CopyResults> {
112
+ const srcRoot = getOrchestratorRoot(pkgRoot)
113
+ const destRoot = resolve(projectRoot, '.github')
114
+
115
+ const results: CopyResults = { copied: [], skipped: [], created: [] }
116
+
117
+ const excludedSkills = stack ? getExcludedSkills(stack) : new Set<string>()
118
+ const excludedAgents = stack ? getExcludedAgents(stack) : new Set<string>()
119
+
120
+ // Overwrite copilot-instructions.md
121
+ const copilotDest = resolve(destRoot, 'copilot-instructions.md')
122
+ await writeFile(
123
+ copilotDest,
124
+ await readFile(resolve(srcRoot, 'copilot-instructions.md'), 'utf8')
125
+ )
126
+ results.copied.push(copilotDest)
127
+
128
+ // Remove existing framework directories to clear stale files
129
+ for (const dir of FRAMEWORK_DIRS) {
130
+ await removeDirIfExists(resolve(destRoot, dir))
131
+ }
132
+
133
+ // Re-copy framework directories
134
+ for (const dir of FRAMEWORK_DIRS) {
135
+ const srcDir = resolve(srcRoot, dir)
136
+ if (!existsSync(srcDir)) continue
137
+ const destDir = resolve(destRoot, dir)
138
+
139
+ let filter: ((_name: string, _srcPath: string) => boolean) | undefined
140
+ if (dir === 'skills') {
141
+ filter = (name) => !excludedSkills.has(name)
142
+ } else if (dir === 'agents') {
143
+ filter = (name) => !excludedAgents.has(name)
144
+ }
145
+
146
+ const sub = await copyDir(srcDir, destDir, { overwrite: true, filter })
147
+ // All re-installed framework files count as "updated" (copied), not "created"
148
+ results.copied.push(...sub.copied, ...sub.created)
149
+ results.skipped.push(...sub.skipped)
150
+ }
151
+
152
+ // Customizations are NEVER overwritten during update.
153
+
154
+ return results
155
+ }
156
+
157
+ export function getManagedPaths(): ManagedPaths {
158
+ return {
159
+ framework: [
160
+ '.github/copilot-instructions.md',
161
+ ...FRAMEWORK_DIRS.map((d) => `.github/${d}/`),
162
+ ],
163
+ customizable: [
164
+ ...CUSTOMIZABLE_DIRS.map((d) => `.github/${d}/`),
165
+ '.vscode/mcp.json',
166
+ ],
167
+ }
168
+ }
@@ -0,0 +1,79 @@
1
+ import {
2
+ readdir,
3
+ readFile,
4
+ mkdir,
5
+ writeFile,
6
+ copyFile,
7
+ rm,
8
+ } from 'node:fs/promises';
9
+ import { resolve } from 'node:path';
10
+ import { existsSync } from 'node:fs';
11
+ import type { CopyResults, CopyDirOptions } from './types.js';
12
+
13
+ /**
14
+ * Recursively copy a directory tree.
15
+ */
16
+ export async function copyDir(
17
+ src: string,
18
+ dest: string,
19
+ { overwrite = false, filter, transform }: CopyDirOptions = {}
20
+ ): Promise<CopyResults> {
21
+ const entries = await readdir(src, { withFileTypes: true });
22
+ await mkdir(dest, { recursive: true });
23
+
24
+ const results: CopyResults = { copied: [], skipped: [], created: [] };
25
+
26
+ for (const entry of entries) {
27
+ const srcPath = resolve(src, entry.name);
28
+ const destPath = resolve(dest, entry.name);
29
+
30
+ if (filter && !filter(entry.name, srcPath)) continue;
31
+
32
+ if (entry.isDirectory()) {
33
+ const sub = await copyDir(srcPath, destPath, {
34
+ overwrite,
35
+ filter,
36
+ transform,
37
+ });
38
+ results.copied.push(...sub.copied);
39
+ results.skipped.push(...sub.skipped);
40
+ results.created.push(...sub.created);
41
+ } else {
42
+ const exists = existsSync(destPath);
43
+ if (exists && !overwrite) {
44
+ results.skipped.push(destPath);
45
+ continue;
46
+ }
47
+
48
+ if (transform) {
49
+ const content = await readFile(srcPath, 'utf8');
50
+ const transformed = await transform(content, srcPath);
51
+ if (transformed !== null) {
52
+ await writeFile(destPath, transformed);
53
+ results[exists ? 'copied' : 'created'].push(destPath);
54
+ }
55
+ } else {
56
+ await copyFile(srcPath, destPath);
57
+ results[exists ? 'copied' : 'created'].push(destPath);
58
+ }
59
+ }
60
+ }
61
+
62
+ return results;
63
+ }
64
+
65
+ /**
66
+ * Resolve the orchestrator source directory from the CLI package root.
67
+ */
68
+ export function getOrchestratorRoot(pkgRoot: string): string {
69
+ return resolve(pkgRoot, 'src', 'orchestrator');
70
+ }
71
+
72
+ /**
73
+ * Remove a directory if it exists. No-op if it doesn't.
74
+ */
75
+ export async function removeDirIfExists(dirPath: string): Promise<void> {
76
+ if (existsSync(dirPath)) {
77
+ await rm(dirPath, { recursive: true });
78
+ }
79
+ }
@@ -0,0 +1,225 @@
1
+ import { createServer } from 'node:http'
2
+ import type { IncomingMessage, ServerResponse, Server } from 'node:http'
3
+ import { readFile, access } from 'node:fs/promises'
4
+ import { resolve, join, extname } from 'node:path'
5
+ import { exec } from 'node:child_process'
6
+ import type { CliContext } from './types.js'
7
+
8
+ const MIME_TYPES: Record<string, string> = {
9
+ '.html': 'text/html',
10
+ '.css': 'text/css',
11
+ '.js': 'application/javascript',
12
+ '.json': 'application/json',
13
+ '.ndjson': 'application/x-ndjson',
14
+ '.png': 'image/png',
15
+ '.svg': 'image/svg+xml',
16
+ '.ico': 'image/x-icon',
17
+ '.woff': 'font/woff',
18
+ '.woff2': 'font/woff2',
19
+ }
20
+
21
+ const DATA_FILES = [
22
+ 'sessions.ndjson',
23
+ 'delegations.ndjson',
24
+ 'panels.ndjson',
25
+ ]
26
+
27
+ interface DashboardArgs {
28
+ port: number
29
+ openBrowser: boolean
30
+ seed: boolean
31
+ }
32
+
33
+ function parseArgs(args: string[]): DashboardArgs {
34
+ let port = 4300
35
+ let openBrowser = true
36
+ let seed = false
37
+
38
+ for (let i = 0; i < args.length; i++) {
39
+ if (args[i] === '--port' && args[i + 1]) {
40
+ port = parseInt(args[i + 1], 10)
41
+ i++
42
+ } else if (args[i] === '--no-open') {
43
+ openBrowser = false
44
+ } else if (args[i] === '--seed') {
45
+ seed = true
46
+ }
47
+ }
48
+
49
+ return { port, openBrowser, seed }
50
+ }
51
+
52
+ function openUrl(url: string): void {
53
+ const plat = process.platform
54
+ const cmd =
55
+ plat === 'darwin'
56
+ ? 'open'
57
+ : plat === 'win32'
58
+ ? 'start'
59
+ : 'xdg-open'
60
+ exec(`${cmd} ${url}`)
61
+ }
62
+
63
+ async function fileExists(filePath: string): Promise<boolean> {
64
+ try {
65
+ await access(filePath)
66
+ return true
67
+ } catch {
68
+ return false
69
+ }
70
+ }
71
+
72
+ function tryListen(
73
+ server: Server,
74
+ port: number,
75
+ maxAttempts = 10
76
+ ): Promise<number> {
77
+ return new Promise((res, rej) => {
78
+ let attempt = 0
79
+
80
+ function attemptListen(): void {
81
+ server.listen(port + attempt, '127.0.0.1', () => {
82
+ res(port + attempt)
83
+ })
84
+ server.once('error', (err: Error & { code?: string }) => {
85
+ if (err.code === 'EADDRINUSE' && attempt < maxAttempts) {
86
+ attempt++
87
+ attemptListen()
88
+ } else {
89
+ rej(err)
90
+ }
91
+ })
92
+ }
93
+
94
+ attemptListen()
95
+ })
96
+ }
97
+
98
+ export default async function dashboard({
99
+ pkgRoot,
100
+ args,
101
+ }: CliContext): Promise<void> {
102
+ const { port, openBrowser, seed } = parseArgs(args)
103
+
104
+ const distDir = resolve(pkgRoot, 'src', 'dashboard', 'dist')
105
+ const projectRoot = process.cwd()
106
+ const logsDir = resolve(projectRoot, '.github', 'customizations', 'logs')
107
+
108
+ // Check if dist exists
109
+ if (!(await fileExists(distDir))) {
110
+ throw new Error(
111
+ 'Dashboard not built. Run "npm run build:dashboard" in the opencastle package first.'
112
+ )
113
+ }
114
+
115
+ // Check if any log files exist (for messaging)
116
+ let hasLogs = false
117
+ if (!seed) {
118
+ for (const f of DATA_FILES) {
119
+ if (await fileExists(join(logsDir, f))) {
120
+ hasLogs = true
121
+ break
122
+ }
123
+ }
124
+ }
125
+
126
+ const server = createServer(
127
+ async (req: IncomingMessage, res: ServerResponse) => {
128
+ try {
129
+ const url = new URL(req.url ?? '/', `http://${req.headers.host}`)
130
+ let pathname = decodeURIComponent(url.pathname)
131
+
132
+ // Serve index.html for root
133
+ if (pathname === '/') {
134
+ pathname = '/index.html'
135
+ }
136
+
137
+ // Handle data file requests — proxy to project logs or dist
138
+ const dataMatch = pathname.match(/^\/data\/(.+\.ndjson)$/)
139
+ if (dataMatch && DATA_FILES.includes(dataMatch[1])) {
140
+ const filename = dataMatch[1]
141
+ let filePath: string
142
+
143
+ if (seed) {
144
+ filePath = join(distDir, 'data', filename)
145
+ } else {
146
+ filePath = join(logsDir, filename)
147
+ }
148
+
149
+ if (await fileExists(filePath)) {
150
+ const content = await readFile(filePath)
151
+ res.writeHead(200, { 'Content-Type': 'application/x-ndjson' })
152
+ res.end(content)
153
+ } else {
154
+ // Graceful fallback — empty body
155
+ res.writeHead(200, { 'Content-Type': 'application/x-ndjson' })
156
+ res.end('')
157
+ }
158
+ return
159
+ }
160
+
161
+ // Serve static files from dist/
162
+ const filePath = resolve(distDir, pathname.slice(1))
163
+
164
+ // Security: prevent path traversal
165
+ if (!filePath.startsWith(distDir)) {
166
+ res.writeHead(403)
167
+ res.end('Forbidden')
168
+ return
169
+ }
170
+
171
+ if (await fileExists(filePath)) {
172
+ const ext = extname(filePath)
173
+ const contentType = MIME_TYPES[ext] ?? 'application/octet-stream'
174
+ const content = await readFile(filePath)
175
+ res.writeHead(200, { 'Content-Type': contentType })
176
+ res.end(content)
177
+ } else {
178
+ res.writeHead(404)
179
+ res.end('Not Found')
180
+ }
181
+ } catch {
182
+ res.writeHead(500)
183
+ res.end('Internal Server Error')
184
+ }
185
+ }
186
+ )
187
+
188
+ const actualPort = await tryListen(server, port)
189
+ const url = `http://localhost:${actualPort}`
190
+
191
+ console.log('')
192
+ console.log(' \u{1F3F0} OpenCastle Dashboard')
193
+ console.log('')
194
+ console.log(` \u2192 ${url}`)
195
+
196
+ if (seed) {
197
+ console.log(
198
+ ' \u{1F4C2} Showing demo data (use without --seed to read project logs)'
199
+ )
200
+ } else if (hasLogs) {
201
+ console.log(' \u{1F4C2} Reading logs from .github/customizations/logs/')
202
+ } else {
203
+ console.log(
204
+ ' \u{1F4A1} No agent logs found. Run agents with OpenCastle to generate data, or use --seed for demo data.'
205
+ )
206
+ }
207
+
208
+ console.log('')
209
+ console.log(' Press Ctrl+C to stop')
210
+ console.log('')
211
+
212
+ if (openBrowser) {
213
+ openUrl(url)
214
+ }
215
+
216
+ // Graceful shutdown
217
+ process.on('SIGINT', () => {
218
+ console.log('\n Dashboard stopped.\n')
219
+ server.close()
220
+ process.exit(0)
221
+ })
222
+
223
+ // Keep the process alive
224
+ await new Promise<never>(() => {})
225
+ }
@@ -0,0 +1,44 @@
1
+ import { resolve } from 'node:path'
2
+ import { readFile } from 'node:fs/promises'
3
+ import { readManifest } from './manifest.js'
4
+ import type { CliContext } from './types.js'
5
+
6
+ export default async function diff({ pkgRoot }: CliContext): Promise<void> {
7
+ const projectRoot = process.cwd()
8
+
9
+ const manifest = await readManifest(projectRoot)
10
+ if (!manifest) {
11
+ console.error(
12
+ ' ✗ No OpenCastle installation found. Run "npx opencastle init" first.'
13
+ )
14
+ process.exit(1)
15
+ }
16
+
17
+ const pkg = JSON.parse(
18
+ await readFile(resolve(pkgRoot, 'package.json'), 'utf8')
19
+ ) as { version: string }
20
+
21
+ if (manifest.version === pkg.version) {
22
+ console.log(
23
+ ` No changes — installed version matches package version (v${pkg.version}).`
24
+ )
25
+ return
26
+ }
27
+
28
+ console.log(
29
+ `\n 🏰 OpenCastle diff: v${manifest.version} → v${pkg.version}\n`
30
+ )
31
+ console.log(' Framework files that would be updated:\n')
32
+
33
+ for (const path of manifest.managedPaths?.framework ?? []) {
34
+ console.log(` ↻ ${path}`)
35
+ }
36
+
37
+ console.log('\n Customization files that would be preserved:\n')
38
+
39
+ for (const path of manifest.managedPaths?.customizable ?? []) {
40
+ console.log(` ✓ ${path}`)
41
+ }
42
+
43
+ console.log(`\n Run "npx opencastle update" to apply.\n`)
44
+ }
@@ -0,0 +1,39 @@
1
+ import { resolve } from 'node:path'
2
+ import { unlink } from 'node:fs/promises'
3
+ import { readManifest } from './manifest.js'
4
+ import { confirm, closePrompts } from './prompt.js'
5
+ import type { CliContext } from './types.js'
6
+
7
+ export default async function eject({
8
+ pkgRoot: _pkgRoot,
9
+ args: _args,
10
+ }: CliContext): Promise<void> {
11
+ const projectRoot = process.cwd()
12
+
13
+ const manifest = await readManifest(projectRoot)
14
+ if (!manifest) {
15
+ console.error(' ✗ No OpenCastle installation found.')
16
+ process.exit(1)
17
+ }
18
+
19
+ console.log(`\n 🏰 OpenCastle eject\n`)
20
+ console.log(' This will:')
21
+ console.log(' • Remove .opencastle.json (manifest)')
22
+ console.log(' • Keep ALL generated files as standalone')
23
+ console.log(
24
+ ' • You can safely uninstall the opencastle package after this\n'
25
+ )
26
+
27
+ const proceed = await confirm('Continue?')
28
+ if (!proceed) {
29
+ console.log(' Aborted.')
30
+ return
31
+ }
32
+
33
+ await unlink(resolve(projectRoot, '.opencastle.json'))
34
+
35
+ console.log('\n ✓ Ejected. Files are now standalone.')
36
+ console.log(' You can uninstall: npm uninstall opencastle\n')
37
+
38
+ closePrompts()
39
+ }
@@ -0,0 +1,120 @@
1
+ import { resolve } from 'node:path'
2
+ import { readFile } from 'node:fs/promises'
3
+ import { select, confirm, closePrompts } from './prompt.js'
4
+ import { readManifest, writeManifest, createManifest } from './manifest.js'
5
+ import type { CliContext, IdeAdapter, CmsChoice, DbChoice, PmChoice, NotifChoice, StackConfig } from './types.js'
6
+
7
+ const ADAPTERS: Record<string, () => Promise<IdeAdapter>> = {
8
+ vscode: () => import('./adapters/vscode.js') as Promise<IdeAdapter>,
9
+ cursor: () => import('./adapters/cursor.js') as Promise<IdeAdapter>,
10
+ 'claude-code': () =>
11
+ import('./adapters/claude-code.js') as Promise<IdeAdapter>,
12
+ }
13
+
14
+ export default async function init({ pkgRoot }: CliContext): Promise<void> {
15
+ const projectRoot = process.cwd()
16
+
17
+ // Check for existing installation
18
+ const existing = await readManifest(projectRoot)
19
+ if (existing) {
20
+ const proceed = await confirm(
21
+ `OpenCastle already installed (v${existing.version}, ${existing.ide}). Re-initialize?`,
22
+ false
23
+ )
24
+ if (!proceed) {
25
+ console.log(' Aborted.')
26
+ return
27
+ }
28
+ }
29
+
30
+ const pkg = JSON.parse(
31
+ await readFile(resolve(pkgRoot, 'package.json'), 'utf8')
32
+ ) as { version: string }
33
+
34
+ console.log(`\n 🏰 OpenCastle v${pkg.version}`)
35
+ console.log(
36
+ ' Multi-agent orchestration framework for AI coding assistants\n'
37
+ )
38
+
39
+ // ── IDE selection ───────────────────────────────────────────────
40
+ const ide = await select('Which IDE are you using?', [
41
+ {
42
+ label: 'VS Code',
43
+ hint: 'GitHub Copilot — .github/ agents, instructions, skills',
44
+ value: 'vscode',
45
+ },
46
+ {
47
+ label: 'Cursor',
48
+ hint: '.cursorrules & .cursor/rules/*.mdc',
49
+ value: 'cursor',
50
+ },
51
+ {
52
+ label: 'Claude Code',
53
+ hint: 'CLAUDE.md & .claude/ commands, skills',
54
+ value: 'claude-code',
55
+ },
56
+ ])
57
+
58
+ // ── CMS selection ───────────────────────────────────────────────
59
+ const cms = await select('Which CMS are you using?', [
60
+ { label: 'Sanity', hint: 'GROQ queries, real-time collaboration', value: 'sanity' },
61
+ { label: 'Contentful', hint: 'GraphQL / REST API, structured content', value: 'contentful' },
62
+ { label: 'Strapi', hint: 'Open-source headless CMS', value: 'strapi' },
63
+ { label: 'None', hint: 'No CMS — skip CMS skills and agents', value: 'none' },
64
+ ])
65
+
66
+ // ── Database selection ──────────────────────────────────────────
67
+ const db = await select('Which database are you using?', [
68
+ { label: 'Supabase', hint: 'Postgres + Auth + RLS + Edge Functions', value: 'supabase' },
69
+ { label: 'Convex', hint: 'Reactive backend with real-time sync', value: 'convex' },
70
+ { label: 'None', hint: 'No database — skip DB skills and agents', value: 'none' },
71
+ ])
72
+
73
+ // ── Project management selection ────────────────────────────────
74
+ const pm = await select('Which project management tool are you using?', [
75
+ { label: 'Linear', hint: 'Issue tracking with MCP integration', value: 'linear' },
76
+ { label: 'Jira', hint: 'Atlassian issue tracking via Rovo MCP', value: 'jira' },
77
+ { label: 'None', hint: 'No project management — skip PM skills', value: 'none' },
78
+ ])
79
+
80
+ // ── Notifications selection ────────────────────────────────────
81
+ const notifications = await select('Which notifications tool are you using?', [
82
+ { label: 'Slack', hint: 'Agent notifications and bi-directional communication', value: 'slack' },
83
+ { label: 'Microsoft Teams', hint: 'Agent notifications via Teams channels', value: 'teams' },
84
+ { label: 'None', hint: 'No notifications — skip messaging skills', value: 'none' },
85
+ ])
86
+
87
+ const stack: StackConfig = { cms: cms as CmsChoice, db: db as DbChoice, pm: pm as PmChoice, notifications: notifications as NotifChoice }
88
+
89
+ console.log(`\n Installing for ${ide}...`)
90
+ console.log(` Stack: CMS=${stack.cms}, DB=${stack.db}, PM=${stack.pm}, Notifications=${stack.notifications}\n`)
91
+
92
+ // ── Run adapter ─────────────────────────────────────────────────
93
+ const adapter = await ADAPTERS[ide]()
94
+ const results = await adapter.install(pkgRoot, projectRoot, stack)
95
+
96
+ // ── Write manifest ──────────────────────────────────────────────
97
+ const manifest = createManifest(pkg.version, ide)
98
+ manifest.managedPaths = adapter.getManagedPaths()
99
+ manifest.stack = stack
100
+ await writeManifest(projectRoot, manifest)
101
+
102
+ // ── Summary ─────────────────────────────────────────────────────
103
+ const created = results.created.length
104
+ const skipped = results.skipped.length
105
+
106
+ console.log(` ✓ Created ${created} files`)
107
+ if (skipped > 0) {
108
+ console.log(` → Skipped ${skipped} existing files`)
109
+ }
110
+
111
+ console.log(`\n Next steps:`)
112
+ console.log(
113
+ ' 1. Run the "Bootstrap Customizations" prompt to configure for your project'
114
+ )
115
+ console.log(' 2. Customize agent definitions for your tech stack')
116
+ console.log(' 3. Commit the generated files to your repository')
117
+ console.log()
118
+
119
+ closePrompts()
120
+ }