UNPKG

@oliverpople/agency-x

Version:

🚀 **Transform feature requests into production-ready code in seconds**

764 lines (686 loc) • 27 kB
var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/utils/voice.ts import { Say } from "say"; var voiceEnabled = false; var say = new Say(); var setVoiceEnabled = (enabled) => { voiceEnabled = enabled; }; var speak = (text) => { if (voiceEnabled) { say.speak(text); } }; // src/utils/contextStore.ts import fs from "fs/promises"; import path from "path"; var currentContext = { log: [] }; var logToContext = (message) => { currentContext.log.push(message); }; var sessionsDir = path.join(process.cwd(), "sessions"); var generateSpecId = () => { const date = /* @__PURE__ */ new Date(); const year = date.getFullYear(); const month = (date.getMonth() + 1).toString().padStart(2, "0"); const day = date.getDate().toString().padStart(2, "0"); const randomId = Math.random().toString(36).substring(2, 6).toUpperCase(); return `SPEC-${year}${month}${day}-${randomId}`; }; var createContext = (featurePrompt) => { const specId = generateSpecId(); currentContext = { specId, featurePrompt, spec: {}, agents: {}, finalBundle: {}, log: [] }; return currentContext; }; var getContext = () => currentContext; var updateContext = (updates) => { currentContext = { ...currentContext, ...updates }; return currentContext; }; var saveContext = async () => { const filePath = path.join(sessionsDir, `${currentContext.specId}.json`); await fs.writeFile(filePath, JSON.stringify(currentContext, null, 2)); return filePath; }; var loadContext = async (specId) => { const filePath = path.join(sessionsDir, `${specId}.json`); const data = await fs.readFile(filePath, "utf-8"); currentContext = JSON.parse(data); return currentContext; }; // src/utils/logger.ts import chalk from "chalk"; var agentStyles = { productManager: chalk.bold.blue, frontendDeveloper: chalk.green, backendDeveloper: chalk.yellow.italic, qaEngineer: chalk.red.underline, documentationAgent: chalk.cyan, fullstackIntegrator: chalk.magenta, infraEngineer: chalk.gray, securityEngineer: chalk.yellow, uxDesigner: chalk.blue, copywriter: chalk.green, releaseManager: chalk.magenta.bold, aiPromptEngineer: chalk.blue.italic, dataEngineer: chalk.yellow, orchestratorAgent: chalk.bold.magenta, voiceNarratorAgent: chalk.italic.gray, reviewerAgent: chalk.bold.yellow, assumptionChallenger: chalk.bold.red, userEmpathyAgent: chalk.bold.green }; var log = (agentName, message) => { const styledMessage = `${agentStyles[agentName](`[${agentName}]`)}: ${message}`; console.log(styledMessage); logToContext(styledMessage); }; var spinner = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]; var interval; var startThinking = (agentName) => { let i = 0; const thinkingMessage = `is thinking...`; interval = setInterval(() => { i = (i + 1) % spinner.length; process.stdout.write(`\r${agentStyles[agentName](`[${agentName}]`)}: ${spinner[i]} ${thinkingMessage}`); }, 100); }; var stopThinking = () => { clearInterval(interval); process.stdout.write("\r" + " ".repeat(50) + "\r"); }; var createLogger = (agentName) => ({ log: (message) => log(agentName, message), start: () => startThinking(agentName), stop: () => stopThinking(), info: (message) => log(agentName, chalk.white(message)), success: (message) => log(agentName, chalk.greenBright(message)), error: (message) => log(agentName, chalk.redBright(message)) }); var orchestratorLogger = createLogger("orchestratorAgent"); // src/protocols/index.ts var protocols_exports = {}; __export(protocols_exports, { devToQA: () => devToQA, fullRoundtrip: () => fullRoundtrip, productToDev: () => productToDev, specToDocs: () => specToDocs }); // src/llm/claudeClient.ts import Anthropic from "@anthropic-ai/sdk"; var anthropic = null; var getAnthropic = () => { if (!anthropic) { const apiKey = process.env.CLAUDE_API_KEY; if (!apiKey) { throw new Error("CLAUDE_API_KEY environment variable is not set"); } anthropic = new Anthropic({ apiKey }); } return anthropic; }; var getClaudeClient = () => { return { generate: async (prompt) => { const client = getAnthropic(); const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error("Claude API request timed out after 60 seconds")), 6e4); }); try { const response = await Promise.race([ client.messages.create({ model: "claude-3-opus-20240229", max_tokens: 2048, messages: [{ role: "user", content: prompt }] }), timeoutPromise ]); return response.content[0].text; } catch (error) { console.error("Claude API error:", error); throw error; } } }; }; // src/llm/openaiClient.ts import OpenAI from "openai"; var openai = null; var getOpenAI = () => { if (!openai) { const apiKey = process.env.OPENAI_API_KEY; if (!apiKey) { throw new Error("OPENAI_API_KEY environment variable is not set"); } openai = new OpenAI({ apiKey }); } return openai; }; var getOpenAIClient = () => { return { generate: async (prompt) => { const client = getOpenAI(); const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error("OpenAI API request timed out after 60 seconds")), 6e4); }); try { const response = await Promise.race([ client.chat.completions.create({ model: "gpt-4o", messages: [{ role: "user", content: prompt }] }), timeoutPromise ]); return response.choices[0].message.content || ""; } catch (error) { console.error("OpenAI API error:", error); throw error; } } }; }; // src/llm/llmRouter.ts var defaultProvider = "openai"; var getLlmClient = () => { switch (defaultProvider) { case "claude": return getClaudeClient(); case "openai": return getOpenAIClient(); default: throw new Error(`Unknown LLM provider: ${defaultProvider}`); } }; // src/utils/promptTemplates.ts var productManagerPrompt = ` You are a world-class Product Manager. Your task is to take a high-level feature prompt and convert it into a structured product specification. The output must be a single, valid JSON object with the following keys: "title", "userStories", "acceptanceCriteria", and "edgeCases". - "title": A concise, descriptive title for the feature. - "userStories": An array of strings, each representing a user story in the format "As a [user type], I want to [action] so that [benefit]". - "acceptanceCriteria": An array of strings, each describing a specific, testable condition that must be met for the feature to be considered complete. - "edgeCases": An array of strings, each identifying a potential edge case, error state, or unexpected user behavior that needs to be considered. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the JSON object. Feature Prompt: "{{featurePrompt}}" `; var backendDeveloperPrompt = ` You are a world-class Backend Developer. Your task is to take a product specification and generate the necessary server-side code. The output must be a single, valid code block that implements the described feature using Express.js. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the code. Product Specification: {{spec}} `; var frontendDeveloperPrompt = ` You are a world-class Frontend Developer. Your task is to take a product specification and generate the necessary client-side code. The output must be a single, valid React component that implements the described feature. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the code. Product Specification: {{spec}} `; var qaEngineerPrompt = ` You are a world-class QA Engineer. Your task is to take a product specification and the generated code and write tests for it. The output must be a single, valid code block that contains the tests, using Jest and React Testing Library. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the code. Product Specification: {{spec}} Backend Code: {{backendCode}} Frontend Code: {{frontendCode}} `; var documentationAgentPrompt = ` You are a world-class Technical Writer. Your task is to take a product specification and generate documentation for it. The output must be a single, valid Markdown document. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the Markdown. Product Specification: {{spec}} `; var infraEngineerPrompt = ` You are a world-class Infrastructure Engineer. Your task is to analyze the project's code and generate a production-ready, multi-stage Dockerfile to build and run the application. The output must be a single, valid Dockerfile. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the Dockerfile code. Backend Code: {{backendCode}} Frontend Code: {{frontendCode}} `; var securityEngineerPrompt = ` You are a world-class Security Engineer. Your task is to audit the provided code and product specification for potential vulnerabilities. The output must be a Markdown report outlining potential risks, such as XSS, SQLi, or authentication gaps, with recommendations for mitigation. Product Specification: {{spec}} Backend Code: {{backendCode}} Frontend Code: {{frontendCode}} `; var uxDesignerPrompt = ` You are a world-class UX Designer. Your task is to analyze a product specification and provide a text-based wireframe and interaction flow summary. The output must be a Markdown document outlining the user flow, key UI elements, and accessibility considerations. Product Specification: {{spec}} `; var copywriterPrompt = ` You are a world-class UX Copywriter. Your task is to review a product specification and the UI code to provide clear, concise, and helpful microcopy. The output must be a JSON object mapping component IDs or class names to suggested copy for labels, tooltips, and error messages. Product Specification: {{spec}} Frontend Code: {{frontendCode}} `; var releaseManagerPrompt = ` You are a world-class Release Manager. Your task is to review the entire project context and prepare for a production release. The output must be a Markdown document containing a release checklist, including a suggested SemVer bump, a summary for the changelog, and a list of pre-flight checks. Project Context: {{context}} `; var aiPromptEngineerPrompt = ` You are a world-class AI Prompt Engineer. Your task is to analyze a product specification and generate optimized Claude/GPT prompt templates for any AI features in the product. The output must be a JSON object containing structured prompts with few-shot examples. Product Specification: {{spec}} `; var dataEngineerPrompt = ` You are a world-class Data Engineer. Your task is to analyze a product specification and generate analytics event schemas and tracking plans. The output must be a JSON object defining event maps, schema extensions, and tracking requirements. Product Specification: {{spec}} `; var reviewerAgentPrompt = ` You are a world-class Code Reviewer. Your task is to review all generated code and documentation for quality, consistency, and best practices. The output must be a Markdown report with specific feedback and improvement suggestions. Code to Review: {{code}} Documentation: {{docs}} `; var assumptionChallengerPrompt = ` You are a world-class Devil's Advocate. Your task is to identify potential gaps, assumptions, or edge cases in the product specification that may have been overlooked. The output must be a list of challenging questions and potential issues that should be addressed. Product Specification: {{spec}} `; var userEmpathyAgentPrompt = ` You are a world-class User Experience Advocate. Your task is to simulate a real user interacting with the proposed feature and identify usability concerns. The output must be a Markdown report describing the user journey, potential friction points, and suggestions for improvement. Product Specification: {{spec}} Frontend Code: {{frontendCode}} `; var jsonRepairPrompt = ` You are a JSON repair bot. Your task is to take a piece of text that is supposed to be a JSON object and fix it so that it is a single, valid JSON object. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the repaired JSON object. Invalid JSON: {{invalidJson}} `; // src/agents/productManager.ts var logger = createLogger("productManager"); var runProductManager = async () => { logger.start(); const context = getContext(); const { featurePrompt } = context; const llmClient = getLlmClient(); const prompt = productManagerPrompt.replace("{{featurePrompt}}", featurePrompt); let response = await llmClient.generate(prompt); let spec; try { spec = JSON.parse(response); } catch (error) { logger.error("Invalid JSON response from LLM. Attempting to repair..."); const repairPrompt = jsonRepairPrompt.replace("{{invalidJson}}", response); response = await llmClient.generate(repairPrompt); try { spec = JSON.parse(response); } catch (error2) { logger.error("Failed to repair JSON response. Skipping spec generation."); return; } } updateContext({ spec }); logger.stop(); logger.success("Generated product spec."); return spec; }; // src/agents/backendDeveloper.ts var logger2 = createLogger("backendDeveloper"); var runBackendDeveloper = async () => { logger2.start(); const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = backendDeveloperPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); const code = await llmClient.generate(prompt); updateContext({ agents: { ...context.agents || {}, backendDeveloper: { output: code, completed: true } } }); logger2.stop(); logger2.success("Generated backend code."); return code; }; // src/agents/frontendDeveloper.ts var logger3 = createLogger("frontendDeveloper"); var runFrontendDeveloper = async () => { logger3.start(); const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = frontendDeveloperPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); const code = await llmClient.generate(prompt); updateContext({ agents: { ...context.agents || {}, frontendDeveloper: { output: code, completed: true } } }); logger3.stop(); logger3.success("Generated frontend code."); return code; }; // src/protocols/productToDev.ts var productToDev = async () => { await runProductManager(); await Promise.all([ runBackendDeveloper(), runFrontendDeveloper() ]); }; // src/agents/qaEngineer.ts var logger4 = createLogger("qaEngineer"); var runQaEngineer = async () => { logger4.start(); const context = getContext(); const { spec, agents = {} } = context; const backendOutput = agents.backendDeveloper?.output || "No backend code generated yet"; const frontendOutput = agents.frontendDeveloper?.output || "No frontend code generated yet"; const llmClient = getLlmClient(); const prompt = qaEngineerPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)).replace("{{backendCode}}", backendOutput).replace("{{frontendCode}}", frontendOutput); const tests = await llmClient.generate(prompt); updateContext({ agents: { ...agents, qaEngineer: { tests, passed: true, completed: true } } }); logger4.stop(); logger4.success("Generated QA tests."); return tests; }; // src/protocols/devToQA.ts var devToQA = async () => { await Promise.all([ runBackendDeveloper(), runFrontendDeveloper() ]); await runQaEngineer(); }; // src/agents/documentationAgent.ts var logger5 = createLogger("documentationAgent"); var runDocumentationAgent = async () => { logger5.start(); const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = documentationAgentPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); const doc = await llmClient.generate(prompt); updateContext({ agents: { ...context.agents || {}, documentationAgent: { doc, completed: true } } }); logger5.stop(); logger5.success("Generated documentation."); return doc; }; // src/protocols/specToDocs.ts var specToDocs = async () => { await runProductManager(); await runDocumentationAgent(); }; // src/agents/fullstackIntegrator.ts var logger6 = createLogger("fullstackIntegrator"); var runFullstackIntegrator = async () => { logger6.start(); const context = getContext(); logger6.info("Ensuring FE and BE contract alignment."); updateContext({ agents: { ...context.agents || {}, fullstackIntegrator: { completed: true } } }); logger6.stop(); logger6.success("Fullstack integration complete."); return true; }; // src/agents/infraEngineer.ts var logger7 = createLogger("infraEngineer"); var runInfraEngineer = async () => { logger7.start(); const context = getContext(); const { agents = {} } = context; const backendOutput = agents.backendDeveloper?.output || "No backend code generated yet"; const frontendOutput = agents.frontendDeveloper?.output || "No frontend code generated yet"; const llmClient = getLlmClient(); const prompt = infraEngineerPrompt.replace("{{backendCode}}", backendOutput).replace("{{frontendCode}}", frontendOutput); const dockerfile = await llmClient.generate(prompt); updateContext({ agents: { ...agents, infraEngineer: { output: dockerfile, completed: true } } }); logger7.stop(); logger7.success("Generated Dockerfile."); return dockerfile; }; // src/agents/securityEngineer.ts var logger8 = createLogger("securityEngineer"); var runSecurityEngineer = async () => { logger8.start(); const context = getContext(); const { spec, agents = {} } = context; const backendOutput = agents.backendDeveloper?.output || "No backend code generated yet"; const frontendOutput = agents.frontendDeveloper?.output || "No frontend code generated yet"; const llmClient = getLlmClient(); const prompt = securityEngineerPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)).replace("{{backendCode}}", backendOutput).replace("{{frontendCode}}", frontendOutput); const report = await llmClient.generate(prompt); updateContext({ agents: { ...agents, securityEngineer: { output: report, completed: true } } }); logger8.stop(); logger8.success("Generated security report."); return report; }; // src/agents/uxDesigner.ts var logger9 = createLogger("uxDesigner"); var runUxDesigner = async () => { logger9.start(); const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = uxDesignerPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); const report = await llmClient.generate(prompt); updateContext({ agents: { ...context.agents || {}, uxDesigner: { output: report, completed: true } } }); logger9.stop(); logger9.success("Generated UX design report."); return report; }; // src/agents/copywriter.ts var logger10 = createLogger("copywriter"); var runCopywriter = async () => { logger10.start(); const context = getContext(); const { spec, agents = {} } = context; const frontendOutput = agents.frontendDeveloper?.output || "No frontend code generated yet"; const llmClient = getLlmClient(); const prompt = copywriterPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)).replace("{{frontendCode}}", frontendOutput); let response = await llmClient.generate(prompt); let copy; try { copy = JSON.parse(response); } catch (error) { logger10.error("Invalid JSON response from LLM. Attempting to repair..."); const repairPrompt = jsonRepairPrompt.replace("{{invalidJson}}", response); response = await llmClient.generate(repairPrompt); try { copy = JSON.parse(response); } catch (error2) { logger10.error("Failed to repair JSON response. Skipping copy generation."); return; } } updateContext({ agents: { ...agents, copywriter: { output: copy, completed: true } } }); logger10.stop(); logger10.success("Generated microcopy."); return copy; }; // src/agents/releaseManager.ts var logger11 = createLogger("releaseManager"); var runReleaseManager = async () => { logger11.start(); const context = getContext(); const llmClient = getLlmClient(); const prompt = releaseManagerPrompt.replace("{{context}}", JSON.stringify(context, null, 2)); const report = await llmClient.generate(prompt); updateContext({ agents: { ...context.agents || {}, releaseManager: { output: report, completed: true } } }); logger11.stop(); logger11.success("Generated release plan."); return report; }; // src/agents/aiPromptEngineer.ts var logger12 = createLogger("aiPromptEngineer"); var runAiPromptEngineer = async () => { logger12.start(); const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = aiPromptEngineerPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); let response = await llmClient.generate(prompt); let prompts; try { prompts = JSON.parse(response); } catch (error) { logger12.error("Invalid JSON response from LLM. Attempting to repair..."); const repairPrompt = jsonRepairPrompt.replace("{{invalidJson}}", response); response = await llmClient.generate(repairPrompt); try { prompts = JSON.parse(response); } catch (error2) { logger12.error("Failed to repair JSON response. Skipping AI prompt generation."); return; } } updateContext({ agents: { ...context.agents || {}, aiPromptEngineer: { output: prompts, completed: true } } }); logger12.stop(); logger12.success("Generated AI prompts."); return prompts; }; // src/agents/dataEngineer.ts var logger13 = createLogger("dataEngineer"); var runDataEngineer = async () => { logger13.start(); const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = dataEngineerPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); let response = await llmClient.generate(prompt); let events; try { events = JSON.parse(response); } catch (error) { logger13.error("Invalid JSON response from LLM. Attempting to repair..."); const repairPrompt = jsonRepairPrompt.replace("{{invalidJson}}", response); response = await llmClient.generate(repairPrompt); try { events = JSON.parse(response); } catch (error2) { logger13.error("Failed to repair JSON response. Skipping analytics event generation."); return; } } updateContext({ agents: { ...context.agents || {}, dataEngineer: { output: events, completed: true } } }); logger13.stop(); logger13.success("Generated analytics events."); return events; }; // src/agents/reviewerAgent.ts var logger14 = createLogger("reviewerAgent"); var runReviewerAgent = async () => { logger14.start(); const context = getContext(); const { agents = {} } = context; const backendOutput = agents.backendDeveloper?.output || "No backend code generated yet"; const frontendOutput = agents.frontendDeveloper?.output || "No frontend code generated yet"; const docsOutput = agents.documentationAgent?.doc || "No documentation generated yet"; const llmClient = getLlmClient(); const prompt = reviewerAgentPrompt.replace("{{code}}", JSON.stringify({ backend: backendOutput, frontend: frontendOutput }, null, 2)).replace("{{docs}}", docsOutput); const report = await llmClient.generate(prompt); updateContext({ agents: { ...agents, reviewerAgent: { output: report, completed: true } } }); logger14.stop(); logger14.success("Generated review."); return report; }; // src/agents/assumptionChallenger.ts var logger15 = createLogger("assumptionChallenger"); var runAssumptionChallenger = async () => { logger15.start(); const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = assumptionChallengerPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); const report = await llmClient.generate(prompt); updateContext({ agents: { ...context.agents || {}, assumptionChallenger: { output: report, completed: true } } }); logger15.stop(); logger15.success("Generated assumption challenge report."); return report; }; // src/agents/userEmpathyAgent.ts var logger16 = createLogger("userEmpathyAgent"); var runUserEmpathyAgent = async () => { logger16.start(); const context = getContext(); const { spec, agents = {} } = context; const frontendOutput = agents.frontendDeveloper?.output || "No frontend code generated yet"; const llmClient = getLlmClient(); const prompt = userEmpathyAgentPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)).replace("{{frontendCode}}", frontendOutput); const report = await llmClient.generate(prompt); updateContext({ agents: { ...agents, userEmpathyAgent: { output: report, completed: true } } }); logger16.stop(); logger16.success("Generated user empathy report."); return report; }; // src/protocols/fullRoundtrip.ts var fullRoundtrip = async () => { await runProductManager(); await Promise.all([ runBackendDeveloper(), runFrontendDeveloper() ]); await runFullstackIntegrator(); await runQaEngineer(); await runDocumentationAgent(); await runInfraEngineer(); await runSecurityEngineer(); await runUxDesigner(); await runCopywriter(); await runReleaseManager(); await runAiPromptEngineer(); await runDataEngineer(); await runReviewerAgent(); await runAssumptionChallenger(); await runUserEmpathyAgent(); }; // src/orchestrator/orchestratorAgent.ts var runOrchestrator = async (options) => { const { feature, protocol = "fullRoundtrip", voice = false, resume, onStop } = options; setVoiceEnabled(voice); if (resume) { await loadContext(resume); orchestratorLogger.info(`Resuming orchestration for specId: ${resume}`); } else { createContext(feature); orchestratorLogger.info(`Starting orchestration for feature: "${feature}"`); } const selectedProtocol = protocols_exports[protocol]; if (!selectedProtocol) { throw new Error(`Unknown protocol: ${protocol}`); } await selectedProtocol(); const context = getContext(); const savedPath = await saveContext(); orchestratorLogger.success(`Orchestration complete. Context saved to ${savedPath}`); if (onStop) { onStop(context.log); } return getContext(); }; export { speak, runOrchestrator };