agent-enderun 0.5.1 → 0.5.2
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/.enderun/BRAIN_DASHBOARD.md +0 -0
- package/.enderun/PROJECT_MEMORY.md +27 -2
- package/.enderun/STATUS.md +2 -0
- package/.enderun/agents/analyst.md +8 -8
- package/.enderun/agents/backend.md +11 -11
- package/.enderun/agents/explorer.md +4 -4
- package/.enderun/agents/frontend.md +7 -7
- package/.enderun/agents/git.md +5 -5
- package/.enderun/agents/manager.md +12 -12
- package/.enderun/agents/mobile.md +5 -5
- package/.enderun/agents/native.md +5 -5
- package/.enderun/benchmarks/.gitkeep +0 -0
- package/.enderun/cli-commands.json +0 -0
- package/.enderun/config.json +0 -0
- package/.enderun/docs/api/README.md +0 -0
- package/.enderun/docs/api/auth.md +0 -0
- package/.enderun/docs/api/errors.md +0 -0
- package/.enderun/docs/error-handling.md +0 -0
- package/.enderun/docs/privacy.md +0 -0
- package/.enderun/docs/project-docs.md +0 -0
- package/.enderun/docs/security.md +0 -0
- package/.enderun/docs/tech-stack.md +1 -1
- package/.enderun/docs/troubleshooting.md +0 -0
- package/.enderun/knowledge/api_design_rules.md +0 -0
- package/.enderun/knowledge/async_error_handling.md +0 -0
- package/.enderun/knowledge/branded_types_pattern.md +0 -0
- package/.enderun/knowledge/code_review_checklist.md +0 -0
- package/.enderun/knowledge/contract_versioning.md +0 -0
- package/.enderun/knowledge/database_migration.md +0 -0
- package/.enderun/knowledge/deployment_checklist.md +0 -0
- package/.enderun/knowledge/git_commit_strategy.md +0 -0
- package/.enderun/knowledge/hermes_protocol.md +0 -0
- package/.enderun/knowledge/legacy_onboarding.md +0 -0
- package/.enderun/knowledge/monitoring_setup.md +0 -0
- package/.enderun/knowledge/performance_guidelines.md +0 -0
- package/.enderun/knowledge/repository_patterns.md +0 -0
- package/.enderun/knowledge/responsive_design_standards.md +0 -0
- package/.enderun/knowledge/security_scanning.md +0 -0
- package/.enderun/knowledge/testing_standards.md +1 -1
- package/.enderun/knowledge/troubleshooting_guide.md +0 -0
- package/.enderun/knowledge/zero_ui_library_policy.md +0 -0
- package/.enderun/logs/.gitkeep +0 -0
- package/.enderun/messages/.gitkeep +0 -0
- package/.enderun/monitoring/.gitkeep +0 -0
- package/.env.example +0 -0
- package/ENDERUN.md +3 -3
- package/LICENSE +0 -0
- package/README.md +38 -1
- package/bin/cli.js +627 -3
- package/bin/update-contract.js +21 -2
- package/claude.md +1 -1
- package/codex.md +1 -1
- package/cursor.md +1 -1
- package/docs/README.md +23 -0
- package/gemini-extension.json +8 -2
- package/gemini.md +1 -1
- package/mcp.json +0 -0
- package/package.json +4 -3
- package/packages/framework-mcp/dist/index.js +0 -0
- package/packages/framework-mcp/dist/schemas.js +2 -0
- package/packages/framework-mcp/dist/tools/contract.js +13 -7
- package/packages/framework-mcp/dist/tools/knowledge.js +1 -1
- package/packages/framework-mcp/dist/tools/messages.js +15 -6
- package/packages/framework-mcp/dist/tools/repository.js +24 -3
- package/packages/framework-mcp/dist/utils.js +2 -2
- package/packages/framework-mcp/package.json +2 -2
- package/packages/framework-mcp/src/schemas.ts +2 -0
- package/packages/framework-mcp/src/tools/contract.ts +14 -7
- package/packages/framework-mcp/src/tools/knowledge.ts +1 -1
- package/packages/framework-mcp/src/tools/messages.ts +17 -8
- package/packages/framework-mcp/src/tools/repository.ts +24 -3
- package/packages/framework-mcp/src/utils.ts +2 -2
- package/packages/shared-types/README.md +0 -0
- package/packages/shared-types/contract.version.json +3 -3
- package/packages/shared-types/package.json +4 -4
- package/packages/shared-types/src/index.ts +0 -0
- package/packages/shared-types/tsconfig.json +0 -0
- package/panda.config.ts +5 -1
- package/tsconfig.json +0 -0
- package/packages/framework-mcp/dist/index.d.ts +0 -1
- package/packages/shared-types/dist/index.d.ts.map +0 -1
package/bin/cli.js
CHANGED
|
@@ -22,7 +22,7 @@ function getPackageVersion() {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function getFrameworkDir() {
|
|
25
|
-
const adapters = [".gemini", ".claude", ".cursor", ".
|
|
25
|
+
const adapters = [".gemini", ".claude", ".cursor", ".enderun", ".codex"];
|
|
26
26
|
for (const adp of adapters) {
|
|
27
27
|
const fullPath = path.join(targetDir, adp);
|
|
28
28
|
if (fs.existsSync(fullPath) && fs.lstatSync(fullPath).isDirectory()) {
|
|
@@ -323,7 +323,7 @@ async function initCommand(selectedAdapter) {
|
|
|
323
323
|
codex: ["codex.md"],
|
|
324
324
|
};
|
|
325
325
|
|
|
326
|
-
const targetBase = selectedAdapter ? `.${selectedAdapter}` : ".enderun";
|
|
326
|
+
const targetBase = selectedAdapter && selectedAdapter !== "codex" ? `.${selectedAdapter}` : ".enderun";
|
|
327
327
|
|
|
328
328
|
const targetFrameworkDir = path.join(targetDir, targetBase);
|
|
329
329
|
|
|
@@ -351,7 +351,7 @@ async function initCommand(selectedAdapter) {
|
|
|
351
351
|
`${targetBase}/messages`,
|
|
352
352
|
"apps/web",
|
|
353
353
|
"apps/backend",
|
|
354
|
-
|
|
354
|
+
"docs",
|
|
355
355
|
"packages/shared-types",
|
|
356
356
|
"packages/framework-mcp",
|
|
357
357
|
];
|
|
@@ -454,6 +454,8 @@ async function initCommand(selectedAdapter) {
|
|
|
454
454
|
|
|
455
455
|
content = content.replace(/\{\{FRAMEWORK_DIR\}\}/g, targetBase);
|
|
456
456
|
content = content.replace(/\{\{ADAPTER\}\}/g, currentAdapter);
|
|
457
|
+
// Fallback: replace any residual hardcoded .enderun/ paths
|
|
458
|
+
content = content.replace(/\.enderun\//g, `${targetBase}/`);
|
|
457
459
|
|
|
458
460
|
if (ext === ".json") {
|
|
459
461
|
try {
|
|
@@ -497,6 +499,21 @@ async function initCommand(selectedAdapter) {
|
|
|
497
499
|
|
|
498
500
|
|
|
499
501
|
if (selectedAdapter === "gemini") {
|
|
502
|
+
// Patch gemini-extension.json to wire up the MCP server automatically
|
|
503
|
+
const geminiExtPath = path.join(targetDir, "gemini-extension.json");
|
|
504
|
+
try {
|
|
505
|
+
const ext = JSON.parse(fs.readFileSync(geminiExtPath, "utf8"));
|
|
506
|
+
ext.mcpServers = {
|
|
507
|
+
"agent-enderun": {
|
|
508
|
+
command: "node",
|
|
509
|
+
args: ["packages/framework-mcp/dist/index.js"]
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
fs.writeFileSync(geminiExtPath, JSON.stringify(ext, null, 2) + "\n");
|
|
513
|
+
console.log("💎 Gemini: MCP server wired up in gemini-extension.json automatically.");
|
|
514
|
+
} catch (e) {
|
|
515
|
+
console.warn("⚠️ Gemini: Could not patch gemini-extension.json for MCP. Wire it up manually.");
|
|
516
|
+
}
|
|
500
517
|
console.log(`💎 Gemini: Adapter gemini.md and ${targetBase}/ folder are ready.`);
|
|
501
518
|
}
|
|
502
519
|
|
|
@@ -730,6 +747,8 @@ function copyDir(src, dest, skipSet = new Set(), nonDestructive = false, framewo
|
|
|
730
747
|
if (textExtensions.includes(ext)) {
|
|
731
748
|
let content = fs.readFileSync(srcPath, "utf8");
|
|
732
749
|
content = content.replace(/\{\{FRAMEWORK_DIR\}\}/g, frameworkDir);
|
|
750
|
+
// Also replace any residual hardcoded .enderun/ paths left in source files
|
|
751
|
+
content = content.replace(/\.enderun\//g, `${frameworkDir}/`);
|
|
733
752
|
|
|
734
753
|
// Sanitize workspace: protocol
|
|
735
754
|
if (ext === ".json") {
|
|
@@ -751,6 +770,8 @@ function copyDir(src, dest, skipSet = new Set(), nonDestructive = false, framewo
|
|
|
751
770
|
|
|
752
771
|
content = content.replace(/\{\{FRAMEWORK_DIR\}\}/g, frameworkDir);
|
|
753
772
|
content = content.replace(/\{\{ADAPTER\}\}/g, currentAdapter);
|
|
773
|
+
// Fallback: replace any residual hardcoded .enderun/ paths
|
|
774
|
+
content = content.replace(/\.enderun\//g, `${frameworkDir}/`);
|
|
754
775
|
|
|
755
776
|
fs.writeFileSync(destPath, content);
|
|
756
777
|
} else {
|
|
@@ -825,6 +846,7 @@ function traceNewCommand(description, agent = "manager", priority = "P2") {
|
|
|
825
846
|
fs.writeFileSync(memoryPath, updated);
|
|
826
847
|
console.log(`\n✅ New Trace ID created: ${traceId}`);
|
|
827
848
|
console.log(`📝 Added to task list: ${description}\n`);
|
|
849
|
+
return traceId;
|
|
828
850
|
} finally {
|
|
829
851
|
releaseMemoryLock(lockPath);
|
|
830
852
|
}
|
|
@@ -850,7 +872,10 @@ function verifyContractCommand() {
|
|
|
850
872
|
const files = walk(sharedDir).sort();
|
|
851
873
|
const h = crypto.createHash("sha256");
|
|
852
874
|
for (const f of files) {
|
|
875
|
+
h.update(path.relative(targetDir, f));
|
|
876
|
+
h.update("\0");
|
|
853
877
|
h.update(fs.readFileSync(f));
|
|
878
|
+
h.update("\0");
|
|
854
879
|
}
|
|
855
880
|
const currentHash = h.digest("hex");
|
|
856
881
|
|
|
@@ -1099,6 +1124,593 @@ function updateProjectMemoryCommand(section, content) {
|
|
|
1099
1124
|
}
|
|
1100
1125
|
}
|
|
1101
1126
|
|
|
1127
|
+
function ensureDir(dirPath) {
|
|
1128
|
+
if (!fs.existsSync(dirPath)) {
|
|
1129
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
function writeTextFile(filePath, content) {
|
|
1134
|
+
ensureDir(path.dirname(filePath));
|
|
1135
|
+
fs.writeFileSync(filePath, content.endsWith("\n") ? content : `${content}\n`);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
function writeJsonFile(filePath, value) {
|
|
1139
|
+
writeTextFile(filePath, JSON.stringify(value, null, 2));
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function slugifyName(value) {
|
|
1143
|
+
const slug = String(value || "enderun-app")
|
|
1144
|
+
.toLowerCase()
|
|
1145
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
1146
|
+
.replace(/^-+|-+$/g, "");
|
|
1147
|
+
return slug || "enderun-app";
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
function titleCase(value) {
|
|
1151
|
+
return String(value || "Enderun App")
|
|
1152
|
+
.replace(/[-_]+/g, " ")
|
|
1153
|
+
.replace(/\s+/g, " ")
|
|
1154
|
+
.trim()
|
|
1155
|
+
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function inferAppSpec(description) {
|
|
1159
|
+
const normalized = String(description || "").toLowerCase();
|
|
1160
|
+
const isCrm = /\bcrm\b|customer|musteri|müşteri/.test(normalized);
|
|
1161
|
+
const hasAuth = /auth|login|giris|giriş|signin|sign in|user|kullanici|kullanıcı|role|rol/.test(normalized);
|
|
1162
|
+
const hasRoles = /role|rol|permission|yetki|admin/.test(normalized);
|
|
1163
|
+
const hasReports = /report|rapor|analytics|dashboard|chart|metric/.test(normalized);
|
|
1164
|
+
const appName = isCrm ? "crm-dashboard" : slugifyName(description).split("-").slice(0, 4).join("-");
|
|
1165
|
+
|
|
1166
|
+
return {
|
|
1167
|
+
rawDescription: description,
|
|
1168
|
+
appName,
|
|
1169
|
+
title: isCrm ? "CRM Dashboard" : titleCase(appName),
|
|
1170
|
+
domain: isCrm ? "CRM" : "Business",
|
|
1171
|
+
modules: {
|
|
1172
|
+
auth: hasAuth || isCrm,
|
|
1173
|
+
users: hasAuth || hasRoles || isCrm,
|
|
1174
|
+
roles: hasRoles || isCrm,
|
|
1175
|
+
reports: hasReports || isCrm,
|
|
1176
|
+
},
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function buildSharedTypesContent(existingContent) {
|
|
1181
|
+
const marker = "// --- Generated Application Contract ---";
|
|
1182
|
+
const generated = [
|
|
1183
|
+
marker,
|
|
1184
|
+
'export type RoleID = Brand<string, "RoleID">;',
|
|
1185
|
+
'export type ReportID = Brand<string, "ReportID">;',
|
|
1186
|
+
'export type CustomerID = Brand<string, "CustomerID">;',
|
|
1187
|
+
"",
|
|
1188
|
+
"export interface AuthSession {",
|
|
1189
|
+
" user: User;",
|
|
1190
|
+
" token: string;",
|
|
1191
|
+
" expiresAt: string;",
|
|
1192
|
+
"}",
|
|
1193
|
+
"",
|
|
1194
|
+
"export interface Role {",
|
|
1195
|
+
" id: RoleID;",
|
|
1196
|
+
" name: string;",
|
|
1197
|
+
" permissions: string[];",
|
|
1198
|
+
"}",
|
|
1199
|
+
"",
|
|
1200
|
+
"export interface Customer {",
|
|
1201
|
+
" id: CustomerID;",
|
|
1202
|
+
" name: string;",
|
|
1203
|
+
" ownerId: UserID;",
|
|
1204
|
+
" status: \"LEAD\" | \"ACTIVE\" | \"AT_RISK\";",
|
|
1205
|
+
" annualValue: number;",
|
|
1206
|
+
" createdAt: string;",
|
|
1207
|
+
"}",
|
|
1208
|
+
"",
|
|
1209
|
+
"export interface ReportMetric {",
|
|
1210
|
+
" id: ReportID;",
|
|
1211
|
+
" label: string;",
|
|
1212
|
+
" value: number;",
|
|
1213
|
+
" trend: \"UP\" | \"DOWN\" | \"FLAT\";",
|
|
1214
|
+
"}",
|
|
1215
|
+
"",
|
|
1216
|
+
"export interface DashboardSummary {",
|
|
1217
|
+
" customers: Customer[];",
|
|
1218
|
+
" users: User[];",
|
|
1219
|
+
" roles: Role[];",
|
|
1220
|
+
" reports: ReportMetric[];",
|
|
1221
|
+
"}",
|
|
1222
|
+
].join("\n");
|
|
1223
|
+
|
|
1224
|
+
if (existingContent.includes(marker)) return existingContent;
|
|
1225
|
+
return `${existingContent.trim()}\n\n${generated}\n`;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
function updateContractHashFile() {
|
|
1229
|
+
const sharedDir = path.join(targetDir, "packages/shared-types/src");
|
|
1230
|
+
const contractPath = path.join(targetDir, "packages/shared-types/contract.version.json");
|
|
1231
|
+
if (!fs.existsSync(sharedDir) || !fs.existsSync(contractPath)) return;
|
|
1232
|
+
|
|
1233
|
+
const walk = (dir) => fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
1234
|
+
const fullPath = path.join(dir, entry.name);
|
|
1235
|
+
return entry.isDirectory() ? walk(fullPath) : (entry.name.endsWith(".ts") ? [fullPath] : []);
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
const hash = crypto.createHash("sha256");
|
|
1239
|
+
for (const filePath of walk(sharedDir).sort()) {
|
|
1240
|
+
hash.update(path.relative(targetDir, filePath));
|
|
1241
|
+
hash.update("\0");
|
|
1242
|
+
hash.update(fs.readFileSync(filePath));
|
|
1243
|
+
hash.update("\0");
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const contract = JSON.parse(fs.readFileSync(contractPath, "utf8"));
|
|
1247
|
+
contract.contract_hash = hash.digest("hex");
|
|
1248
|
+
contract.last_updated = new Date().toISOString();
|
|
1249
|
+
fs.writeFileSync(contractPath, JSON.stringify(contract, null, 2));
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function createBackendFiles(spec) {
|
|
1253
|
+
writeJsonFile(path.join(targetDir, "apps/backend/package.json"), {
|
|
1254
|
+
name: "@agent-enderun/backend",
|
|
1255
|
+
version: "0.1.0",
|
|
1256
|
+
private: true,
|
|
1257
|
+
type: "module",
|
|
1258
|
+
scripts: {
|
|
1259
|
+
dev: "tsx src/server.ts",
|
|
1260
|
+
build: "tsc -p tsconfig.json",
|
|
1261
|
+
start: "node dist/server.js",
|
|
1262
|
+
test: "vitest run",
|
|
1263
|
+
},
|
|
1264
|
+
dependencies: {
|
|
1265
|
+
"@fastify/cors": "^11.0.0",
|
|
1266
|
+
fastify: "^5.0.0",
|
|
1267
|
+
zod: "^3.24.2",
|
|
1268
|
+
},
|
|
1269
|
+
devDependencies: {
|
|
1270
|
+
"@types/node": "^22.13.4",
|
|
1271
|
+
tsx: "^4.19.4",
|
|
1272
|
+
typescript: "^5.9.3",
|
|
1273
|
+
vitest: "^3.0.5",
|
|
1274
|
+
},
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
writeJsonFile(path.join(targetDir, "apps/backend/tsconfig.json"), {
|
|
1278
|
+
extends: "../../tsconfig.json",
|
|
1279
|
+
compilerOptions: {
|
|
1280
|
+
outDir: "dist",
|
|
1281
|
+
rootDir: "src",
|
|
1282
|
+
module: "NodeNext",
|
|
1283
|
+
moduleResolution: "NodeNext",
|
|
1284
|
+
target: "ES2022",
|
|
1285
|
+
strict: true,
|
|
1286
|
+
skipLibCheck: true,
|
|
1287
|
+
},
|
|
1288
|
+
include: ["src/**/*.ts"],
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
writeTextFile(path.join(targetDir, "apps/backend/src/data.ts"), [
|
|
1292
|
+
'import type { Customer, DashboardSummary, ReportMetric, Role, User } from "../../../packages/shared-types/src/index.js";',
|
|
1293
|
+
"",
|
|
1294
|
+
'const now = new Date().toISOString();',
|
|
1295
|
+
"",
|
|
1296
|
+
"export const roles: Role[] = [",
|
|
1297
|
+
' { id: "role_admin" as Role["id"], name: "Admin", permissions: ["users:manage", "reports:view", "customers:manage"] },',
|
|
1298
|
+
' { id: "role_manager" as Role["id"], name: "Manager", permissions: ["reports:view", "customers:manage"] },',
|
|
1299
|
+
' { id: "role_viewer" as Role["id"], name: "Viewer", permissions: ["reports:view"] },',
|
|
1300
|
+
"];",
|
|
1301
|
+
"",
|
|
1302
|
+
"export const users: User[] = [",
|
|
1303
|
+
' { id: "user_1" as User["id"], email: "admin@example.com", fullName: "Admin User", role: "ADMIN", createdAt: now },',
|
|
1304
|
+
' { id: "user_2" as User["id"], email: "manager@example.com", fullName: "Sales Manager", role: "DEVELOPER", createdAt: now },',
|
|
1305
|
+
"];",
|
|
1306
|
+
"",
|
|
1307
|
+
"export const customers: Customer[] = [",
|
|
1308
|
+
' { id: "customer_1" as Customer["id"], name: "Northwind", ownerId: users[1].id, status: "ACTIVE", annualValue: 125000, createdAt: now },',
|
|
1309
|
+
' { id: "customer_2" as Customer["id"], name: "Acme Corp", ownerId: users[1].id, status: "LEAD", annualValue: 82000, createdAt: now },',
|
|
1310
|
+
' { id: "customer_3" as Customer["id"], name: "Globex", ownerId: users[0].id, status: "AT_RISK", annualValue: 54000, createdAt: now },',
|
|
1311
|
+
"];",
|
|
1312
|
+
"",
|
|
1313
|
+
"export const reports: ReportMetric[] = [",
|
|
1314
|
+
' { id: "report_pipeline" as ReportMetric["id"], label: "Pipeline", value: 261000, trend: "UP" },',
|
|
1315
|
+
' { id: "report_active_customers" as ReportMetric["id"], label: "Active Customers", value: 1, trend: "FLAT" },',
|
|
1316
|
+
' { id: "report_risk" as ReportMetric["id"], label: "At Risk", value: 1, trend: "DOWN" },',
|
|
1317
|
+
"];",
|
|
1318
|
+
"",
|
|
1319
|
+
"export function getDashboardSummary(): DashboardSummary {",
|
|
1320
|
+
" return { customers, users, roles, reports };",
|
|
1321
|
+
"}",
|
|
1322
|
+
].join("\n"));
|
|
1323
|
+
|
|
1324
|
+
writeTextFile(path.join(targetDir, "apps/backend/src/server.ts"), [
|
|
1325
|
+
'import Fastify from "fastify";',
|
|
1326
|
+
'import cors from "@fastify/cors";',
|
|
1327
|
+
'import { z } from "zod";',
|
|
1328
|
+
'import { customers, getDashboardSummary, reports, roles, users } from "./data.js";',
|
|
1329
|
+
"",
|
|
1330
|
+
"const app = Fastify({ logger: true });",
|
|
1331
|
+
"await app.register(cors, { origin: true });",
|
|
1332
|
+
"",
|
|
1333
|
+
'app.get("/health", async () => ({ ok: true, service: "agent-enderun-backend" }));',
|
|
1334
|
+
'app.get("/api/v1/dashboard", async () => ({ data: getDashboardSummary() }));',
|
|
1335
|
+
'app.get("/api/v1/users", async () => ({ data: users }));',
|
|
1336
|
+
'app.get("/api/v1/roles", async () => ({ data: roles }));',
|
|
1337
|
+
'app.get("/api/v1/customers", async () => ({ data: customers }));',
|
|
1338
|
+
'app.get("/api/v1/reports", async () => ({ data: reports }));',
|
|
1339
|
+
"",
|
|
1340
|
+
'app.post("/api/v1/auth/login", async (request, reply) => {',
|
|
1341
|
+
" const body = z.object({ email: z.string().email(), password: z.string().min(1) }).safeParse(request.body);",
|
|
1342
|
+
" if (!body.success) return reply.code(400).send({ error: { code: \"VALIDATION_ERROR\", message: \"Invalid login payload\" } });",
|
|
1343
|
+
"",
|
|
1344
|
+
" const user = users.find((item) => item.email === body.data.email) || users[0];",
|
|
1345
|
+
" return { data: { user, token: \"demo-token\", expiresAt: new Date(Date.now() + 3600000).toISOString() } };",
|
|
1346
|
+
"});",
|
|
1347
|
+
"",
|
|
1348
|
+
"const port = Number(process.env.PORT || 4000);",
|
|
1349
|
+
"await app.listen({ port, host: \"0.0.0.0\" });",
|
|
1350
|
+
].join("\n"));
|
|
1351
|
+
|
|
1352
|
+
writeTextFile(path.join(targetDir, "apps/backend/README.md"), [
|
|
1353
|
+
`# ${spec.title} Backend`,
|
|
1354
|
+
"",
|
|
1355
|
+
"Fastify API generated by Agent Enderun.",
|
|
1356
|
+
"",
|
|
1357
|
+
"## Commands",
|
|
1358
|
+
"",
|
|
1359
|
+
"- `npm run dev`",
|
|
1360
|
+
"- `npm run build`",
|
|
1361
|
+
"- `npm run test`",
|
|
1362
|
+
].join("\n"));
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
function createWebFiles(spec) {
|
|
1366
|
+
writeJsonFile(path.join(targetDir, "apps/web/package.json"), {
|
|
1367
|
+
name: "@agent-enderun/web",
|
|
1368
|
+
version: "0.1.0",
|
|
1369
|
+
private: true,
|
|
1370
|
+
type: "module",
|
|
1371
|
+
scripts: {
|
|
1372
|
+
dev: "vite --host 0.0.0.0",
|
|
1373
|
+
build: "tsc -p tsconfig.json && vite build",
|
|
1374
|
+
preview: "vite preview",
|
|
1375
|
+
test: "vitest run",
|
|
1376
|
+
},
|
|
1377
|
+
dependencies: {
|
|
1378
|
+
"@vitejs/plugin-react": "^5.0.0",
|
|
1379
|
+
vite: "^7.0.0",
|
|
1380
|
+
react: "^19.0.0",
|
|
1381
|
+
"react-dom": "^19.0.0",
|
|
1382
|
+
"lucide-react": "^0.468.0",
|
|
1383
|
+
},
|
|
1384
|
+
devDependencies: {
|
|
1385
|
+
"@types/react": "^19.0.0",
|
|
1386
|
+
"@types/react-dom": "^19.0.0",
|
|
1387
|
+
typescript: "^5.9.3",
|
|
1388
|
+
vitest: "^3.0.5",
|
|
1389
|
+
},
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
writeJsonFile(path.join(targetDir, "apps/web/tsconfig.json"), {
|
|
1393
|
+
extends: "../../tsconfig.json",
|
|
1394
|
+
compilerOptions: {
|
|
1395
|
+
jsx: "react-jsx",
|
|
1396
|
+
module: "NodeNext",
|
|
1397
|
+
moduleResolution: "NodeNext",
|
|
1398
|
+
target: "ES2022",
|
|
1399
|
+
strict: true,
|
|
1400
|
+
skipLibCheck: true,
|
|
1401
|
+
},
|
|
1402
|
+
include: ["src/**/*.ts", "src/**/*.tsx"],
|
|
1403
|
+
});
|
|
1404
|
+
|
|
1405
|
+
writeTextFile(path.join(targetDir, "apps/web/index.html"), [
|
|
1406
|
+
'<div id="root"></div>',
|
|
1407
|
+
'<script type="module" src="/src/main.tsx"></script>',
|
|
1408
|
+
].join("\n"));
|
|
1409
|
+
|
|
1410
|
+
writeTextFile(path.join(targetDir, "apps/web/src/main.tsx"), [
|
|
1411
|
+
'import React from "react";',
|
|
1412
|
+
'import { createRoot } from "react-dom/client";',
|
|
1413
|
+
'import { App } from "./App.js";',
|
|
1414
|
+
'import "./styles.css";',
|
|
1415
|
+
"",
|
|
1416
|
+
'createRoot(document.getElementById("root") as HTMLElement).render(',
|
|
1417
|
+
" <React.StrictMode>",
|
|
1418
|
+
" <App />",
|
|
1419
|
+
" </React.StrictMode>,",
|
|
1420
|
+
");",
|
|
1421
|
+
].join("\n"));
|
|
1422
|
+
|
|
1423
|
+
writeTextFile(path.join(targetDir, "apps/web/src/App.tsx"), [
|
|
1424
|
+
'import { BarChart3, ShieldCheck, UsersRound } from "lucide-react";',
|
|
1425
|
+
"",
|
|
1426
|
+
"const metrics = [",
|
|
1427
|
+
' { label: "Pipeline", value: "$261K", tone: "green" },',
|
|
1428
|
+
' { label: "Active customers", value: "18", tone: "blue" },',
|
|
1429
|
+
' { label: "At risk", value: "3", tone: "red" },',
|
|
1430
|
+
"];",
|
|
1431
|
+
"",
|
|
1432
|
+
"const customers = [",
|
|
1433
|
+
' { name: "Northwind", status: "Active", owner: "Sales Manager", value: "$125K" },',
|
|
1434
|
+
' { name: "Acme Corp", status: "Lead", owner: "Sales Manager", value: "$82K" },',
|
|
1435
|
+
' { name: "Globex", status: "At risk", owner: "Admin User", value: "$54K" },',
|
|
1436
|
+
"];",
|
|
1437
|
+
"",
|
|
1438
|
+
"export function App() {",
|
|
1439
|
+
" return (",
|
|
1440
|
+
' <main className="shell">',
|
|
1441
|
+
' <aside className="sidebar" aria-label="Primary navigation">',
|
|
1442
|
+
' <div className="brand">AE</div>',
|
|
1443
|
+
' <nav>',
|
|
1444
|
+
' <a className="active" href="#dashboard"><BarChart3 size={18} /> Dashboard</a>',
|
|
1445
|
+
' <a href="#users"><UsersRound size={18} /> Users</a>',
|
|
1446
|
+
' <a href="#roles"><ShieldCheck size={18} /> Roles</a>',
|
|
1447
|
+
" </nav>",
|
|
1448
|
+
" </aside>",
|
|
1449
|
+
"",
|
|
1450
|
+
' <section className="workspace">',
|
|
1451
|
+
' <header className="topbar">',
|
|
1452
|
+
" <div>",
|
|
1453
|
+
` <p>${spec.domain}</p>`,
|
|
1454
|
+
` <h1>${spec.title}</h1>`,
|
|
1455
|
+
" </div>",
|
|
1456
|
+
' <button type="button">New customer</button>',
|
|
1457
|
+
" </header>",
|
|
1458
|
+
"",
|
|
1459
|
+
' <section className="metrics" aria-label="Report metrics">',
|
|
1460
|
+
" {metrics.map((metric) => (",
|
|
1461
|
+
' <article className={`metric ${metric.tone}`} key={metric.label}>',
|
|
1462
|
+
" <span>{metric.label}</span>",
|
|
1463
|
+
" <strong>{metric.value}</strong>",
|
|
1464
|
+
" </article>",
|
|
1465
|
+
" ))}",
|
|
1466
|
+
" </section>",
|
|
1467
|
+
"",
|
|
1468
|
+
' <section className="panel">',
|
|
1469
|
+
" <div>",
|
|
1470
|
+
" <h2>Customers</h2>",
|
|
1471
|
+
" <p>Ownership, value and status at a glance.</p>",
|
|
1472
|
+
" </div>",
|
|
1473
|
+
' <div className="table">',
|
|
1474
|
+
" {customers.map((customer) => (",
|
|
1475
|
+
' <div className="row" key={customer.name}>',
|
|
1476
|
+
" <strong>{customer.name}</strong>",
|
|
1477
|
+
" <span>{customer.status}</span>",
|
|
1478
|
+
" <span>{customer.owner}</span>",
|
|
1479
|
+
" <b>{customer.value}</b>",
|
|
1480
|
+
" </div>",
|
|
1481
|
+
" ))}",
|
|
1482
|
+
" </div>",
|
|
1483
|
+
" </section>",
|
|
1484
|
+
" </section>",
|
|
1485
|
+
" </main>",
|
|
1486
|
+
" );",
|
|
1487
|
+
"}",
|
|
1488
|
+
].join("\n"));
|
|
1489
|
+
|
|
1490
|
+
writeTextFile(path.join(targetDir, "apps/web/src/styles.css"), [
|
|
1491
|
+
":root {",
|
|
1492
|
+
" color: #172026;",
|
|
1493
|
+
" background: #f4f7f6;",
|
|
1494
|
+
" font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;",
|
|
1495
|
+
"}",
|
|
1496
|
+
"",
|
|
1497
|
+
"* { box-sizing: border-box; }",
|
|
1498
|
+
"body { margin: 0; }",
|
|
1499
|
+
"button { font: inherit; }",
|
|
1500
|
+
"",
|
|
1501
|
+
".shell {",
|
|
1502
|
+
" min-height: 100vh;",
|
|
1503
|
+
" display: grid;",
|
|
1504
|
+
" grid-template-columns: 240px 1fr;",
|
|
1505
|
+
"}",
|
|
1506
|
+
"",
|
|
1507
|
+
".sidebar {",
|
|
1508
|
+
" background: #102022;",
|
|
1509
|
+
" color: #eef6f2;",
|
|
1510
|
+
" padding: 24px;",
|
|
1511
|
+
"}",
|
|
1512
|
+
"",
|
|
1513
|
+
".brand {",
|
|
1514
|
+
" width: 40px;",
|
|
1515
|
+
" height: 40px;",
|
|
1516
|
+
" display: grid;",
|
|
1517
|
+
" place-items: center;",
|
|
1518
|
+
" background: #d8f36a;",
|
|
1519
|
+
" color: #102022;",
|
|
1520
|
+
" font-weight: 800;",
|
|
1521
|
+
" border-radius: 8px;",
|
|
1522
|
+
" margin-bottom: 32px;",
|
|
1523
|
+
"}",
|
|
1524
|
+
"",
|
|
1525
|
+
"nav { display: grid; gap: 8px; }",
|
|
1526
|
+
"nav a {",
|
|
1527
|
+
" color: inherit;",
|
|
1528
|
+
" text-decoration: none;",
|
|
1529
|
+
" display: flex;",
|
|
1530
|
+
" gap: 10px;",
|
|
1531
|
+
" align-items: center;",
|
|
1532
|
+
" padding: 10px 12px;",
|
|
1533
|
+
" border-radius: 8px;",
|
|
1534
|
+
"}",
|
|
1535
|
+
"nav a.active, nav a:hover { background: rgba(255,255,255,0.12); }",
|
|
1536
|
+
"",
|
|
1537
|
+
".workspace { padding: 32px; }",
|
|
1538
|
+
".topbar {",
|
|
1539
|
+
" display: flex;",
|
|
1540
|
+
" justify-content: space-between;",
|
|
1541
|
+
" align-items: center;",
|
|
1542
|
+
" gap: 24px;",
|
|
1543
|
+
" margin-bottom: 24px;",
|
|
1544
|
+
"}",
|
|
1545
|
+
".topbar p { margin: 0 0 4px; color: #58666a; font-size: 14px; }",
|
|
1546
|
+
".topbar h1 { margin: 0; font-size: 32px; letter-spacing: 0; }",
|
|
1547
|
+
".topbar button {",
|
|
1548
|
+
" border: 0;",
|
|
1549
|
+
" border-radius: 8px;",
|
|
1550
|
+
" background: #176b5d;",
|
|
1551
|
+
" color: white;",
|
|
1552
|
+
" padding: 10px 14px;",
|
|
1553
|
+
"}",
|
|
1554
|
+
"",
|
|
1555
|
+
".metrics {",
|
|
1556
|
+
" display: grid;",
|
|
1557
|
+
" grid-template-columns: repeat(3, minmax(0, 1fr));",
|
|
1558
|
+
" gap: 16px;",
|
|
1559
|
+
" margin-bottom: 24px;",
|
|
1560
|
+
"}",
|
|
1561
|
+
".metric, .panel {",
|
|
1562
|
+
" background: white;",
|
|
1563
|
+
" border: 1px solid #d9e3e0;",
|
|
1564
|
+
" border-radius: 8px;",
|
|
1565
|
+
"}",
|
|
1566
|
+
".metric { padding: 18px; }",
|
|
1567
|
+
".metric span { display: block; color: #58666a; margin-bottom: 8px; }",
|
|
1568
|
+
".metric strong { font-size: 28px; }",
|
|
1569
|
+
".metric.green { border-top: 4px solid #49a078; }",
|
|
1570
|
+
".metric.blue { border-top: 4px solid #3f7cac; }",
|
|
1571
|
+
".metric.red { border-top: 4px solid #d95d39; }",
|
|
1572
|
+
"",
|
|
1573
|
+
".panel { padding: 20px; }",
|
|
1574
|
+
".panel h2 { margin: 0 0 4px; font-size: 20px; }",
|
|
1575
|
+
".panel p { margin: 0 0 18px; color: #58666a; }",
|
|
1576
|
+
".table { display: grid; gap: 8px; }",
|
|
1577
|
+
".row {",
|
|
1578
|
+
" display: grid;",
|
|
1579
|
+
" grid-template-columns: 1.4fr 0.8fr 1fr 0.6fr;",
|
|
1580
|
+
" gap: 16px;",
|
|
1581
|
+
" align-items: center;",
|
|
1582
|
+
" padding: 12px;",
|
|
1583
|
+
" border-radius: 8px;",
|
|
1584
|
+
" background: #f7faf9;",
|
|
1585
|
+
"}",
|
|
1586
|
+
"",
|
|
1587
|
+
"@media (max-width: 760px) {",
|
|
1588
|
+
" .shell { grid-template-columns: 1fr; }",
|
|
1589
|
+
" .sidebar { position: static; }",
|
|
1590
|
+
" .metrics { grid-template-columns: 1fr; }",
|
|
1591
|
+
" .topbar { align-items: flex-start; flex-direction: column; }",
|
|
1592
|
+
" .row { grid-template-columns: 1fr; }",
|
|
1593
|
+
"}",
|
|
1594
|
+
].join("\n"));
|
|
1595
|
+
|
|
1596
|
+
writeTextFile(path.join(targetDir, "apps/web/README.md"), [
|
|
1597
|
+
`# ${spec.title} Web`,
|
|
1598
|
+
"",
|
|
1599
|
+
"React dashboard generated by Agent Enderun.",
|
|
1600
|
+
"",
|
|
1601
|
+
"## Commands",
|
|
1602
|
+
"",
|
|
1603
|
+
"- `npm run dev`",
|
|
1604
|
+
"- `npm run build`",
|
|
1605
|
+
"- `npm run test`",
|
|
1606
|
+
].join("\n"));
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
function updateProjectDocs(spec) {
|
|
1610
|
+
const frameworkDir = getFrameworkDir();
|
|
1611
|
+
const docsDir = path.join(targetDir, frameworkDir, "docs");
|
|
1612
|
+
const apiDir = path.join(docsDir, "api");
|
|
1613
|
+
ensureDir(apiDir);
|
|
1614
|
+
|
|
1615
|
+
writeTextFile(path.join(docsDir, "project-docs.md"), [
|
|
1616
|
+
`# ${spec.title} Requirements`,
|
|
1617
|
+
"",
|
|
1618
|
+
"## Request",
|
|
1619
|
+
"",
|
|
1620
|
+
spec.rawDescription,
|
|
1621
|
+
"",
|
|
1622
|
+
"## Generated Scope",
|
|
1623
|
+
"",
|
|
1624
|
+
`- Domain: ${spec.domain}`,
|
|
1625
|
+
`- Auth: ${spec.modules.auth ? "yes" : "no"}`,
|
|
1626
|
+
`- Users: ${spec.modules.users ? "yes" : "no"}`,
|
|
1627
|
+
`- Roles: ${spec.modules.roles ? "yes" : "no"}`,
|
|
1628
|
+
`- Reports: ${spec.modules.reports ? "yes" : "no"}`,
|
|
1629
|
+
"",
|
|
1630
|
+
"## Architecture",
|
|
1631
|
+
"",
|
|
1632
|
+
"- `apps/backend`: Fastify API",
|
|
1633
|
+
"- `apps/web`: React dashboard",
|
|
1634
|
+
"- `packages/shared-types`: Contract-first shared TypeScript types",
|
|
1635
|
+
].join("\n"));
|
|
1636
|
+
|
|
1637
|
+
writeTextFile(path.join(apiDir, "README.md"), [
|
|
1638
|
+
"# API Registry",
|
|
1639
|
+
"",
|
|
1640
|
+
"- `POST /api/v1/auth/login`",
|
|
1641
|
+
"- `GET /api/v1/dashboard`",
|
|
1642
|
+
"- `GET /api/v1/users`",
|
|
1643
|
+
"- `GET /api/v1/roles`",
|
|
1644
|
+
"- `GET /api/v1/customers`",
|
|
1645
|
+
"- `GET /api/v1/reports`",
|
|
1646
|
+
].join("\n"));
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
function updateMemoryForGeneratedApp(spec, traceId) {
|
|
1650
|
+
const memoryPath = getMemoryPath();
|
|
1651
|
+
if (!fs.existsSync(memoryPath)) return;
|
|
1652
|
+
|
|
1653
|
+
const today = new Date().toISOString().split("T")[0];
|
|
1654
|
+
const history = [
|
|
1655
|
+
`### ${today} — Generated ${spec.title}`,
|
|
1656
|
+
"",
|
|
1657
|
+
"- **Agent:** @manager",
|
|
1658
|
+
`- **Trace ID:** ${traceId}`,
|
|
1659
|
+
"- **Action:** Created full-stack starter from natural language request.",
|
|
1660
|
+
"- **Files:** apps/backend, apps/web, shared-types, project docs",
|
|
1661
|
+
].join("\n");
|
|
1662
|
+
|
|
1663
|
+
updateProjectMemoryCommand("HISTORY", history);
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
async function collectCreateAppDescription(args) {
|
|
1667
|
+
const initial = args.join(" ").trim();
|
|
1668
|
+
if (initial) return initial;
|
|
1669
|
+
|
|
1670
|
+
const readline = await import("readline/promises");
|
|
1671
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1672
|
+
try {
|
|
1673
|
+
const idea = await rl.question("What do you want to build? ");
|
|
1674
|
+
const platform = await rl.question("Platform? (full-stack/web/backend) ");
|
|
1675
|
+
const auth = await rl.question("Auth and roles? (yes/no) ");
|
|
1676
|
+
const reports = await rl.question("Reports/dashboard? (yes/no) ");
|
|
1677
|
+
return [idea, platform, auth.includes("y") ? "with auth and roles" : "", reports.includes("y") ? "with reports dashboard" : ""].filter(Boolean).join(" ");
|
|
1678
|
+
} finally {
|
|
1679
|
+
rl.close();
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
async function createAppCommand(args) {
|
|
1684
|
+
const description = await collectCreateAppDescription(args);
|
|
1685
|
+
const spec = inferAppSpec(description);
|
|
1686
|
+
const traceId = generateULID();
|
|
1687
|
+
|
|
1688
|
+
ensureDir(path.join(targetDir, "apps/backend"));
|
|
1689
|
+
ensureDir(path.join(targetDir, "apps/web"));
|
|
1690
|
+
|
|
1691
|
+
createBackendFiles(spec);
|
|
1692
|
+
createWebFiles(spec);
|
|
1693
|
+
updateProjectDocs(spec);
|
|
1694
|
+
|
|
1695
|
+
const sharedTypesPath = path.join(targetDir, "packages/shared-types/src/index.ts");
|
|
1696
|
+
if (fs.existsSync(sharedTypesPath)) {
|
|
1697
|
+
const existing = fs.readFileSync(sharedTypesPath, "utf8");
|
|
1698
|
+
fs.writeFileSync(sharedTypesPath, buildSharedTypesContent(existing));
|
|
1699
|
+
updateContractHashFile();
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
const activeTraceId = traceNewCommand(`Generate ${spec.title} from natural language request`, "manager", "P1") || traceId;
|
|
1703
|
+
updateMemoryForGeneratedApp(spec, activeTraceId);
|
|
1704
|
+
|
|
1705
|
+
console.log(`\n✅ Created ${spec.title}`);
|
|
1706
|
+
console.log("📁 Generated apps/backend and apps/web");
|
|
1707
|
+
console.log("📜 Updated project docs and shared-types contract");
|
|
1708
|
+
console.log("\nNext commands:");
|
|
1709
|
+
console.log(" npm install");
|
|
1710
|
+
console.log(" npm run enderun:build");
|
|
1711
|
+
console.log(" agent-enderun frontend:dev\n");
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1102
1714
|
// --- MAIN DISPATCHER ---
|
|
1103
1715
|
|
|
1104
1716
|
async function main() {
|
|
@@ -1123,6 +1735,12 @@ async function main() {
|
|
|
1123
1735
|
traceNewCommand(args[0], args[1], args[2]);
|
|
1124
1736
|
}
|
|
1125
1737
|
break;
|
|
1738
|
+
case "create-app":
|
|
1739
|
+
case "new":
|
|
1740
|
+
case "start":
|
|
1741
|
+
case "build-app":
|
|
1742
|
+
await createAppCommand(args);
|
|
1743
|
+
break;
|
|
1126
1744
|
case "verify-contract":
|
|
1127
1745
|
verifyContractCommand();
|
|
1128
1746
|
break;
|
|
@@ -1254,11 +1872,16 @@ async function main() {
|
|
|
1254
1872
|
console.log(`v${FRAMEWORK_VERSION}`);
|
|
1255
1873
|
break;
|
|
1256
1874
|
default:
|
|
1875
|
+
if (command && (command.includes(" ") || args.length > 0)) {
|
|
1876
|
+
await createAppCommand([command, ...args]);
|
|
1877
|
+
break;
|
|
1878
|
+
}
|
|
1257
1879
|
console.log(`
|
|
1258
1880
|
🤖 Agent Enderun CLI (v${FRAMEWORK_VERSION})
|
|
1259
1881
|
|
|
1260
1882
|
Available Commands:
|
|
1261
1883
|
init [adapter] Initialize the framework (gemini, claude, cursor, codex)
|
|
1884
|
+
create-app <idea> Generate a full-stack starter from natural language
|
|
1262
1885
|
check Full health check
|
|
1263
1886
|
check:security Run security audit scan
|
|
1264
1887
|
check:compliance Run constitution compliance check
|
|
@@ -1273,6 +1896,7 @@ Available Commands:
|
|
|
1273
1896
|
|
|
1274
1897
|
Example:
|
|
1275
1898
|
agent-enderun trace:new "Auth module design" backend P1
|
|
1899
|
+
agent-enderun create-app "CRM dashboard with auth, users, roles, reports"
|
|
1276
1900
|
`);
|
|
1277
1901
|
break;
|
|
1278
1902
|
}
|