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.
- package/LICENSE +21 -0
- package/README.md +215 -0
- package/bin/cli.mjs +69 -0
- package/dist/cli/adapters/claude-code.d.ts +22 -0
- package/dist/cli/adapters/claude-code.d.ts.map +1 -0
- package/dist/cli/adapters/claude-code.js +237 -0
- package/dist/cli/adapters/claude-code.js.map +1 -0
- package/dist/cli/adapters/cursor.d.ts +20 -0
- package/dist/cli/adapters/cursor.d.ts.map +1 -0
- package/dist/cli/adapters/cursor.js +231 -0
- package/dist/cli/adapters/cursor.js.map +1 -0
- package/dist/cli/adapters/vscode.d.ts +20 -0
- package/dist/cli/adapters/vscode.d.ts.map +1 -0
- package/dist/cli/adapters/vscode.js +132 -0
- package/dist/cli/adapters/vscode.js.map +1 -0
- package/dist/cli/copy.d.ts +14 -0
- package/dist/cli/copy.d.ts.map +1 -0
- package/dist/cli/copy.js +62 -0
- package/dist/cli/copy.js.map +1 -0
- package/dist/cli/dashboard.d.ts +3 -0
- package/dist/cli/dashboard.d.ts.map +1 -0
- package/dist/cli/dashboard.js +183 -0
- package/dist/cli/dashboard.js.map +1 -0
- package/dist/cli/diff.d.ts +3 -0
- package/dist/cli/diff.d.ts.map +1 -0
- package/dist/cli/diff.js +27 -0
- package/dist/cli/diff.js.map +1 -0
- package/dist/cli/eject.d.ts +3 -0
- package/dist/cli/eject.d.ts.map +1 -0
- package/dist/cli/eject.js +27 -0
- package/dist/cli/eject.js.map +1 -0
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +92 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/manifest.d.ts +14 -0
- package/dist/cli/manifest.d.ts.map +1 -0
- package/dist/cli/manifest.js +34 -0
- package/dist/cli/manifest.js.map +1 -0
- package/dist/cli/mcp.d.ts +14 -0
- package/dist/cli/mcp.d.ts.map +1 -0
- package/dist/cli/mcp.js +35 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/prompt.d.ts +12 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +104 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/run/adapters/claude-code.d.ts +16 -0
- package/dist/cli/run/adapters/claude-code.d.ts.map +1 -0
- package/dist/cli/run/adapters/claude-code.js +82 -0
- package/dist/cli/run/adapters/claude-code.js.map +1 -0
- package/dist/cli/run/adapters/copilot.d.ts +16 -0
- package/dist/cli/run/adapters/copilot.d.ts.map +1 -0
- package/dist/cli/run/adapters/copilot.js +84 -0
- package/dist/cli/run/adapters/copilot.js.map +1 -0
- package/dist/cli/run/adapters/cursor.d.ts +16 -0
- package/dist/cli/run/adapters/cursor.d.ts.map +1 -0
- package/dist/cli/run/adapters/cursor.js +81 -0
- package/dist/cli/run/adapters/cursor.js.map +1 -0
- package/dist/cli/run/adapters/index.d.ts +14 -0
- package/dist/cli/run/adapters/index.d.ts.map +1 -0
- package/dist/cli/run/adapters/index.js +35 -0
- package/dist/cli/run/adapters/index.js.map +1 -0
- package/dist/cli/run/executor.d.ts +15 -0
- package/dist/cli/run/executor.d.ts.map +1 -0
- package/dist/cli/run/executor.js +249 -0
- package/dist/cli/run/executor.js.map +1 -0
- package/dist/cli/run/reporter.d.ts +10 -0
- package/dist/cli/run/reporter.d.ts.map +1 -0
- package/dist/cli/run/reporter.js +112 -0
- package/dist/cli/run/reporter.js.map +1 -0
- package/dist/cli/run/schema.d.ts +28 -0
- package/dist/cli/run/schema.d.ts.map +1 -0
- package/dist/cli/run/schema.js +511 -0
- package/dist/cli/run/schema.js.map +1 -0
- package/dist/cli/run.d.ts +6 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +123 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/stack-config.d.ts +12 -0
- package/dist/cli/stack-config.d.ts.map +1 -0
- package/dist/cli/stack-config.js +146 -0
- package/dist/cli/stack-config.js.map +1 -0
- package/dist/cli/types.d.ts +169 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +2 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/update.d.ts +3 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/cli/update.js +50 -0
- package/dist/cli/update.js.map +1 -0
- package/package.json +48 -0
- package/src/cli/adapters/claude-code.ts +287 -0
- package/src/cli/adapters/cursor.ts +377 -0
- package/src/cli/adapters/vscode.ts +168 -0
- package/src/cli/copy.ts +79 -0
- package/src/cli/dashboard.ts +225 -0
- package/src/cli/diff.ts +44 -0
- package/src/cli/eject.ts +39 -0
- package/src/cli/init.ts +120 -0
- package/src/cli/manifest.ts +45 -0
- package/src/cli/mcp.ts +49 -0
- package/src/cli/prompt.ts +115 -0
- package/src/cli/run/adapters/claude-code.ts +95 -0
- package/src/cli/run/adapters/copilot.ts +97 -0
- package/src/cli/run/adapters/cursor.ts +94 -0
- package/src/cli/run/adapters/index.ts +40 -0
- package/src/cli/run/executor.ts +292 -0
- package/src/cli/run/reporter.ts +129 -0
- package/src/cli/run/schema.ts +595 -0
- package/src/cli/run.ts +137 -0
- package/src/cli/stack-config.ts +180 -0
- package/src/cli/types.ts +207 -0
- package/src/cli/update.ts +75 -0
- package/src/dashboard/astro.config.mjs +6 -0
- package/src/dashboard/package-lock.json +5455 -0
- package/src/dashboard/package.json +14 -0
- package/src/dashboard/public/data/delegations.ndjson +35 -0
- package/src/dashboard/public/data/panels.ndjson +13 -0
- package/src/dashboard/public/data/sessions.ndjson +50 -0
- package/src/dashboard/public/icon-192.png +0 -0
- package/src/dashboard/scripts/generate-seed-data.ts +355 -0
- package/src/dashboard/src/layouts/Layout.astro +25 -0
- package/src/dashboard/src/pages/index.astro +1070 -0
- package/src/dashboard/src/styles/dashboard.css +1078 -0
- package/src/dashboard/tsconfig.json +6 -0
- package/src/orchestrator/agent-workflows/README.md +22 -0
- package/src/orchestrator/agent-workflows/bug-fix.md +128 -0
- package/src/orchestrator/agent-workflows/data-pipeline.md +145 -0
- package/src/orchestrator/agent-workflows/database-migration.md +159 -0
- package/src/orchestrator/agent-workflows/feature-implementation.md +223 -0
- package/src/orchestrator/agent-workflows/performance-optimization.md +125 -0
- package/src/orchestrator/agent-workflows/refactoring.md +142 -0
- package/src/orchestrator/agent-workflows/schema-changes.md +164 -0
- package/src/orchestrator/agent-workflows/security-audit.md +148 -0
- package/src/orchestrator/agent-workflows/shared-delivery-phase.md +33 -0
- package/src/orchestrator/agents/api-designer.agent.md +68 -0
- package/src/orchestrator/agents/architect.agent.md +129 -0
- package/src/orchestrator/agents/content-engineer.agent.md +57 -0
- package/src/orchestrator/agents/copywriter.agent.md +95 -0
- package/src/orchestrator/agents/data-expert.agent.md +63 -0
- package/src/orchestrator/agents/database-engineer.agent.md +62 -0
- package/src/orchestrator/agents/developer.agent.md +66 -0
- package/src/orchestrator/agents/devops-expert.agent.md +57 -0
- package/src/orchestrator/agents/documentation-writer.agent.md +60 -0
- package/src/orchestrator/agents/performance-expert.agent.md +58 -0
- package/src/orchestrator/agents/release-manager.agent.md +72 -0
- package/src/orchestrator/agents/researcher.agent.md +145 -0
- package/src/orchestrator/agents/reviewer.agent.md +62 -0
- package/src/orchestrator/agents/security-expert.agent.md +64 -0
- package/src/orchestrator/agents/seo-specialist.agent.md +67 -0
- package/src/orchestrator/agents/team-lead.agent.md +644 -0
- package/src/orchestrator/agents/testing-expert.agent.md +85 -0
- package/src/orchestrator/agents/ui-ux-expert.agent.md +63 -0
- package/src/orchestrator/copilot-instructions.md +3 -0
- package/src/orchestrator/customizations/AGENT-EXPERTISE.md +325 -0
- package/src/orchestrator/customizations/AGENT-FAILURES.md +69 -0
- package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +58 -0
- package/src/orchestrator/customizations/DISPUTES.md +162 -0
- package/src/orchestrator/customizations/KNOWLEDGE-GRAPH.md +10 -0
- package/src/orchestrator/customizations/LESSONS-LEARNED.md +70 -0
- package/src/orchestrator/customizations/README.md +59 -0
- package/src/orchestrator/customizations/agents/agent-registry.md +46 -0
- package/src/orchestrator/customizations/agents/skill-matrix.md +142 -0
- package/src/orchestrator/customizations/logs/README.md +181 -0
- package/src/orchestrator/customizations/logs/delegations.ndjson +1 -0
- package/src/orchestrator/customizations/logs/panels.ndjson +1 -0
- package/src/orchestrator/customizations/logs/sessions.ndjson +1 -0
- package/src/orchestrator/customizations/project/docs-structure.md +23 -0
- package/src/orchestrator/customizations/project/tracker-config.md +45 -0
- package/src/orchestrator/customizations/project.instructions.md +64 -0
- package/src/orchestrator/customizations/stack/api-config.md +37 -0
- package/src/orchestrator/customizations/stack/cms-config.md +26 -0
- package/src/orchestrator/customizations/stack/data-pipeline-config.md +41 -0
- package/src/orchestrator/customizations/stack/database-config.md +44 -0
- package/src/orchestrator/customizations/stack/deployment-config.md +45 -0
- package/src/orchestrator/customizations/stack/testing-config.md +56 -0
- package/src/orchestrator/instructions/ai-optimization.instructions.md +143 -0
- package/src/orchestrator/instructions/general.instructions.md +194 -0
- package/src/orchestrator/mcp.json +55 -0
- package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +235 -0
- package/src/orchestrator/prompts/brainstorm.prompt.md +115 -0
- package/src/orchestrator/prompts/bug-fix.prompt.md +141 -0
- package/src/orchestrator/prompts/create-skill.prompt.md +103 -0
- package/src/orchestrator/prompts/generate-task-spec.prompt.md +154 -0
- package/src/orchestrator/prompts/implement-feature.prompt.md +124 -0
- package/src/orchestrator/prompts/metrics-report.prompt.md +142 -0
- package/src/orchestrator/prompts/quick-refinement.prompt.md +137 -0
- package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +100 -0
- package/src/orchestrator/skills/accessibility-standards/SKILL.md +164 -0
- package/src/orchestrator/skills/agent-hooks/SKILL.md +147 -0
- package/src/orchestrator/skills/agent-memory/SKILL.md +144 -0
- package/src/orchestrator/skills/api-patterns/SKILL.md +106 -0
- package/src/orchestrator/skills/browser-testing/SKILL.md +203 -0
- package/src/orchestrator/skills/code-commenting/SKILL.md +133 -0
- package/src/orchestrator/skills/contentful-cms/SKILL.md +43 -0
- package/src/orchestrator/skills/context-map/SKILL.md +135 -0
- package/src/orchestrator/skills/convex-database/SKILL.md +80 -0
- package/src/orchestrator/skills/data-engineering/SKILL.md +99 -0
- package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +49 -0
- package/src/orchestrator/skills/documentation-standards/SKILL.md +85 -0
- package/src/orchestrator/skills/fast-review/SKILL.md +327 -0
- package/src/orchestrator/skills/frontend-design/SKILL.md +42 -0
- package/src/orchestrator/skills/jira-management/SKILL.md +168 -0
- package/src/orchestrator/skills/memory-merger/SKILL.md +123 -0
- package/src/orchestrator/skills/nextjs-patterns/SKILL.md +75 -0
- package/src/orchestrator/skills/nx-workspace/SKILL.md +192 -0
- package/src/orchestrator/skills/panel-majority-vote/SKILL.md +184 -0
- package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +38 -0
- package/src/orchestrator/skills/performance-optimization/SKILL.md +101 -0
- package/src/orchestrator/skills/react-development/SKILL.md +117 -0
- package/src/orchestrator/skills/sanity-cms/SKILL.md +18 -0
- package/src/orchestrator/skills/security-hardening/SKILL.md +118 -0
- package/src/orchestrator/skills/self-improvement/SKILL.md +137 -0
- package/src/orchestrator/skills/seo-patterns/SKILL.md +40 -0
- package/src/orchestrator/skills/session-checkpoints/SKILL.md +205 -0
- package/src/orchestrator/skills/slack-notifications/SKILL.md +211 -0
- package/src/orchestrator/skills/strapi-cms/SKILL.md +43 -0
- package/src/orchestrator/skills/supabase-database/SKILL.md +24 -0
- package/src/orchestrator/skills/task-management/SKILL.md +143 -0
- package/src/orchestrator/skills/team-lead-reference/SKILL.md +317 -0
- package/src/orchestrator/skills/teams-notifications/SKILL.md +249 -0
- package/src/orchestrator/skills/testing-workflow/SKILL.md +134 -0
- 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
|
+
}
|