agency-x
Version:
This project is a Claude-compatible, LLM-agnostic sub-agent framework that simulates a complete SaaS product team. It is delivered as a reusable, developer-ready NPM package.
340 lines (313 loc) • 11.6 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
// src/utils/contextStore.ts
var import_promises = __toESM(require("fs/promises"));
var import_path = __toESM(require("path"));
var sessionsDir = import_path.default.join(process.cwd(), "sessions");
var currentContext = {};
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: {}
};
return currentContext;
};
var getContext = () => currentContext;
var updateContext = (updates) => {
currentContext = { ...currentContext, ...updates };
return currentContext;
};
var saveContext = async () => {
const filePath = import_path.default.join(sessionsDir, `${currentContext.specId}.json`);
await import_promises.default.writeFile(filePath, JSON.stringify(currentContext, null, 2));
return filePath;
};
// src/utils/logger.ts
var import_chalk = __toESM(require("chalk"));
var agentStyles = {
productManager: import_chalk.default.bold.blue,
frontendDeveloper: import_chalk.default.green,
backendDeveloper: import_chalk.default.yellow.italic,
qaEngineer: import_chalk.default.red.underline,
documentationAgent: import_chalk.default.cyan,
fullstackIntegrator: import_chalk.default.magenta,
infraEngineer: import_chalk.default.gray,
securityEngineer: import_chalk.default.yellow,
uxDesigner: import_chalk.default.blue,
copywriter: import_chalk.default.green,
releaseManager: import_chalk.default.magenta.bold,
aiPromptEngineer: import_chalk.default.blue.italic,
dataEngineer: import_chalk.default.yellow,
orchestratorAgent: import_chalk.default.bold.magenta,
voiceNarratorAgent: import_chalk.default.italic.gray,
reviewerAgent: import_chalk.default.bold.yellow,
assumptionChallenger: import_chalk.default.bold.red,
userEmpathyAgent: import_chalk.default.bold.green
};
var log = (agentName, message) => {
const style = agentStyles[agentName] || ((str) => str);
console.log(`${style(`[${agentName}]`)}: ${message}`);
};
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, import_chalk.default.white(message)),
success: (message) => log(agentName, import_chalk.default.greenBright(message)),
error: (message) => log(agentName, import_chalk.default.redBright(message))
});
var orchestratorLogger = createLogger("orchestratorAgent");
// src/utils/voice.ts
var import_say = require("say");
var voiceEnabled = false;
var say = new import_say.Say();
var setVoiceEnabled = (enabled) => {
voiceEnabled = enabled;
};
var speak = (text) => {
if (voiceEnabled) {
say.speak(text);
}
};
var stop = () => {
say.stop();
};
var narrator = {
speak,
stop
};
// src/agents/productManager.ts
var logger = createLogger("productManager");
var runProductManager = async () => {
logger.start();
const context = getContext();
const { featurePrompt } = context;
const spec = {
title: `Spec for: ${featurePrompt}`,
userStories: [
"As a user, I want to be able to export filtered reports as CSVs so that I can analyze the data in a spreadsheet."
],
acceptanceCriteria: [
"The CSV export button should be disabled when no filters are applied.",
"The exported CSV should contain all the data from the filtered report.",
"The CSV should be well-formed and open correctly in Excel and Google Sheets."
],
edgeCases: [
"What happens if the report contains no data?",
"What happens if the user tries to export a very large report?"
]
};
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 code = `
const express = require('express');
const router = express.Router();
router.post('/export', (req, res) => {
const { filters } = req.body;
// In a real implementation, this would query the database with the filters.
const data = [{ id: 1, name: 'Test' }];
res.csv(data, true);
});
module.exports = router;
`;
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 code = `
import React, { useState } from 'react';
const ExportButton = () => {
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
const response = await fetch('/api/export', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filters: {} }),
});
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'report.csv';
a.click();
setLoading(false);
};
return (
<button onClick={handleClick} disabled={loading}>
{loading ? 'Exporting...' : 'Export as CSV'}
</button>
);
};
export default ExportButton;
`;
updateContext({ agents: { ...context.agents, frontendDeveloper: { output: code, completed: true } } });
logger3.stop();
logger3.success("Generated frontend code.");
return code;
};
// src/agents/fullstackIntegrator.ts
var logger4 = createLogger("fullstackIntegrator");
var runFullstackIntegrator = async () => {
logger4.start();
const context = getContext();
logger4.info("Ensuring FE and BE contract alignment.");
updateContext({ agents: { ...context.agents, fullstackIntegrator: { completed: true } } });
logger4.stop();
logger4.success("Fullstack integration complete.");
return true;
};
// src/agents/qaEngineer.ts
var logger5 = createLogger("qaEngineer");
var runQaEngineer = async () => {
logger5.start();
const context = getContext();
const { spec, agents } = context;
const tests = `
describe('CSV Export', () => {
it('should export a CSV file when the export button is clicked', () => {
// This is a mock test that would need to be implemented with a testing library like Jest and React Testing Library.
expect(true).toBe(true);
});
});
`;
updateContext({ agents: { ...context.agents, qaEngineer: { tests, passed: true, completed: true } } });
logger5.stop();
logger5.success("Generated QA tests.");
return tests;
};
// src/agents/documentationAgent.ts
var logger6 = createLogger("documentationAgent");
var runDocumentationAgent = async () => {
logger6.start();
const context = getContext();
const { spec } = context;
const doc = `
# CSV Export Feature
This document describes the CSV export feature.
## User Stories
- As a user, I want to be able to export filtered reports as CSVs so that I can analyze the data in a spreadsheet.
## Acceptance Criteria
- The CSV export button should be disabled when no filters are applied.
- The exported CSV should contain all the data from the filtered report.
- The CSV should be well-formed and open correctly in Excel and Google Sheets.
## Edge Cases
- What happens if the report contains no data?
- What happens if the user tries to export a very large report?
`;
updateContext({ agents: { ...context.agents, documentationAgent: { doc, completed: true } } });
logger6.stop();
logger6.success("Generated documentation.");
return doc;
};
// src/orchestrator/orchestratorAgent.ts
var runOrchestrator = async (options) => {
const { feature: feature2, voice: voice2 = false } = options;
setVoiceEnabled(voice2);
orchestratorLogger.info(`Starting orchestration for feature: "${feature2}"`);
narrator.speak(`Starting orchestration for feature: ${feature2}`);
createContext(feature2);
await runProductManager();
narrator.speak("Product manager has completed the spec.");
await runBackendDeveloper();
narrator.speak("Backend developer has implemented the API.");
await runFrontendDeveloper();
narrator.speak("Frontend developer has built the UI.");
await runFullstackIntegrator();
narrator.speak("Fullstack integrator has verified the contracts.");
await runQaEngineer();
narrator.speak("QA engineer has written the tests.");
await runDocumentationAgent();
narrator.speak("Documentation agent has generated the guides.");
const context = getContext();
const finalBundle = {
code: {
frontend: context.agents.frontendDeveloper.output,
backend: context.agents.backendDeveloper.output
},
tests: context.agents.qaEngineer.tests,
docs: context.agents.documentationAgent.doc
};
updateContext({ finalBundle });
const savedPath = await saveContext();
orchestratorLogger.success(`Orchestration complete. Context saved to ${savedPath}`);
narrator.speak("All agents have completed their tasks. The final bundle is ready.");
return getContext();
};
// src/cli/index.ts
var args = process.argv.slice(2);
var featureIndex = args.indexOf("--feature");
var feature = "";
if (featureIndex !== -1 && args[featureIndex + 1]) {
feature = args[featureIndex + 1];
}
if (!feature) {
console.error("Please provide a feature description using the --feature flag.");
process.exit(1);
}
var voice = args.includes("--voice");
runOrchestrator({ feature, voice });