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,45 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import type { Manifest } from './types.js';
4
+
5
+ const MANIFEST_FILE = '.opencastle.json';
6
+
7
+ /**
8
+ * Read the project's OpenCastle manifest, or null if not installed.
9
+ */
10
+ export async function readManifest(
11
+ projectRoot: string
12
+ ): Promise<Manifest | null> {
13
+ try {
14
+ const content = await readFile(
15
+ resolve(projectRoot, MANIFEST_FILE),
16
+ 'utf8'
17
+ );
18
+ return JSON.parse(content) as Manifest;
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Write the manifest to the project root.
26
+ */
27
+ export async function writeManifest(
28
+ projectRoot: string,
29
+ manifest: Manifest
30
+ ): Promise<void> {
31
+ const path = resolve(projectRoot, MANIFEST_FILE);
32
+ await writeFile(path, JSON.stringify(manifest, null, 2) + '\n');
33
+ }
34
+
35
+ /**
36
+ * Create a fresh manifest object.
37
+ */
38
+ export function createManifest(version: string, ide: string): Manifest {
39
+ return {
40
+ version,
41
+ ide,
42
+ installedAt: new Date().toISOString(),
43
+ updatedAt: new Date().toISOString(),
44
+ };
45
+ }
package/src/cli/mcp.ts ADDED
@@ -0,0 +1,49 @@
1
+ import { resolve, dirname } from 'node:path';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { existsSync } from 'node:fs';
4
+ import { getOrchestratorRoot } from './copy.js';
5
+ import { getIncludedMcpServers } from './stack-config.js';
6
+ import type { ScaffoldResult, StackConfig } from './types.js';
7
+
8
+ /**
9
+ * Scaffold the MCP server config into the target project.
10
+ *
11
+ * Reads the template from `opencastle/src/orchestrator/mcp.json`,
12
+ * writes it to `<projectRoot>/<destRelPath>` (e.g. `.vscode/mcp.json`).
13
+ *
14
+ * When a StackConfig is provided, only servers relevant to the chosen
15
+ * CMS/DB stack (plus core servers) are included.
16
+ *
17
+ * This is a customizable file — scaffolded once, never overwritten on update.
18
+ */
19
+ export async function scaffoldMcpConfig(
20
+ pkgRoot: string,
21
+ projectRoot: string,
22
+ destRelPath: string,
23
+ stack?: StackConfig
24
+ ): Promise<ScaffoldResult> {
25
+ const destPath = resolve(projectRoot, destRelPath);
26
+
27
+ if (existsSync(destPath)) {
28
+ return { path: destPath, action: 'skipped' };
29
+ }
30
+
31
+ const srcRoot = getOrchestratorRoot(pkgRoot);
32
+ const templatePath = resolve(srcRoot, 'mcp.json');
33
+ const content = await readFile(templatePath, 'utf8');
34
+
35
+ const template = JSON.parse(content) as { servers: Record<string, unknown> };
36
+
37
+ // Filter servers based on stack config
38
+ if (stack) {
39
+ const included = getIncludedMcpServers(stack);
40
+ template.servers = Object.fromEntries(
41
+ Object.entries(template.servers).filter(([key]) => included.has(key))
42
+ );
43
+ }
44
+
45
+ await mkdir(dirname(destPath), { recursive: true });
46
+ await writeFile(destPath, JSON.stringify(template, null, 2) + '\n');
47
+
48
+ return { path: destPath, action: 'created' };
49
+ }
@@ -0,0 +1,115 @@
1
+ import { createInterface, type Interface } from 'node:readline/promises';
2
+ import { stdin, stdout } from 'node:process';
3
+ import type { SelectOption } from './types.js';
4
+
5
+ // ── Line-buffered readline ────────────────────────────────────────
6
+ // readline.question() drops lines that arrived between calls because
7
+ // it only listens for the NEXT 'line' event. When piped input
8
+ // delivers multiple lines in one chunk (e.g. `printf 'y\n3\n' | …`),
9
+ // the second line fires before the second question() is registered.
10
+ //
11
+ // We solve this with a permanent 'line' listener that pushes into a
12
+ // queue. nextLine() either pops from the queue or awaits the next
13
+ // event — no data is ever lost.
14
+
15
+ let _rl: Interface | null = null;
16
+ const _lineBuffer: string[] = [];
17
+ let _lineResolver: ((_line: string) => void) | null = null;
18
+
19
+ function ensureRL(): void {
20
+ if (_rl) return;
21
+ _rl = createInterface({ input: stdin, output: stdout });
22
+ _rl.on('line', (line: string) => {
23
+ if (_lineResolver) {
24
+ const resolve = _lineResolver;
25
+ _lineResolver = null;
26
+ resolve(line);
27
+ } else {
28
+ _lineBuffer.push(line);
29
+ }
30
+ });
31
+ _rl.on('close', () => {
32
+ _rl = null;
33
+ // Resolve any pending prompt with empty string → triggers defaults
34
+ if (_lineResolver) {
35
+ const resolve = _lineResolver;
36
+ _lineResolver = null;
37
+ resolve('');
38
+ }
39
+ });
40
+ }
41
+
42
+ /**
43
+ * Read the next line from stdin, displaying a prompt first.
44
+ * Consumes from the internal buffer when piped input delivered
45
+ * multiple lines in a single chunk.
46
+ */
47
+ async function nextLine(prompt: string): Promise<string> {
48
+ ensureRL();
49
+ stdout.write(prompt);
50
+ if (_lineBuffer.length > 0) {
51
+ const line = _lineBuffer.shift()!;
52
+ // Echo the buffered answer for non-TTY so logs read naturally
53
+ if (!stdin.isTTY) stdout.write(line + '\n');
54
+ return line;
55
+ }
56
+ return new Promise<string>((resolve) => {
57
+ _lineResolver = resolve;
58
+ });
59
+ }
60
+
61
+ /** Close the shared readline interface. Call once at command end. */
62
+ export function closePrompts(): void {
63
+ if (_rl) {
64
+ _rl.close();
65
+ _rl = null;
66
+ _lineBuffer.length = 0;
67
+ _lineResolver = null;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Interactive single-choice selection prompt.
73
+ */
74
+ export async function select(
75
+ message: string,
76
+ options: SelectOption[]
77
+ ): Promise<string> {
78
+ console.log(`\n ${message}\n`);
79
+ options.forEach((opt, i) => {
80
+ const hint = opt.hint ? ` — ${opt.hint}` : '';
81
+ console.log(` ${i + 1}) ${opt.label}${hint}`);
82
+ });
83
+
84
+ let choice: SelectOption | undefined;
85
+ while (!choice) {
86
+ const answer = await nextLine(`\n Select [1-${options.length}]: `);
87
+ // Handle EOF — stdin closed without valid selection
88
+ if (answer === '' && (!_rl || !stdin.isTTY)) {
89
+ console.error('\n ✗ No input received (stdin closed). Aborting.');
90
+ process.exit(1);
91
+ }
92
+ const num = parseInt(answer, 10);
93
+ if (num >= 1 && num <= options.length) {
94
+ choice = options[num - 1];
95
+ } else {
96
+ console.log(` Please enter a number between 1 and ${options.length}`);
97
+ }
98
+ }
99
+
100
+ return choice.value;
101
+ }
102
+
103
+ /**
104
+ * Yes/No confirmation prompt.
105
+ */
106
+ export async function confirm(
107
+ message: string,
108
+ defaultYes = true
109
+ ): Promise<boolean> {
110
+ const hint = defaultYes ? '[Y/n]' : '[y/N]';
111
+ const answer = await nextLine(` ${message} ${hint} `);
112
+
113
+ if (!answer.trim()) return defaultYes;
114
+ return answer.trim().toLowerCase().startsWith('y');
115
+ }
@@ -0,0 +1,95 @@
1
+ import { spawn } from 'node:child_process'
2
+ import type { Task, ExecuteOptions, ExecuteResult } from '../../types.js'
3
+
4
+ /** Adapter name */
5
+ export const name = 'claude-code'
6
+
7
+ /**
8
+ * Check if the `claude` CLI is available on the system PATH.
9
+ */
10
+ export async function isAvailable(): Promise<boolean> {
11
+ return new Promise((resolve) => {
12
+ const proc = spawn('which', ['claude'], { stdio: 'pipe' })
13
+ proc.on('close', (code) => resolve(code === 0))
14
+ proc.on('error', () => resolve(false))
15
+ })
16
+ }
17
+
18
+ /**
19
+ * Execute a task by invoking the Claude Code CLI in print mode.
20
+ */
21
+ export async function execute(task: Task, options: ExecuteOptions = {}): Promise<ExecuteResult> {
22
+ let prompt = `You are a ${task.agent}. ${task.prompt}`
23
+
24
+ if (task.files && task.files.length > 0) {
25
+ prompt += `\n\nOnly modify files under: ${task.files.join(', ')}`
26
+ }
27
+
28
+ const args = [
29
+ '-p',
30
+ prompt,
31
+ '--output-format',
32
+ 'json',
33
+ '--max-turns',
34
+ '50',
35
+ ]
36
+
37
+ return new Promise((resolve) => {
38
+ const proc = spawn('claude', args, {
39
+ stdio: ['ignore', 'pipe', 'pipe'],
40
+ env: { ...process.env },
41
+ cwd: process.cwd(),
42
+ })
43
+
44
+ let stdout = ''
45
+ let stderr = ''
46
+
47
+ proc.stdout.on('data', (chunk: Buffer) => {
48
+ stdout += chunk.toString()
49
+ if (options.verbose) {
50
+ process.stdout.write(chunk)
51
+ }
52
+ })
53
+
54
+ proc.stderr.on('data', (chunk: Buffer) => {
55
+ stderr += chunk.toString()
56
+ if (options.verbose) {
57
+ process.stderr.write(chunk)
58
+ }
59
+ })
60
+
61
+ proc.on('close', (code) => {
62
+ const output = [stdout, stderr].filter(Boolean).join('\n')
63
+ resolve({
64
+ success: code === 0,
65
+ output: output.slice(0, 10000), // Cap output size
66
+ exitCode: code ?? -1,
67
+ })
68
+ })
69
+
70
+ proc.on('error', (err) => {
71
+ resolve({
72
+ success: false,
73
+ output: `Failed to spawn claude: ${err.message}`,
74
+ exitCode: -1,
75
+ })
76
+ })
77
+
78
+ // Store process ref for potential timeout kill
79
+ task._process = proc
80
+ })
81
+ }
82
+
83
+ /**
84
+ * Kill the process associated with a task (used by timeout enforcement).
85
+ */
86
+ export function kill(task: Task): void {
87
+ if (task._process && !task._process.killed) {
88
+ task._process.kill('SIGTERM')
89
+ setTimeout(() => {
90
+ if (task._process && !task._process.killed) {
91
+ task._process.kill('SIGKILL')
92
+ }
93
+ }, 5000)
94
+ }
95
+ }
@@ -0,0 +1,97 @@
1
+ import { spawn } from 'node:child_process'
2
+ import type { Task, ExecuteOptions, ExecuteResult } from '../../types.js'
3
+
4
+ /** Adapter name */
5
+ export const name = 'copilot'
6
+
7
+ /**
8
+ * Check if the `copilot` CLI is available on the system PATH.
9
+ */
10
+ export async function isAvailable(): Promise<boolean> {
11
+ return new Promise((resolve) => {
12
+ const proc = spawn('which', ['copilot'], { stdio: 'pipe' })
13
+ proc.on('close', (code) => resolve(code === 0))
14
+ proc.on('error', () => resolve(false))
15
+ })
16
+ }
17
+
18
+ /**
19
+ * Execute a task by invoking the Copilot CLI in autopilot mode.
20
+ */
21
+ export async function execute(task: Task, options: ExecuteOptions = {}): Promise<ExecuteResult> {
22
+ let prompt = `You are a ${task.agent}. ${task.prompt}`
23
+
24
+ if (task.files && task.files.length > 0) {
25
+ prompt += `\n\nOnly modify files under: ${task.files.join(', ')}`
26
+ }
27
+
28
+ const args = [
29
+ '-p',
30
+ prompt,
31
+ '--autopilot',
32
+ '--allow-all-tools',
33
+ '--no-ask-user',
34
+ '-s',
35
+ '--max-autopilot-continues',
36
+ '50',
37
+ ]
38
+
39
+ return new Promise((resolve) => {
40
+ const proc = spawn('copilot', args, {
41
+ stdio: ['ignore', 'pipe', 'pipe'],
42
+ env: { ...process.env },
43
+ cwd: process.cwd(),
44
+ })
45
+
46
+ let stdout = ''
47
+ let stderr = ''
48
+
49
+ proc.stdout.on('data', (chunk: Buffer) => {
50
+ stdout += chunk.toString()
51
+ if (options.verbose) {
52
+ process.stdout.write(chunk)
53
+ }
54
+ })
55
+
56
+ proc.stderr.on('data', (chunk: Buffer) => {
57
+ stderr += chunk.toString()
58
+ if (options.verbose) {
59
+ process.stderr.write(chunk)
60
+ }
61
+ })
62
+
63
+ proc.on('close', (code) => {
64
+ const output = [stdout, stderr].filter(Boolean).join('\n')
65
+ resolve({
66
+ success: code === 0,
67
+ output: output.slice(0, 10000), // Cap output size
68
+ exitCode: code ?? -1,
69
+ })
70
+ })
71
+
72
+ proc.on('error', (err) => {
73
+ resolve({
74
+ success: false,
75
+ output: `Failed to spawn copilot: ${err.message}`,
76
+ exitCode: -1,
77
+ })
78
+ })
79
+
80
+ // Store process ref for potential timeout kill
81
+ task._process = proc
82
+ })
83
+ }
84
+
85
+ /**
86
+ * Kill the process associated with a task (used by timeout enforcement).
87
+ */
88
+ export function kill(task: Task): void {
89
+ if (task._process && !task._process.killed) {
90
+ task._process.kill('SIGTERM')
91
+ setTimeout(() => {
92
+ if (task._process && !task._process.killed) {
93
+ task._process.kill('SIGKILL')
94
+ }
95
+ }, 5000)
96
+ }
97
+ }
@@ -0,0 +1,94 @@
1
+ import { spawn } from 'node:child_process'
2
+ import type { Task, ExecuteOptions, ExecuteResult } from '../../types.js'
3
+
4
+ /** Adapter name */
5
+ export const name = 'cursor'
6
+
7
+ /**
8
+ * Check if the Cursor CLI (`agent`) is available on the system PATH.
9
+ */
10
+ export async function isAvailable(): Promise<boolean> {
11
+ return new Promise((resolve) => {
12
+ const proc = spawn('which', ['agent'], { stdio: 'pipe' })
13
+ proc.on('close', (code) => resolve(code === 0))
14
+ proc.on('error', () => resolve(false))
15
+ })
16
+ }
17
+
18
+ /**
19
+ * Execute a task by invoking the Cursor CLI in non-interactive print mode.
20
+ */
21
+ export async function execute(task: Task, options: ExecuteOptions = {}): Promise<ExecuteResult> {
22
+ let prompt = `You are a ${task.agent}. ${task.prompt}`
23
+
24
+ if (task.files && task.files.length > 0) {
25
+ prompt += `\n\nOnly modify files under: ${task.files.join(', ')}`
26
+ }
27
+
28
+ const args = [
29
+ '-p',
30
+ prompt,
31
+ '--force',
32
+ '--output-format',
33
+ 'json',
34
+ ]
35
+
36
+ return new Promise((resolve) => {
37
+ const proc = spawn('agent', args, {
38
+ stdio: ['ignore', 'pipe', 'pipe'],
39
+ env: { ...process.env },
40
+ cwd: process.cwd(),
41
+ })
42
+
43
+ let stdout = ''
44
+ let stderr = ''
45
+
46
+ proc.stdout.on('data', (chunk: Buffer) => {
47
+ stdout += chunk.toString()
48
+ if (options.verbose) {
49
+ process.stdout.write(chunk)
50
+ }
51
+ })
52
+
53
+ proc.stderr.on('data', (chunk: Buffer) => {
54
+ stderr += chunk.toString()
55
+ if (options.verbose) {
56
+ process.stderr.write(chunk)
57
+ }
58
+ })
59
+
60
+ proc.on('close', (code) => {
61
+ const output = [stdout, stderr].filter(Boolean).join('\n')
62
+ resolve({
63
+ success: code === 0,
64
+ output: output.slice(0, 10000), // Cap output size
65
+ exitCode: code ?? -1,
66
+ })
67
+ })
68
+
69
+ proc.on('error', (err) => {
70
+ resolve({
71
+ success: false,
72
+ output: `Failed to spawn cursor agent CLI: ${err.message}`,
73
+ exitCode: -1,
74
+ })
75
+ })
76
+
77
+ // Store process ref for potential timeout kill
78
+ task._process = proc
79
+ })
80
+ }
81
+
82
+ /**
83
+ * Kill the process associated with a task (used by timeout enforcement).
84
+ */
85
+ export function kill(task: Task): void {
86
+ if (task._process && !task._process.killed) {
87
+ task._process.kill('SIGTERM')
88
+ setTimeout(() => {
89
+ if (task._process && !task._process.killed) {
90
+ task._process.kill('SIGKILL')
91
+ }
92
+ }, 5000)
93
+ }
94
+ }
@@ -0,0 +1,40 @@
1
+ import type { AgentAdapter } from '../../types.js'
2
+
3
+ /**
4
+ * Adapter registry for agent runtimes.
5
+ */
6
+ const ADAPTERS: Record<string, () => Promise<AgentAdapter>> = {
7
+ 'claude-code': () => import('./claude-code.js') as Promise<AgentAdapter>,
8
+ copilot: () => import('./copilot.js') as Promise<AgentAdapter>,
9
+ cursor: () => import('./cursor.js') as Promise<AgentAdapter>,
10
+ }
11
+
12
+ /**
13
+ * Get an adapter module by name.
14
+ * @throws If adapter is not registered
15
+ */
16
+ export async function getAdapter(name: string): Promise<AgentAdapter> {
17
+ const loader = ADAPTERS[name]
18
+ if (!loader) {
19
+ const available = Object.keys(ADAPTERS).join(', ')
20
+ throw new Error(
21
+ `Unknown adapter "${name}". Available adapters: ${available}`
22
+ )
23
+ }
24
+ return loader()
25
+ }
26
+
27
+ /**
28
+ * List all registered adapters with their availability status.
29
+ */
30
+ export async function listAdapters(): Promise<Array<{ name: string; available: boolean }>> {
31
+ const result: Array<{ name: string; available: boolean }> = []
32
+ for (const [name, loader] of Object.entries(ADAPTERS)) {
33
+ const mod = await loader()
34
+ result.push({
35
+ name,
36
+ available: await mod.isAvailable(),
37
+ })
38
+ }
39
+ return result
40
+ }