opencastle 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/dist/cli/adapters/claude-code.d.ts.map +1 -1
- package/dist/cli/adapters/claude-code.js +30 -3
- package/dist/cli/adapters/claude-code.js.map +1 -1
- package/dist/cli/adapters/cursor.d.ts.map +1 -1
- package/dist/cli/adapters/cursor.js +27 -3
- package/dist/cli/adapters/cursor.js.map +1 -1
- package/dist/cli/adapters/opencode.d.ts +20 -0
- package/dist/cli/adapters/opencode.d.ts.map +1 -0
- package/dist/cli/adapters/opencode.js +265 -0
- package/dist/cli/adapters/opencode.js.map +1 -0
- package/dist/cli/adapters/vscode.d.ts.map +1 -1
- package/dist/cli/adapters/vscode.js +37 -6
- package/dist/cli/adapters/vscode.js.map +1 -1
- package/dist/cli/copy.d.ts +12 -0
- package/dist/cli/copy.d.ts.map +1 -1
- package/dist/cli/copy.js +27 -0
- package/dist/cli/copy.js.map +1 -1
- package/dist/cli/detect.d.ts +1 -1
- package/dist/cli/detect.js +21 -15
- package/dist/cli/detect.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +143 -94
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/manifest.d.ts +1 -1
- package/dist/cli/manifest.d.ts.map +1 -1
- package/dist/cli/manifest.js +2 -1
- package/dist/cli/manifest.js.map +1 -1
- package/dist/cli/mcp.d.ts +6 -6
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +104 -33
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/prompt.d.ts +19 -0
- package/dist/cli/prompt.d.ts.map +1 -1
- package/dist/cli/prompt.js +143 -0
- package/dist/cli/prompt.js.map +1 -1
- package/dist/cli/stack-config.d.ts +23 -0
- package/dist/cli/stack-config.d.ts.map +1 -1
- package/dist/cli/stack-config.js +128 -124
- package/dist/cli/stack-config.js.map +1 -1
- package/dist/cli/types.d.ts +26 -9
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli/types.js +26 -1
- package/dist/cli/types.js.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +60 -19
- package/dist/cli/update.js.map +1 -1
- package/dist/orchestrator/plugins/chrome-devtools/config.d.ts +3 -0
- package/dist/orchestrator/plugins/chrome-devtools/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/chrome-devtools/config.js +28 -0
- package/dist/orchestrator/plugins/chrome-devtools/config.js.map +1 -0
- package/dist/orchestrator/plugins/contentful/config.d.ts +3 -0
- package/dist/orchestrator/plugins/contentful/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/contentful/config.js +48 -0
- package/dist/orchestrator/plugins/contentful/config.js.map +1 -0
- package/dist/orchestrator/plugins/convex/config.d.ts +3 -0
- package/dist/orchestrator/plugins/convex/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/convex/config.js +32 -0
- package/dist/orchestrator/plugins/convex/config.js.map +1 -0
- package/dist/orchestrator/plugins/index.d.ts +28 -0
- package/dist/orchestrator/plugins/index.d.ts.map +1 -0
- package/dist/orchestrator/plugins/index.js +63 -0
- package/dist/orchestrator/plugins/index.js.map +1 -0
- package/dist/orchestrator/plugins/jira/config.d.ts +3 -0
- package/dist/orchestrator/plugins/jira/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/jira/config.js +29 -0
- package/dist/orchestrator/plugins/jira/config.js.map +1 -0
- package/dist/orchestrator/plugins/linear/config.d.ts +3 -0
- package/dist/orchestrator/plugins/linear/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/linear/config.js +33 -0
- package/dist/orchestrator/plugins/linear/config.js.map +1 -0
- package/dist/orchestrator/plugins/nx/config.d.ts +3 -0
- package/dist/orchestrator/plugins/nx/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/nx/config.js +28 -0
- package/dist/orchestrator/plugins/nx/config.js.map +1 -0
- package/dist/orchestrator/plugins/sanity/config.d.ts +3 -0
- package/dist/orchestrator/plugins/sanity/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/sanity/config.js +43 -0
- package/dist/orchestrator/plugins/sanity/config.js.map +1 -0
- package/dist/orchestrator/plugins/slack/config.d.ts +3 -0
- package/dist/orchestrator/plugins/slack/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/slack/config.js +34 -0
- package/dist/orchestrator/plugins/slack/config.js.map +1 -0
- package/dist/orchestrator/plugins/strapi/config.d.ts +3 -0
- package/dist/orchestrator/plugins/strapi/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/strapi/config.js +40 -0
- package/dist/orchestrator/plugins/strapi/config.js.map +1 -0
- package/dist/orchestrator/plugins/supabase/config.d.ts +3 -0
- package/dist/orchestrator/plugins/supabase/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/supabase/config.js +33 -0
- package/dist/orchestrator/plugins/supabase/config.js.map +1 -0
- package/dist/orchestrator/plugins/teams/config.d.ts +3 -0
- package/dist/orchestrator/plugins/teams/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/teams/config.js +43 -0
- package/dist/orchestrator/plugins/teams/config.js.map +1 -0
- package/dist/orchestrator/plugins/types.d.ts +61 -0
- package/dist/orchestrator/plugins/types.d.ts.map +1 -0
- package/dist/orchestrator/plugins/types.js +2 -0
- package/dist/orchestrator/plugins/types.js.map +1 -0
- package/dist/orchestrator/plugins/vercel/config.d.ts +3 -0
- package/dist/orchestrator/plugins/vercel/config.d.ts.map +1 -0
- package/dist/orchestrator/plugins/vercel/config.js +32 -0
- package/dist/orchestrator/plugins/vercel/config.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/adapters/claude-code.ts +36 -4
- package/src/cli/adapters/cursor.ts +42 -4
- package/src/cli/adapters/opencode.ts +320 -0
- package/src/cli/adapters/vscode.ts +40 -8
- package/src/cli/copy.ts +32 -0
- package/src/cli/detect.ts +17 -17
- package/src/cli/init.ts +157 -99
- package/src/cli/manifest.ts +2 -1
- package/src/cli/mcp.ts +129 -50
- package/src/cli/prompt.ts +176 -0
- package/src/cli/stack-config.ts +174 -145
- package/src/cli/types.ts +39 -8
- package/src/cli/update.ts +71 -20
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/orchestrator/agent-workflows/README.md +1 -1
- package/src/orchestrator/agent-workflows/bug-fix.md +12 -12
- package/src/orchestrator/agent-workflows/data-pipeline.md +21 -20
- package/src/orchestrator/agent-workflows/database-migration.md +11 -11
- package/src/orchestrator/agent-workflows/feature-implementation.md +19 -12
- package/src/orchestrator/agent-workflows/performance-optimization.md +6 -6
- package/src/orchestrator/agent-workflows/refactoring.md +10 -10
- package/src/orchestrator/agent-workflows/schema-changes.md +8 -8
- package/src/orchestrator/agent-workflows/security-audit.md +12 -12
- package/src/orchestrator/agent-workflows/shared-delivery-phase.md +5 -5
- package/src/orchestrator/agents/api-designer.agent.md +1 -1
- package/src/orchestrator/agents/architect.agent.md +2 -2
- package/src/orchestrator/agents/content-engineer.agent.md +3 -3
- package/src/orchestrator/agents/copywriter.agent.md +2 -2
- package/src/orchestrator/agents/data-expert.agent.md +6 -6
- package/src/orchestrator/agents/database-engineer.agent.md +3 -3
- package/src/orchestrator/agents/developer.agent.md +4 -4
- package/src/orchestrator/agents/devops-expert.agent.md +5 -5
- package/src/orchestrator/agents/documentation-writer.agent.md +1 -1
- package/src/orchestrator/agents/performance-expert.agent.md +2 -2
- package/src/orchestrator/agents/release-manager.agent.md +4 -4
- package/src/orchestrator/agents/researcher.agent.md +3 -3
- package/src/orchestrator/agents/reviewer.agent.md +1 -1
- package/src/orchestrator/agents/security-expert.agent.md +4 -4
- package/src/orchestrator/agents/seo-specialist.agent.md +2 -2
- package/src/orchestrator/agents/team-lead.agent.md +56 -38
- package/src/orchestrator/agents/testing-expert.agent.md +5 -5
- package/src/orchestrator/agents/ui-ux-expert.agent.md +6 -6
- package/src/orchestrator/copilot-instructions.md +1 -1
- package/src/orchestrator/customizations/AGENT-FAILURES.md +1 -1
- package/src/orchestrator/customizations/AGENT-PERFORMANCE.md +12 -12
- package/src/orchestrator/customizations/DISPUTES.md +5 -5
- package/src/orchestrator/customizations/KNOWN-ISSUES.md +30 -0
- package/src/orchestrator/customizations/LESSONS-LEARNED.md +7 -7
- package/src/orchestrator/customizations/README.md +5 -2
- package/src/orchestrator/customizations/agents/agent-registry.md +1 -1
- package/src/orchestrator/customizations/agents/skill-matrix.md +12 -7
- package/src/orchestrator/customizations/logs/README.md +1 -1
- package/src/orchestrator/customizations/project/decisions.md +31 -0
- package/src/orchestrator/customizations/project/docs-structure.md +16 -5
- package/src/orchestrator/customizations/project/roadmap.md +24 -0
- package/src/orchestrator/customizations/project/tracker-config.md +1 -1
- package/src/orchestrator/customizations/stack/cms-config.md +1 -1
- package/src/orchestrator/customizations/stack/notifications-config.md +1 -1
- package/src/orchestrator/instructions/ai-optimization.instructions.md +2 -2
- package/src/orchestrator/instructions/general.instructions.md +102 -40
- package/src/orchestrator/{skills/browser-testing → plugins/chrome-devtools}/SKILL.md +1 -1
- package/src/orchestrator/plugins/chrome-devtools/config.ts +29 -0
- package/src/orchestrator/{skills/contentful-cms → plugins/contentful}/SKILL.md +1 -1
- package/src/orchestrator/plugins/contentful/config.ts +49 -0
- package/src/orchestrator/{skills/convex-database → plugins/convex}/SKILL.md +1 -1
- package/src/orchestrator/plugins/convex/config.ts +33 -0
- package/src/orchestrator/plugins/index.ts +85 -0
- package/src/orchestrator/{skills/jira-management → plugins/jira}/SKILL.md +3 -3
- package/src/orchestrator/plugins/jira/config.ts +30 -0
- package/src/orchestrator/{skills/task-management → plugins/linear}/SKILL.md +3 -3
- package/src/orchestrator/plugins/linear/config.ts +34 -0
- package/src/orchestrator/{skills/nx-workspace → plugins/nx}/SKILL.md +1 -1
- package/src/orchestrator/plugins/nx/config.ts +29 -0
- package/src/orchestrator/{skills/sanity-cms → plugins/sanity}/SKILL.md +1 -1
- package/src/orchestrator/plugins/sanity/config.ts +44 -0
- package/src/orchestrator/{skills/slack-notifications → plugins/slack}/SKILL.md +2 -2
- package/src/orchestrator/plugins/slack/config.ts +35 -0
- package/src/orchestrator/{skills/strapi-cms → plugins/strapi}/SKILL.md +1 -1
- package/src/orchestrator/plugins/strapi/config.ts +41 -0
- package/src/orchestrator/{skills/supabase-database → plugins/supabase}/SKILL.md +1 -1
- package/src/orchestrator/plugins/supabase/config.ts +34 -0
- package/src/orchestrator/{skills/teams-notifications → plugins/teams}/SKILL.md +2 -2
- package/src/orchestrator/plugins/teams/config.ts +44 -0
- package/src/orchestrator/plugins/types.ts +79 -0
- package/src/orchestrator/plugins/vercel/config.ts +33 -0
- package/src/orchestrator/prompts/bootstrap-customizations.prompt.md +8 -8
- package/src/orchestrator/prompts/brainstorm.prompt.md +3 -3
- package/src/orchestrator/prompts/bug-fix.prompt.md +27 -22
- package/src/orchestrator/prompts/create-skill.prompt.md +50 -32
- package/src/orchestrator/prompts/generate-task-spec.prompt.md +3 -3
- package/src/orchestrator/prompts/implement-feature.prompt.md +34 -29
- package/src/orchestrator/prompts/metrics-report.prompt.md +11 -11
- package/src/orchestrator/prompts/quick-refinement.prompt.md +23 -19
- package/src/orchestrator/prompts/resolve-pr-comments.prompt.md +19 -5
- package/src/orchestrator/skills/accessibility-standards/SKILL.md +1 -1
- package/src/orchestrator/skills/agent-hooks/SKILL.md +27 -18
- package/src/orchestrator/skills/agent-memory/SKILL.md +7 -7
- package/src/orchestrator/skills/api-patterns/SKILL.md +6 -6
- package/src/orchestrator/skills/code-commenting/SKILL.md +1 -1
- package/src/orchestrator/skills/context-map/SKILL.md +4 -4
- package/src/orchestrator/skills/data-engineering/SKILL.md +7 -4
- package/src/orchestrator/skills/deployment-infrastructure/SKILL.md +2 -2
- package/src/orchestrator/skills/documentation-standards/SKILL.md +1 -1
- package/src/orchestrator/skills/fast-review/SKILL.md +18 -7
- package/src/orchestrator/skills/frontend-design/SKILL.md +1 -1
- package/src/orchestrator/skills/memory-merger/SKILL.md +8 -8
- package/src/orchestrator/skills/nextjs-patterns/SKILL.md +1 -1
- package/src/orchestrator/skills/panel-majority-vote/SKILL.md +2 -2
- package/src/orchestrator/skills/panel-majority-vote/panel-report.template.md +1 -1
- package/src/orchestrator/skills/performance-optimization/SKILL.md +1 -1
- package/src/orchestrator/skills/react-development/SKILL.md +3 -3
- package/src/orchestrator/skills/security-hardening/SKILL.md +27 -27
- package/src/orchestrator/skills/self-improvement/SKILL.md +14 -13
- package/src/orchestrator/skills/seo-patterns/SKILL.md +1 -1
- package/src/orchestrator/skills/session-checkpoints/SKILL.md +19 -19
- package/src/orchestrator/skills/team-lead-reference/SKILL.md +9 -9
- package/src/orchestrator/skills/testing-workflow/SKILL.md +13 -13
- package/src/orchestrator/skills/validation-gates/SKILL.md +157 -27
- package/src/orchestrator/mcp.json +0 -69
package/src/cli/prompt.ts
CHANGED
|
@@ -14,6 +14,19 @@ function moveUp(n: number): string {
|
|
|
14
14
|
return n > 0 ? `${CSI}${n}A` : '';
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// ── Color helpers ─────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/** ANSI color helpers for CLI output. */
|
|
20
|
+
export const c = {
|
|
21
|
+
cyan: (s: string) => `\x1B[36m${s}\x1B[0m`,
|
|
22
|
+
green: (s: string) => `\x1B[32m${s}\x1B[0m`,
|
|
23
|
+
yellow: (s: string) => `\x1B[33m${s}\x1B[0m`,
|
|
24
|
+
red: (s: string) => `\x1B[31m${s}\x1B[0m`,
|
|
25
|
+
bold: (s: string) => `\x1B[1m${s}\x1B[0m`,
|
|
26
|
+
dim: (s: string) => `\x1B[2m${s}\x1B[0m`,
|
|
27
|
+
magenta: (s: string) => `\x1B[35m${s}\x1B[0m`,
|
|
28
|
+
};
|
|
29
|
+
|
|
17
30
|
// ── Line-buffered readline ────────────────────────────────────────
|
|
18
31
|
// readline.question() drops lines that arrived between calls because
|
|
19
32
|
// it only listens for the NEXT 'line' event. When piped input
|
|
@@ -236,3 +249,166 @@ export async function confirm(
|
|
|
236
249
|
if (!answer.trim()) return defaultYes;
|
|
237
250
|
return answer.trim().toLowerCase().startsWith('y');
|
|
238
251
|
}
|
|
252
|
+
|
|
253
|
+
// ── Multiselect ───────────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Interactive multi-choice selection prompt.
|
|
257
|
+
*
|
|
258
|
+
* TTY mode: arrow-key navigation (↑/↓), Space to toggle, Enter to confirm.
|
|
259
|
+
* Piped mode: falls back to comma-separated number input.
|
|
260
|
+
*
|
|
261
|
+
* Returns an array of selected values (possibly empty).
|
|
262
|
+
*/
|
|
263
|
+
export async function multiselect(
|
|
264
|
+
message: string,
|
|
265
|
+
options: SelectOption[]
|
|
266
|
+
): Promise<string[]> {
|
|
267
|
+
if (stdin.isTTY) {
|
|
268
|
+
return multiselectInteractive(message, options);
|
|
269
|
+
}
|
|
270
|
+
return multiselectNumbered(message, options);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ── Arrow-key multiselect (TTY) ───────────────────────────────────
|
|
274
|
+
|
|
275
|
+
function renderMultiselectOptions(
|
|
276
|
+
options: SelectOption[],
|
|
277
|
+
cursor: number,
|
|
278
|
+
selected: Set<number>,
|
|
279
|
+
initial: boolean
|
|
280
|
+
): void {
|
|
281
|
+
if (!initial) {
|
|
282
|
+
stdout.write(moveUp(options.length));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
for (let i = 0; i < options.length; i++) {
|
|
286
|
+
const active = i === cursor;
|
|
287
|
+
const checked = selected.has(i);
|
|
288
|
+
const checkbox = checked ? `\x1B[32m✔\x1B[0m` : ' ';
|
|
289
|
+
const marker = active ? '❯' : ' ';
|
|
290
|
+
const hint = options[i].hint ? ` ${c.dim('—')} ${c.dim(options[i].hint!)}` : '';
|
|
291
|
+
const label = active
|
|
292
|
+
? `\x1B[36m${options[i].label}\x1B[0m${hint}`
|
|
293
|
+
: `${options[i].label}${hint}`;
|
|
294
|
+
stdout.write(`${ERASE_LINE}\r ${marker} [${checkbox}] ${label}\n`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function multiselectInteractive(
|
|
299
|
+
message: string,
|
|
300
|
+
options: SelectOption[]
|
|
301
|
+
): Promise<string[]> {
|
|
302
|
+
return new Promise<string[]>((resolve) => {
|
|
303
|
+
let cursor = 0;
|
|
304
|
+
const selected = new Set<number>();
|
|
305
|
+
// Pre-select options marked as selected
|
|
306
|
+
for (let i = 0; i < options.length; i++) {
|
|
307
|
+
if (options[i].selected) selected.add(i);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (_rl) _rl.pause();
|
|
311
|
+
|
|
312
|
+
stdout.write(`\n ${message} ${c.dim('(↑/↓ navigate, Space toggle, Enter confirm)')}\n\n`);
|
|
313
|
+
stdout.write(HIDE_CURSOR);
|
|
314
|
+
renderMultiselectOptions(options, cursor, selected, true);
|
|
315
|
+
|
|
316
|
+
stdin.setRawMode(true);
|
|
317
|
+
stdin.resume();
|
|
318
|
+
|
|
319
|
+
const onData = (data: Buffer): void => {
|
|
320
|
+
const key = data.toString();
|
|
321
|
+
|
|
322
|
+
// Arrow up or k
|
|
323
|
+
if (key === `${ESC}[A` || key === 'k') {
|
|
324
|
+
cursor = (cursor - 1 + options.length) % options.length;
|
|
325
|
+
renderMultiselectOptions(options, cursor, selected, false);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Arrow down or j
|
|
330
|
+
if (key === `${ESC}[B` || key === 'j') {
|
|
331
|
+
cursor = (cursor + 1) % options.length;
|
|
332
|
+
renderMultiselectOptions(options, cursor, selected, false);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Space — toggle selection
|
|
337
|
+
if (key === ' ') {
|
|
338
|
+
if (selected.has(cursor)) {
|
|
339
|
+
selected.delete(cursor);
|
|
340
|
+
} else {
|
|
341
|
+
selected.add(cursor);
|
|
342
|
+
}
|
|
343
|
+
renderMultiselectOptions(options, cursor, selected, false);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Enter — confirm
|
|
348
|
+
if (key === '\r' || key === '\n') {
|
|
349
|
+
cleanup();
|
|
350
|
+
// Final render
|
|
351
|
+
stdout.write(moveUp(options.length));
|
|
352
|
+
for (let i = 0; i < options.length; i++) {
|
|
353
|
+
const checked = selected.has(i);
|
|
354
|
+
const hint = options[i].hint ? ` ${c.dim('—')} ${c.dim(options[i].hint!)}` : '';
|
|
355
|
+
const checkbox = checked ? `\x1B[32m✔\x1B[0m` : ' ';
|
|
356
|
+
const label = checked
|
|
357
|
+
? `\x1B[36m${options[i].label}\x1B[0m${hint}`
|
|
358
|
+
: `\x1B[2m${options[i].label}${hint}\x1B[0m`;
|
|
359
|
+
stdout.write(`${ERASE_LINE}\r [${checkbox}] ${label}\n`);
|
|
360
|
+
}
|
|
361
|
+
stdout.write('\n');
|
|
362
|
+
resolve(Array.from(selected).sort().map(i => options[i].value));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Ctrl+C
|
|
367
|
+
if (key === '\x03') {
|
|
368
|
+
cleanup();
|
|
369
|
+
stdout.write('\n');
|
|
370
|
+
process.exit(130);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
function cleanup(): void {
|
|
375
|
+
stdin.removeListener('data', onData);
|
|
376
|
+
stdin.setRawMode(false);
|
|
377
|
+
stdout.write(SHOW_CURSOR);
|
|
378
|
+
if (_rl) _rl.resume();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
stdin.on('data', onData);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ── Number-based multiselect (piped / non-TTY) ────────────────────
|
|
386
|
+
|
|
387
|
+
async function multiselectNumbered(
|
|
388
|
+
message: string,
|
|
389
|
+
options: SelectOption[]
|
|
390
|
+
): Promise<string[]> {
|
|
391
|
+
console.log(`\n ${message}\n`);
|
|
392
|
+
options.forEach((opt, i) => {
|
|
393
|
+
const hint = opt.hint ? ` — ${opt.hint}` : '';
|
|
394
|
+
console.log(` ${i + 1}) ${opt.label}${hint}`);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const preselected = options
|
|
398
|
+
.map((opt, i) => (opt.selected ? i + 1 : null))
|
|
399
|
+
.filter((n): n is number => n !== null);
|
|
400
|
+
const defaultHint = preselected.length > 0 ? preselected.join(',') : 'none';
|
|
401
|
+
const answer = await nextLine(`\n Select [comma-separated, e.g. 1,3] or Enter for ${defaultHint}: `);
|
|
402
|
+
if (!answer.trim()) {
|
|
403
|
+
return preselected.map(n => options[n - 1].value);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const nums = answer.split(',').map(s => parseInt(s.trim(), 10));
|
|
407
|
+
const result: string[] = [];
|
|
408
|
+
for (const num of nums) {
|
|
409
|
+
if (num >= 1 && num <= options.length) {
|
|
410
|
+
result.push(options[num - 1].value);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return result;
|
|
414
|
+
}
|
package/src/cli/stack-config.ts
CHANGED
|
@@ -1,111 +1,39 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
linear: { tech: 'Linear', skill: 'task-management' },
|
|
21
|
-
jira: { tech: 'Jira', skill: 'jira-management' },
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
/** Display name for each notifications choice */
|
|
25
|
-
const NOTIF_LABELS: Record<Exclude<NotifChoice, 'none'>, { tech: string; skill: string }> = {
|
|
26
|
-
slack: { tech: 'Slack', skill: 'slack-notifications' },
|
|
27
|
-
teams: { tech: 'Teams', skill: 'teams-notifications' },
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
// ── Exclusion / inclusion maps ────────────────────────────────
|
|
31
|
-
|
|
32
|
-
/** Skills to EXCLUDE based on CMS choice */
|
|
33
|
-
const CMS_SKILL_MAP: Record<CmsChoice, string[]> = {
|
|
34
|
-
sanity: ['contentful-cms', 'strapi-cms'],
|
|
35
|
-
contentful: ['sanity-cms', 'strapi-cms'],
|
|
36
|
-
strapi: ['sanity-cms', 'contentful-cms'],
|
|
37
|
-
none: ['sanity-cms', 'contentful-cms', 'strapi-cms'],
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
/** Skills to EXCLUDE based on DB choice */
|
|
41
|
-
const DB_SKILL_MAP: Record<DbChoice, string[]> = {
|
|
42
|
-
supabase: ['convex-database'],
|
|
43
|
-
convex: ['supabase-database'],
|
|
44
|
-
none: ['supabase-database', 'convex-database'],
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/** Skills to EXCLUDE based on PM choice */
|
|
48
|
-
const PM_SKILL_MAP: Record<PmChoice, string[]> = {
|
|
49
|
-
linear: ['jira-management'],
|
|
50
|
-
jira: ['task-management'],
|
|
51
|
-
none: ['task-management', 'jira-management'],
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
/** Agents to EXCLUDE based on CMS choice */
|
|
55
|
-
const CMS_AGENT_EXCLUSIONS: Record<CmsChoice, string[]> = {
|
|
56
|
-
sanity: [],
|
|
57
|
-
contentful: [],
|
|
58
|
-
strapi: [],
|
|
59
|
-
none: ['content-engineer.agent.md'],
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
/** Agents to EXCLUDE based on DB choice */
|
|
63
|
-
const DB_AGENT_EXCLUSIONS: Record<DbChoice, string[]> = {
|
|
64
|
-
supabase: [],
|
|
65
|
-
convex: [],
|
|
66
|
-
none: ['database-engineer.agent.md'],
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/** MCP server keys to INCLUDE based on CMS choice */
|
|
70
|
-
const CMS_MCP_MAP: Record<CmsChoice, string[]> = {
|
|
71
|
-
sanity: ['Sanity'],
|
|
72
|
-
contentful: ['Contentful'],
|
|
73
|
-
strapi: ['Strapi'],
|
|
74
|
-
none: [],
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
/** MCP server keys to INCLUDE based on DB choice */
|
|
78
|
-
const DB_MCP_MAP: Record<DbChoice, string[]> = {
|
|
79
|
-
supabase: ['Supabase'],
|
|
80
|
-
convex: ['Convex'],
|
|
81
|
-
none: [],
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
/** MCP server keys to INCLUDE based on PM choice */
|
|
85
|
-
const PM_MCP_MAP: Record<PmChoice, string[]> = {
|
|
86
|
-
linear: ['Linear'],
|
|
87
|
-
jira: ['Jira'],
|
|
88
|
-
none: [],
|
|
89
|
-
};
|
|
1
|
+
import type { TechTool, TeamTool, StackConfig, CopyDirOptions, RepoInfo } from './types.js';
|
|
2
|
+
import {
|
|
3
|
+
PLUGINS,
|
|
4
|
+
TECH_PLUGINS,
|
|
5
|
+
TEAM_PLUGINS,
|
|
6
|
+
CMS_PLUGINS,
|
|
7
|
+
DB_PLUGINS,
|
|
8
|
+
ALL_PLUGIN_SKILL_NAMES,
|
|
9
|
+
getSelectedSkillNames,
|
|
10
|
+
} from '../orchestrator/plugins/index.js';
|
|
11
|
+
import type { PluginConfig } from '../orchestrator/plugins/types.js';
|
|
12
|
+
|
|
13
|
+
// ── Tool registries (derived from plugins) ────────────────────
|
|
14
|
+
|
|
15
|
+
interface ToolInfo {
|
|
16
|
+
tech: string;
|
|
17
|
+
skill: string | null;
|
|
18
|
+
mcpServer: string | null;
|
|
19
|
+
}
|
|
90
20
|
|
|
91
|
-
/**
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
none: ['slack-notifications', 'teams-notifications'],
|
|
96
|
-
};
|
|
21
|
+
/** All tech-tool metadata — derived from plugin configs. */
|
|
22
|
+
const TECH_TOOL_INFO: Record<TechTool, ToolInfo> = Object.fromEntries(
|
|
23
|
+
TECH_PLUGINS.map((p) => [p.id, { tech: p.name, skill: p.skillName, mcpServer: p.mcpServerKey }])
|
|
24
|
+
) as Record<TechTool, ToolInfo>;
|
|
97
25
|
|
|
98
|
-
/**
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
none: [],
|
|
103
|
-
};
|
|
26
|
+
/** All team-tool metadata — derived from plugin configs. */
|
|
27
|
+
const TEAM_TOOL_INFO: Record<TeamTool, ToolInfo> = Object.fromEntries(
|
|
28
|
+
TEAM_PLUGINS.map((p) => [p.id, { tech: p.name, skill: p.skillName, mcpServer: p.mcpServerKey }])
|
|
29
|
+
) as Record<TeamTool, ToolInfo>;
|
|
104
30
|
|
|
105
|
-
/**
|
|
106
|
-
const
|
|
31
|
+
/** CMS-related tech tools. */
|
|
32
|
+
const CMS_TOOLS: readonly TechTool[] = CMS_PLUGINS.map((p) => p.id) as TechTool[];
|
|
33
|
+
/** Database-related tech tools. */
|
|
34
|
+
const DB_TOOLS: readonly TechTool[] = DB_PLUGINS.map((p) => p.id) as TechTool[];
|
|
107
35
|
|
|
108
|
-
/** MCP servers included
|
|
36
|
+
/** MCP servers auto-included when detected in the repo. */
|
|
109
37
|
const DETECTED_MCP_MAP: Record<string, string> = {
|
|
110
38
|
vercel: 'Vercel',
|
|
111
39
|
};
|
|
@@ -123,41 +51,64 @@ export interface McpEnvRequirement {
|
|
|
123
51
|
|
|
124
52
|
/**
|
|
125
53
|
* Registry of MCP servers that require API keys via environment variables.
|
|
126
|
-
*
|
|
127
|
-
* HTTP-based servers (Sanity, Slack, Vercel, etc.) handle auth via OAuth.
|
|
54
|
+
* Derived from plugin configs — only plugins with envVars are included.
|
|
128
55
|
*/
|
|
129
|
-
const MCP_ENV_REQUIREMENTS: McpEnvRequirement[] =
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
56
|
+
const MCP_ENV_REQUIREMENTS: McpEnvRequirement[] = Object.values(PLUGINS)
|
|
57
|
+
.filter((p) => p.envVars.length > 0 && p.mcpServerKey)
|
|
58
|
+
.flatMap((p) =>
|
|
59
|
+
p.envVars.map((ev) => ({
|
|
60
|
+
server: p.mcpServerKey!,
|
|
61
|
+
envVar: ev.name,
|
|
62
|
+
hint: ev.hint,
|
|
63
|
+
}))
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// ── Exported helpers ──────────────────────────────────────────
|
|
136
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Skills to EXCLUDE — all tool-specific skills that are NOT selected.
|
|
70
|
+
*/
|
|
137
71
|
export function getExcludedSkills(stack: StackConfig): Set<string> {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
...PM_SKILL_MAP[stack.pm ?? 'none'],
|
|
142
|
-
...NOTIF_SKILL_MAP[stack.notifications ?? 'none'],
|
|
143
|
-
]);
|
|
72
|
+
const selectedIds = [...stack.techTools, ...stack.teamTools] as string[];
|
|
73
|
+
const includedSkills = new Set(getSelectedSkillNames(selectedIds));
|
|
74
|
+
return new Set(ALL_PLUGIN_SKILL_NAMES.filter((s) => !includedSkills.has(s)));
|
|
144
75
|
}
|
|
145
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Plugin IDs to INCLUDE — the user's selected tools.
|
|
79
|
+
*/
|
|
80
|
+
export function getIncludedPluginIds(stack: StackConfig): Set<string> {
|
|
81
|
+
return new Set([...stack.techTools, ...stack.teamTools]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Agents to EXCLUDE — content-engineer if no CMS, database-engineer if no DB.
|
|
86
|
+
*/
|
|
146
87
|
export function getExcludedAgents(stack: StackConfig): Set<string> {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
88
|
+
const excluded = new Set<string>();
|
|
89
|
+
const hasCms = stack.techTools.some((t) => (CMS_TOOLS as readonly string[]).includes(t));
|
|
90
|
+
const hasDb = stack.techTools.some((t) => (DB_TOOLS as readonly string[]).includes(t));
|
|
91
|
+
|
|
92
|
+
if (!hasCms) excluded.add('content-engineer.agent.md');
|
|
93
|
+
if (!hasDb) excluded.add('database-engineer.agent.md');
|
|
94
|
+
|
|
95
|
+
return excluded;
|
|
151
96
|
}
|
|
152
97
|
|
|
98
|
+
/**
|
|
99
|
+
* MCP servers to INCLUDE — core + selected tools + auto-detected from repo.
|
|
100
|
+
*/
|
|
153
101
|
export function getIncludedMcpServers(stack: StackConfig, repoInfo?: RepoInfo): Set<string> {
|
|
154
|
-
const servers = new Set(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
102
|
+
const servers = new Set<string>();
|
|
103
|
+
|
|
104
|
+
for (const tool of stack.techTools) {
|
|
105
|
+
const server = TECH_TOOL_INFO[tool]?.mcpServer;
|
|
106
|
+
if (server) servers.add(server);
|
|
107
|
+
}
|
|
108
|
+
for (const tool of stack.teamTools) {
|
|
109
|
+
const server = TEAM_TOOL_INFO[tool]?.mcpServer;
|
|
110
|
+
if (server) servers.add(server);
|
|
111
|
+
}
|
|
161
112
|
|
|
162
113
|
// Add servers for detected deployment targets
|
|
163
114
|
for (const dep of repoInfo?.deployment ?? []) {
|
|
@@ -165,6 +116,11 @@ export function getIncludedMcpServers(stack: StackConfig, repoInfo?: RepoInfo):
|
|
|
165
116
|
if (server) servers.add(server);
|
|
166
117
|
}
|
|
167
118
|
|
|
119
|
+
// Auto-detect NX from monorepo info
|
|
120
|
+
if (repoInfo?.monorepo === 'nx' && !stack.techTools.includes('nx')) {
|
|
121
|
+
servers.add('Nx');
|
|
122
|
+
}
|
|
123
|
+
|
|
168
124
|
return servers;
|
|
169
125
|
}
|
|
170
126
|
|
|
@@ -189,7 +145,6 @@ export function getCustomizationsTransform(
|
|
|
189
145
|
stack: StackConfig
|
|
190
146
|
): NonNullable<CopyDirOptions['transform']> {
|
|
191
147
|
return (content: string, srcPath: string) => {
|
|
192
|
-
// Pre-fill skill matrix with CMS and DB bindings
|
|
193
148
|
if (srcPath.endsWith('skill-matrix.md')) {
|
|
194
149
|
return transformSkillMatrix(content, stack);
|
|
195
150
|
}
|
|
@@ -204,23 +159,97 @@ export function getCustomizationsTransform(
|
|
|
204
159
|
function transformSkillMatrix(content: string, stack: StackConfig): string {
|
|
205
160
|
let result = content;
|
|
206
161
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
162
|
+
// Find first selected DB tool
|
|
163
|
+
const db = stack.techTools.find((t) => (DB_TOOLS as readonly string[]).includes(t));
|
|
164
|
+
if (db) {
|
|
165
|
+
const info = TECH_TOOL_INFO[db as TechTool];
|
|
166
|
+
if (info?.skill) {
|
|
167
|
+
result = result.replace(
|
|
168
|
+
/(\| `database`\s*\|)\s*\|(\s*\|)/,
|
|
169
|
+
`$1 ${info.tech} | \`${info.skill}\` $2`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
214
172
|
}
|
|
215
173
|
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
174
|
+
// Find first selected CMS tool
|
|
175
|
+
const cms = stack.techTools.find((t) => (CMS_TOOLS as readonly string[]).includes(t));
|
|
176
|
+
if (cms) {
|
|
177
|
+
const info = TECH_TOOL_INFO[cms as TechTool];
|
|
178
|
+
if (info?.skill) {
|
|
179
|
+
result = result.replace(
|
|
180
|
+
/(\| `cms`\s*\|)\s*\|(\s*\|)/,
|
|
181
|
+
`$1 ${info.tech} | \`${info.skill}\` $2`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
223
184
|
}
|
|
224
185
|
|
|
225
186
|
return result;
|
|
226
187
|
}
|
|
188
|
+
|
|
189
|
+
// ── Agent tool injection ──────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Compute tool injections per agent based on the user's selected stack.
|
|
193
|
+
* Returns a Map where key = agent name (e.g. 'content-engineer'), value = tools to inject.
|
|
194
|
+
*/
|
|
195
|
+
export function getAgentToolInjections(stack: StackConfig): Map<string, string[]> {
|
|
196
|
+
const injections = new Map<string, string[]>();
|
|
197
|
+
const selectedIds = [...stack.techTools, ...stack.teamTools] as string[];
|
|
198
|
+
|
|
199
|
+
for (const id of selectedIds) {
|
|
200
|
+
const plugin = PLUGINS[id];
|
|
201
|
+
if (!plugin?.agentToolMap) continue;
|
|
202
|
+
|
|
203
|
+
for (const [agentName, tools] of Object.entries(plugin.agentToolMap)) {
|
|
204
|
+
const existing = injections.get(agentName) ?? [];
|
|
205
|
+
existing.push(...tools);
|
|
206
|
+
injections.set(agentName, existing);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return injections;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Returns a transform callback that injects plugin-specific tools
|
|
215
|
+
* into agent file frontmatter based on the user's stack selection.
|
|
216
|
+
*/
|
|
217
|
+
export function getAgentTransform(
|
|
218
|
+
stack: StackConfig
|
|
219
|
+
): NonNullable<CopyDirOptions['transform']> {
|
|
220
|
+
const injections = getAgentToolInjections(stack);
|
|
221
|
+
|
|
222
|
+
return (content: string, srcPath: string) => {
|
|
223
|
+
// Extract agent name from filename (e.g., 'content-engineer' from 'content-engineer.agent.md')
|
|
224
|
+
const match = srcPath.match(/([^/\\]+)\.agent\.md$/);
|
|
225
|
+
if (!match) return content;
|
|
226
|
+
|
|
227
|
+
const agentName = match[1];
|
|
228
|
+
const toolsToInject = injections.get(agentName);
|
|
229
|
+
if (!toolsToInject || toolsToInject.length === 0) return content;
|
|
230
|
+
|
|
231
|
+
// Parse the frontmatter to find the tools array
|
|
232
|
+
const fmMatch = content.match(/^(---\n)([\s\S]*?)\n(---\n)([\s\S]*)$/);
|
|
233
|
+
if (!fmMatch) return content;
|
|
234
|
+
|
|
235
|
+
const frontmatter = fmMatch[2];
|
|
236
|
+
const body = fmMatch[4];
|
|
237
|
+
|
|
238
|
+
// Find and modify the tools line
|
|
239
|
+
const toolsMatch = frontmatter.match(/^(tools:\s*\[)(.*?)(\]\s*)$/m);
|
|
240
|
+
if (!toolsMatch) return content;
|
|
241
|
+
|
|
242
|
+
const existingTools = toolsMatch[2];
|
|
243
|
+
const injectedToolsList = toolsToInject.map((t) => `'${t}'`).join(', ');
|
|
244
|
+
const newTools = existingTools
|
|
245
|
+
? `${existingTools}, ${injectedToolsList}`
|
|
246
|
+
: injectedToolsList;
|
|
247
|
+
|
|
248
|
+
const newFrontmatter = frontmatter.replace(
|
|
249
|
+
toolsMatch[0],
|
|
250
|
+
`${toolsMatch[1]}${newTools}${toolsMatch[3]}`
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
return `---\n${newFrontmatter}\n---\n${body}`;
|
|
254
|
+
};
|
|
255
|
+
}
|
package/src/cli/types.ts
CHANGED
|
@@ -2,16 +2,36 @@ import type { ChildProcess } from 'node:child_process';
|
|
|
2
2
|
|
|
3
3
|
// ── Stack selection types ──────────────────────────────────────
|
|
4
4
|
|
|
5
|
-
export type
|
|
6
|
-
export type
|
|
7
|
-
export type
|
|
8
|
-
export type NotifChoice = 'slack' | 'teams' | 'none';
|
|
5
|
+
export type IdeChoice = 'vscode' | 'cursor' | 'claude-code' | 'opencode';
|
|
6
|
+
export type TechTool = 'sanity' | 'contentful' | 'strapi' | 'supabase' | 'convex' | 'vercel' | 'nx' | 'chrome-devtools';
|
|
7
|
+
export type TeamTool = 'linear' | 'jira' | 'slack' | 'teams';
|
|
9
8
|
|
|
10
9
|
export interface StackConfig {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
ides: IdeChoice[];
|
|
11
|
+
techTools: TechTool[];
|
|
12
|
+
teamTools: TeamTool[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Check if a stack config uses the legacy v1 format (individual choices with 'none'). */
|
|
16
|
+
export function isLegacyStack(stack: unknown): stack is { cms: string; db: string; pm?: string; notifications?: string } {
|
|
17
|
+
return typeof stack === 'object' && stack !== null && 'cms' in stack;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Migrate a legacy v1 stack config to the v2 array format. */
|
|
21
|
+
export function migrateStackConfig(
|
|
22
|
+
legacy: { cms: string; db: string; pm?: string; notifications?: string },
|
|
23
|
+
ide?: string
|
|
24
|
+
): StackConfig {
|
|
25
|
+
const techTools: TechTool[] = [];
|
|
26
|
+
const teamTools: TeamTool[] = [];
|
|
27
|
+
|
|
28
|
+
if (legacy.cms && legacy.cms !== 'none') techTools.push(legacy.cms as TechTool);
|
|
29
|
+
if (legacy.db && legacy.db !== 'none') techTools.push(legacy.db as TechTool);
|
|
30
|
+
if (legacy.pm && legacy.pm !== 'none') teamTools.push(legacy.pm as TeamTool);
|
|
31
|
+
if (legacy.notifications && legacy.notifications !== 'none') teamTools.push(legacy.notifications as TeamTool);
|
|
32
|
+
|
|
33
|
+
const ides: IdeChoice[] = ide ? [ide as IdeChoice] : [];
|
|
34
|
+
return { ides, techTools, teamTools };
|
|
15
35
|
}
|
|
16
36
|
|
|
17
37
|
/** Context passed from bin/cli.mjs to every command handler. */
|
|
@@ -60,6 +80,7 @@ export interface RepoInfo {
|
|
|
60
80
|
export interface Manifest {
|
|
61
81
|
version: string;
|
|
62
82
|
ide: string;
|
|
83
|
+
ides?: string[];
|
|
63
84
|
installedAt: string;
|
|
64
85
|
updatedAt: string;
|
|
65
86
|
managedPaths?: ManagedPaths;
|
|
@@ -85,6 +106,8 @@ export interface SelectOption {
|
|
|
85
106
|
label: string;
|
|
86
107
|
hint?: string;
|
|
87
108
|
value: string;
|
|
109
|
+
/** Whether this option starts selected (preselected). */
|
|
110
|
+
selected?: boolean;
|
|
88
111
|
}
|
|
89
112
|
|
|
90
113
|
/** Scaffold result from MCP config. */
|
|
@@ -93,6 +116,14 @@ export interface ScaffoldResult {
|
|
|
93
116
|
action: 'created' | 'skipped';
|
|
94
117
|
}
|
|
95
118
|
|
|
119
|
+
/** IDE display labels. */
|
|
120
|
+
export const IDE_LABELS: Record<IdeChoice, string> = {
|
|
121
|
+
vscode: 'VS Code',
|
|
122
|
+
cursor: 'Cursor',
|
|
123
|
+
'claude-code': 'Claude Code',
|
|
124
|
+
opencode: 'OpenCode',
|
|
125
|
+
};
|
|
126
|
+
|
|
96
127
|
// ── Run command types ──────────────────────────────────────────
|
|
97
128
|
|
|
98
129
|
/** Validated task spec from YAML. */
|