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.
Files changed (81) hide show
  1. package/.enderun/BRAIN_DASHBOARD.md +0 -0
  2. package/.enderun/PROJECT_MEMORY.md +27 -2
  3. package/.enderun/STATUS.md +2 -0
  4. package/.enderun/agents/analyst.md +8 -8
  5. package/.enderun/agents/backend.md +11 -11
  6. package/.enderun/agents/explorer.md +4 -4
  7. package/.enderun/agents/frontend.md +7 -7
  8. package/.enderun/agents/git.md +5 -5
  9. package/.enderun/agents/manager.md +12 -12
  10. package/.enderun/agents/mobile.md +5 -5
  11. package/.enderun/agents/native.md +5 -5
  12. package/.enderun/benchmarks/.gitkeep +0 -0
  13. package/.enderun/cli-commands.json +0 -0
  14. package/.enderun/config.json +0 -0
  15. package/.enderun/docs/api/README.md +0 -0
  16. package/.enderun/docs/api/auth.md +0 -0
  17. package/.enderun/docs/api/errors.md +0 -0
  18. package/.enderun/docs/error-handling.md +0 -0
  19. package/.enderun/docs/privacy.md +0 -0
  20. package/.enderun/docs/project-docs.md +0 -0
  21. package/.enderun/docs/security.md +0 -0
  22. package/.enderun/docs/tech-stack.md +1 -1
  23. package/.enderun/docs/troubleshooting.md +0 -0
  24. package/.enderun/knowledge/api_design_rules.md +0 -0
  25. package/.enderun/knowledge/async_error_handling.md +0 -0
  26. package/.enderun/knowledge/branded_types_pattern.md +0 -0
  27. package/.enderun/knowledge/code_review_checklist.md +0 -0
  28. package/.enderun/knowledge/contract_versioning.md +0 -0
  29. package/.enderun/knowledge/database_migration.md +0 -0
  30. package/.enderun/knowledge/deployment_checklist.md +0 -0
  31. package/.enderun/knowledge/git_commit_strategy.md +0 -0
  32. package/.enderun/knowledge/hermes_protocol.md +0 -0
  33. package/.enderun/knowledge/legacy_onboarding.md +0 -0
  34. package/.enderun/knowledge/monitoring_setup.md +0 -0
  35. package/.enderun/knowledge/performance_guidelines.md +0 -0
  36. package/.enderun/knowledge/repository_patterns.md +0 -0
  37. package/.enderun/knowledge/responsive_design_standards.md +0 -0
  38. package/.enderun/knowledge/security_scanning.md +0 -0
  39. package/.enderun/knowledge/testing_standards.md +1 -1
  40. package/.enderun/knowledge/troubleshooting_guide.md +0 -0
  41. package/.enderun/knowledge/zero_ui_library_policy.md +0 -0
  42. package/.enderun/logs/.gitkeep +0 -0
  43. package/.enderun/messages/.gitkeep +0 -0
  44. package/.enderun/monitoring/.gitkeep +0 -0
  45. package/.env.example +0 -0
  46. package/ENDERUN.md +3 -3
  47. package/LICENSE +0 -0
  48. package/README.md +38 -1
  49. package/bin/cli.js +627 -3
  50. package/bin/update-contract.js +21 -2
  51. package/claude.md +1 -1
  52. package/codex.md +1 -1
  53. package/cursor.md +1 -1
  54. package/docs/README.md +23 -0
  55. package/gemini-extension.json +8 -2
  56. package/gemini.md +1 -1
  57. package/mcp.json +0 -0
  58. package/package.json +4 -3
  59. package/packages/framework-mcp/dist/index.js +0 -0
  60. package/packages/framework-mcp/dist/schemas.js +2 -0
  61. package/packages/framework-mcp/dist/tools/contract.js +13 -7
  62. package/packages/framework-mcp/dist/tools/knowledge.js +1 -1
  63. package/packages/framework-mcp/dist/tools/messages.js +15 -6
  64. package/packages/framework-mcp/dist/tools/repository.js +24 -3
  65. package/packages/framework-mcp/dist/utils.js +2 -2
  66. package/packages/framework-mcp/package.json +2 -2
  67. package/packages/framework-mcp/src/schemas.ts +2 -0
  68. package/packages/framework-mcp/src/tools/contract.ts +14 -7
  69. package/packages/framework-mcp/src/tools/knowledge.ts +1 -1
  70. package/packages/framework-mcp/src/tools/messages.ts +17 -8
  71. package/packages/framework-mcp/src/tools/repository.ts +24 -3
  72. package/packages/framework-mcp/src/utils.ts +2 -2
  73. package/packages/shared-types/README.md +0 -0
  74. package/packages/shared-types/contract.version.json +3 -3
  75. package/packages/shared-types/package.json +4 -4
  76. package/packages/shared-types/src/index.ts +0 -0
  77. package/packages/shared-types/tsconfig.json +0 -0
  78. package/panda.config.ts +5 -1
  79. package/tsconfig.json +0 -0
  80. package/packages/framework-mcp/dist/index.d.ts +0 -1
  81. 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", ".codex", ".enderun"];
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
  }