agent-enderun 0.5.0 → 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 (84) hide show
  1. package/.enderun/BRAIN_DASHBOARD.md +0 -0
  2. package/.enderun/PROJECT_MEMORY.md +40 -1
  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 +38 -10
  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 +7 -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 +59 -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 +8 -4
  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 +10 -5
  47. package/LICENSE +0 -0
  48. package/README.md +93 -45
  49. package/bin/cli.js +633 -3
  50. package/bin/update-contract.js +63 -0
  51. package/claude.md +2 -2
  52. package/codex.md +2 -2
  53. package/cursor.md +2 -2
  54. package/docs/README.md +23 -0
  55. package/gemini-extension.json +8 -2
  56. package/gemini.md +2 -2
  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 +16 -0
  61. package/packages/framework-mcp/dist/tools/contract.js +13 -7
  62. package/packages/framework-mcp/dist/tools/framework.js +25 -0
  63. package/packages/framework-mcp/dist/tools/knowledge.js +75 -11
  64. package/packages/framework-mcp/dist/tools/messages.js +73 -11
  65. package/packages/framework-mcp/dist/tools/repository.js +24 -3
  66. package/packages/framework-mcp/dist/utils.js +2 -2
  67. package/packages/framework-mcp/package.json +2 -2
  68. package/packages/framework-mcp/src/schemas.ts +20 -0
  69. package/packages/framework-mcp/src/tools/contract.ts +14 -7
  70. package/packages/framework-mcp/src/tools/framework.ts +24 -0
  71. package/packages/framework-mcp/src/tools/knowledge.ts +86 -12
  72. package/packages/framework-mcp/src/tools/messages.ts +80 -11
  73. package/packages/framework-mcp/src/tools/repository.ts +24 -3
  74. package/packages/framework-mcp/src/utils.ts +2 -2
  75. package/packages/shared-types/README.md +0 -0
  76. package/packages/shared-types/contract.version.json +6 -3
  77. package/packages/shared-types/package.json +4 -4
  78. package/packages/shared-types/src/index.ts +0 -0
  79. package/packages/shared-types/tsconfig.json +0 -0
  80. package/panda.config.ts +5 -1
  81. package/tsconfig.json +0 -0
  82. package/.enderun/ENDERUN.md +0 -205
  83. package/packages/framework-mcp/dist/index.d.ts +0 -1
  84. 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
 
@@ -525,6 +542,12 @@ async function initCommand(selectedAdapter) {
525
542
  const buildCmd = pkgMgr === "npm" ? "npm run enderun:build" : `${pkgMgr} run enderun:build`;
526
543
 
527
544
  console.log(`\n✨ Framework scaffolded! (v${FRAMEWORK_VERSION})`);
545
+
546
+ // Allow skipping install in test/CI environments
547
+ if (process.env.ENDERUN_SKIP_INSTALL === "1") {
548
+ console.log("\n⏭️ Skipping install steps (ENDERUN_SKIP_INSTALL=1).");
549
+ return;
550
+ }
528
551
 
529
552
  try {
530
553
  const { execSync } = await import("child_process");
@@ -724,6 +747,8 @@ function copyDir(src, dest, skipSet = new Set(), nonDestructive = false, framewo
724
747
  if (textExtensions.includes(ext)) {
725
748
  let content = fs.readFileSync(srcPath, "utf8");
726
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}/`);
727
752
 
728
753
  // Sanitize workspace: protocol
729
754
  if (ext === ".json") {
@@ -745,6 +770,8 @@ function copyDir(src, dest, skipSet = new Set(), nonDestructive = false, framewo
745
770
 
746
771
  content = content.replace(/\{\{FRAMEWORK_DIR\}\}/g, frameworkDir);
747
772
  content = content.replace(/\{\{ADAPTER\}\}/g, currentAdapter);
773
+ // Fallback: replace any residual hardcoded .enderun/ paths
774
+ content = content.replace(/\.enderun\//g, `${frameworkDir}/`);
748
775
 
749
776
  fs.writeFileSync(destPath, content);
750
777
  } else {
@@ -819,6 +846,7 @@ function traceNewCommand(description, agent = "manager", priority = "P2") {
819
846
  fs.writeFileSync(memoryPath, updated);
820
847
  console.log(`\n✅ New Trace ID created: ${traceId}`);
821
848
  console.log(`📝 Added to task list: ${description}\n`);
849
+ return traceId;
822
850
  } finally {
823
851
  releaseMemoryLock(lockPath);
824
852
  }
@@ -844,7 +872,10 @@ function verifyContractCommand() {
844
872
  const files = walk(sharedDir).sort();
845
873
  const h = crypto.createHash("sha256");
846
874
  for (const f of files) {
875
+ h.update(path.relative(targetDir, f));
876
+ h.update("\0");
847
877
  h.update(fs.readFileSync(f));
878
+ h.update("\0");
848
879
  }
849
880
  const currentHash = h.digest("hex");
850
881
 
@@ -1093,6 +1124,593 @@ function updateProjectMemoryCommand(section, content) {
1093
1124
  }
1094
1125
  }
1095
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
+
1096
1714
  // --- MAIN DISPATCHER ---
1097
1715
 
1098
1716
  async function main() {
@@ -1117,6 +1735,12 @@ async function main() {
1117
1735
  traceNewCommand(args[0], args[1], args[2]);
1118
1736
  }
1119
1737
  break;
1738
+ case "create-app":
1739
+ case "new":
1740
+ case "start":
1741
+ case "build-app":
1742
+ await createAppCommand(args);
1743
+ break;
1120
1744
  case "verify-contract":
1121
1745
  verifyContractCommand();
1122
1746
  break;
@@ -1248,11 +1872,16 @@ async function main() {
1248
1872
  console.log(`v${FRAMEWORK_VERSION}`);
1249
1873
  break;
1250
1874
  default:
1875
+ if (command && (command.includes(" ") || args.length > 0)) {
1876
+ await createAppCommand([command, ...args]);
1877
+ break;
1878
+ }
1251
1879
  console.log(`
1252
1880
  🤖 Agent Enderun CLI (v${FRAMEWORK_VERSION})
1253
1881
 
1254
1882
  Available Commands:
1255
1883
  init [adapter] Initialize the framework (gemini, claude, cursor, codex)
1884
+ create-app <idea> Generate a full-stack starter from natural language
1256
1885
  check Full health check
1257
1886
  check:security Run security audit scan
1258
1887
  check:compliance Run constitution compliance check
@@ -1267,6 +1896,7 @@ Available Commands:
1267
1896
 
1268
1897
  Example:
1269
1898
  agent-enderun trace:new "Auth module design" backend P1
1899
+ agent-enderun create-app "CRM dashboard with auth, users, roles, reports"
1270
1900
  `);
1271
1901
  break;
1272
1902
  }