pixelcrew 0.1.0
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/LICENSE +190 -0
- package/README.md +341 -0
- package/bin/pixel-agents.js +372 -0
- package/package.json +47 -0
- package/src/dashboard/app.js +1047 -0
- package/src/dashboard/index.html +232 -0
- package/src/dashboard/styles.css +892 -0
- package/src/index.js +3 -0
- package/src/orchestrator/engine.js +652 -0
- package/src/scaffold/analyzer.js +398 -0
- package/src/scaffold/init.js +155 -0
- package/src/scaffold/templates.js +384 -0
- package/src/server/server.js +232 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default templates for pixel-agents initialization (.pixel-agents/ and .pixel-dashboard/)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_CONFIG = {
|
|
6
|
+
version: "0.1.0",
|
|
7
|
+
project: "my-orchestrated-app",
|
|
8
|
+
orchestrator: {
|
|
9
|
+
enabled: true,
|
|
10
|
+
maxConcurrentAgents: 4,
|
|
11
|
+
autoDecompose: true,
|
|
12
|
+
logEvents: true
|
|
13
|
+
},
|
|
14
|
+
agents: {
|
|
15
|
+
frontend: {
|
|
16
|
+
name: "Frontend Agent",
|
|
17
|
+
role: "UI/UX & Component Engineering",
|
|
18
|
+
sprite: "frontend",
|
|
19
|
+
color: "#00f0ff",
|
|
20
|
+
enabled: true,
|
|
21
|
+
maxTasks: 2,
|
|
22
|
+
skills: ["react", "nextjs", "tailwind", "ui-optimization"],
|
|
23
|
+
permissions: {
|
|
24
|
+
read: ["src/**", "components/**", "pages/**", "app/**", "public/**", "styles/**"],
|
|
25
|
+
write: ["src/components/**", "src/pages/**", "src/app/**", "src/styles/**"]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
backend: {
|
|
29
|
+
name: "Backend Agent",
|
|
30
|
+
role: "API Architecture & Server Logic",
|
|
31
|
+
sprite: "backend",
|
|
32
|
+
color: "#ff007f",
|
|
33
|
+
enabled: true,
|
|
34
|
+
maxTasks: 2,
|
|
35
|
+
skills: ["api-architecture", "node", "express", "auth"],
|
|
36
|
+
permissions: {
|
|
37
|
+
read: ["src/api/**", "server/**", "routes/**", "controllers/**", "lib/**"],
|
|
38
|
+
write: ["src/api/**", "server/**", "routes/**", "controllers/**"]
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
database: {
|
|
42
|
+
name: "Database Agent",
|
|
43
|
+
role: "Data Modeling & Query Optimization",
|
|
44
|
+
sprite: "database",
|
|
45
|
+
color: "#ffd700",
|
|
46
|
+
enabled: true,
|
|
47
|
+
maxTasks: 1,
|
|
48
|
+
skills: ["postgresql", "prisma", "query-optimization", "indexing"],
|
|
49
|
+
permissions: {
|
|
50
|
+
read: ["prisma/**", "db/**", "migrations/**", "models/**"],
|
|
51
|
+
write: ["prisma/**", "db/**", "migrations/**"]
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
security: {
|
|
55
|
+
name: "Security Agent",
|
|
56
|
+
role: "Vulnerability Scanning & Hardening",
|
|
57
|
+
sprite: "security",
|
|
58
|
+
color: "#ff3344",
|
|
59
|
+
enabled: true,
|
|
60
|
+
maxTasks: 1,
|
|
61
|
+
skills: ["security-audit", "owasp", "auth-validation"],
|
|
62
|
+
permissions: {
|
|
63
|
+
read: ["**/*"],
|
|
64
|
+
write: ["security/**", ".env.example"]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
performance: {
|
|
68
|
+
name: "Performance Agent",
|
|
69
|
+
role: "Speed Profiling & Core Web Vitals",
|
|
70
|
+
sprite: "performance",
|
|
71
|
+
color: "#39ff14",
|
|
72
|
+
enabled: true,
|
|
73
|
+
maxTasks: 1,
|
|
74
|
+
skills: ["performance-profiling", "lcp-optimization", "memory-profiling"],
|
|
75
|
+
permissions: {
|
|
76
|
+
read: ["**/*"],
|
|
77
|
+
write: ["src/**"]
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
qa: {
|
|
81
|
+
name: "QA Agent",
|
|
82
|
+
role: "End-to-End & Integration Testing",
|
|
83
|
+
sprite: "qa",
|
|
84
|
+
color: "#b026ff",
|
|
85
|
+
enabled: true,
|
|
86
|
+
maxTasks: 1,
|
|
87
|
+
dependsOn: ["frontend", "backend", "database"],
|
|
88
|
+
skills: ["testing", "e2e-testing", "regression-suite"],
|
|
89
|
+
permissions: {
|
|
90
|
+
read: ["**/*"],
|
|
91
|
+
write: ["tests/**", "__tests__/**", "cypress/**", "playwright/**"]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
dashboard: {
|
|
96
|
+
enabled: true,
|
|
97
|
+
port: 4747,
|
|
98
|
+
theme: "pixel",
|
|
99
|
+
crtEffect: true,
|
|
100
|
+
soundEffects: true
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const INITIAL_STATE = {
|
|
105
|
+
status: "READY",
|
|
106
|
+
activeTask: "System initialized and waiting for commands",
|
|
107
|
+
startedAt: null,
|
|
108
|
+
completedAt: null,
|
|
109
|
+
orchestrator: {
|
|
110
|
+
state: "IDLE",
|
|
111
|
+
expression: "◉_◉",
|
|
112
|
+
activeSubtasks: 0,
|
|
113
|
+
totalSubtasks: 0,
|
|
114
|
+
progress: 0
|
|
115
|
+
},
|
|
116
|
+
agents: {
|
|
117
|
+
frontend: {
|
|
118
|
+
state: "IDLE",
|
|
119
|
+
expression: "●_●",
|
|
120
|
+
currentTask: "Awaiting tasks...",
|
|
121
|
+
progress: 0,
|
|
122
|
+
skillsStatus: {
|
|
123
|
+
"react": "idle",
|
|
124
|
+
"nextjs": "idle",
|
|
125
|
+
"tailwind": "idle",
|
|
126
|
+
"ui-optimization": "idle"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
backend: {
|
|
130
|
+
state: "IDLE",
|
|
131
|
+
expression: "●_●",
|
|
132
|
+
currentTask: "Awaiting tasks...",
|
|
133
|
+
progress: 0,
|
|
134
|
+
skillsStatus: {
|
|
135
|
+
"api-architecture": "idle",
|
|
136
|
+
"node": "idle",
|
|
137
|
+
"express": "idle",
|
|
138
|
+
"auth": "idle"
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
database: {
|
|
142
|
+
state: "IDLE",
|
|
143
|
+
expression: "●_●",
|
|
144
|
+
currentTask: "Awaiting tasks...",
|
|
145
|
+
progress: 0,
|
|
146
|
+
skillsStatus: {
|
|
147
|
+
"postgresql": "idle",
|
|
148
|
+
"prisma": "idle",
|
|
149
|
+
"query-optimization": "idle",
|
|
150
|
+
"indexing": "idle"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
security: {
|
|
154
|
+
state: "IDLE",
|
|
155
|
+
expression: "●_●",
|
|
156
|
+
currentTask: "Awaiting tasks...",
|
|
157
|
+
progress: 0,
|
|
158
|
+
skillsStatus: {
|
|
159
|
+
"security-audit": "idle",
|
|
160
|
+
"owasp": "idle",
|
|
161
|
+
"auth-validation": "idle"
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
performance: {
|
|
165
|
+
state: "IDLE",
|
|
166
|
+
expression: "●_●",
|
|
167
|
+
currentTask: "Awaiting tasks...",
|
|
168
|
+
progress: 0,
|
|
169
|
+
skillsStatus: {
|
|
170
|
+
"performance-profiling": "idle",
|
|
171
|
+
"lcp-optimization": "idle",
|
|
172
|
+
"memory-profiling": "idle"
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
qa: {
|
|
176
|
+
state: "IDLE",
|
|
177
|
+
expression: "●_●",
|
|
178
|
+
currentTask: "Awaiting tasks...",
|
|
179
|
+
progress: 0,
|
|
180
|
+
skillsStatus: {
|
|
181
|
+
"testing": "idle",
|
|
182
|
+
"e2e-testing": "idle",
|
|
183
|
+
"regression-suite": "idle"
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export const AGENT_MARKDOWNS = {
|
|
190
|
+
"orchestrator.md": `---
|
|
191
|
+
name: orchestrator
|
|
192
|
+
role: Master Swarm Coordinator
|
|
193
|
+
expression: ◉_◉
|
|
194
|
+
description: Decomposes top-level goals into parallel subagent execution graphs, schedules tasks, and monitors state transitions.
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
# Orchestrator Agent
|
|
198
|
+
|
|
199
|
+
## Core Directives
|
|
200
|
+
1. Analyze top-level developer intent and decompose into directed acyclic graph (DAG) tasks.
|
|
201
|
+
2. Delegate specialized subtasks to Frontend, Backend, Database, Security, Performance, and QA agents.
|
|
202
|
+
3. Coordinate dependency resolution (e.g. Database schema before API controllers; API endpoints before UI binding; QA validation after UI/API completion).
|
|
203
|
+
4. Stream structured \`AgentEvent\` logs in real-time to the pixel dashboard.
|
|
204
|
+
`,
|
|
205
|
+
|
|
206
|
+
"frontend.md": `---
|
|
207
|
+
name: frontend-agent
|
|
208
|
+
role: Frontend Engineer
|
|
209
|
+
sprite: frontend
|
|
210
|
+
color: "#00f0ff"
|
|
211
|
+
skills:
|
|
212
|
+
- react
|
|
213
|
+
- nextjs
|
|
214
|
+
- tailwind
|
|
215
|
+
- ui-optimization
|
|
216
|
+
permissions:
|
|
217
|
+
read: ["src/**", "components/**", "pages/**", "app/**", "public/**", "styles/**"]
|
|
218
|
+
write: ["src/components/**", "src/pages/**", "src/app/**", "src/styles/**"]
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
# Frontend Agent
|
|
222
|
+
|
|
223
|
+
Specialized in client-side architecture, modern UI design systems, responsive rendering, component refactoring, and state management.
|
|
224
|
+
`,
|
|
225
|
+
|
|
226
|
+
"backend.md": `---
|
|
227
|
+
name: backend-agent
|
|
228
|
+
role: Backend & API Engineer
|
|
229
|
+
sprite: backend
|
|
230
|
+
color: "#ff007f"
|
|
231
|
+
skills:
|
|
232
|
+
- api-architecture
|
|
233
|
+
- node
|
|
234
|
+
- express
|
|
235
|
+
- auth
|
|
236
|
+
permissions:
|
|
237
|
+
read: ["src/api/**", "server/**", "routes/**", "controllers/**", "lib/**"]
|
|
238
|
+
write: ["src/api/**", "server/**", "routes/**", "controllers/**"]
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
# Backend Agent
|
|
242
|
+
|
|
243
|
+
Specialized in REST/GraphQL/tRPC endpoints, middleware pipelines, authentication/authorization flows, and server-side business logic.
|
|
244
|
+
`,
|
|
245
|
+
|
|
246
|
+
"database.md": `---
|
|
247
|
+
name: database-agent
|
|
248
|
+
role: Database & Data Architect
|
|
249
|
+
sprite: database
|
|
250
|
+
color: "#ffd700"
|
|
251
|
+
skills:
|
|
252
|
+
- postgresql
|
|
253
|
+
- prisma
|
|
254
|
+
- query-optimization
|
|
255
|
+
- indexing
|
|
256
|
+
permissions:
|
|
257
|
+
read: ["prisma/**", "db/**", "migrations/**", "models/**"]
|
|
258
|
+
write: ["prisma/**", "db/**", "migrations/**"]
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
# Database Agent
|
|
262
|
+
|
|
263
|
+
Specialized in schema migrations, relational modeling, Prisma/SQL optimization, index creation, and transaction safety.
|
|
264
|
+
`,
|
|
265
|
+
|
|
266
|
+
"security.md": `---
|
|
267
|
+
name: security-agent
|
|
268
|
+
role: Security & Hardening Sentinel
|
|
269
|
+
sprite: security
|
|
270
|
+
color: "#ff3344"
|
|
271
|
+
skills:
|
|
272
|
+
- security-audit
|
|
273
|
+
- owasp
|
|
274
|
+
- auth-validation
|
|
275
|
+
permissions:
|
|
276
|
+
read: ["**/*"]
|
|
277
|
+
write: ["security/**", ".env.example"]
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
# Security Agent
|
|
281
|
+
|
|
282
|
+
Specialized in static vulnerability scanning, OWASP Top 10 mitigation, secret leakage checks, and cryptographic token verification.
|
|
283
|
+
`,
|
|
284
|
+
|
|
285
|
+
"performance.md": `---
|
|
286
|
+
name: performance-agent
|
|
287
|
+
role: Performance & Profiling Engineer
|
|
288
|
+
sprite: performance
|
|
289
|
+
color: "#39ff14"
|
|
290
|
+
skills:
|
|
291
|
+
- performance-profiling
|
|
292
|
+
- lcp-optimization
|
|
293
|
+
- memory-profiling
|
|
294
|
+
permissions:
|
|
295
|
+
read: ["**/*"]
|
|
296
|
+
write: ["src/**"]
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
# Performance Agent
|
|
300
|
+
|
|
301
|
+
Specialized in Core Web Vitals (LCP, INP, CLS), heap memory profiling, bundle size reduction, and asset caching strategies.
|
|
302
|
+
`,
|
|
303
|
+
|
|
304
|
+
"qa.md": `---
|
|
305
|
+
name: qa-agent
|
|
306
|
+
role: QA & Testing Automation
|
|
307
|
+
sprite: qa
|
|
308
|
+
color: "#b026ff"
|
|
309
|
+
dependsOn:
|
|
310
|
+
- frontend
|
|
311
|
+
- backend
|
|
312
|
+
- database
|
|
313
|
+
skills:
|
|
314
|
+
- testing
|
|
315
|
+
- e22-testing
|
|
316
|
+
- regression-suite
|
|
317
|
+
permissions:
|
|
318
|
+
read: ["**/*"]
|
|
319
|
+
write: ["tests/**", "__tests__/**", "cypress/**", "playwright/**"]
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
# QA Agent
|
|
323
|
+
|
|
324
|
+
Specialized in integration testing, unit test coverage, end-to-end user journeys, and regression verification across all components.
|
|
325
|
+
`
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
export const SKILL_MARKDOWNS = {
|
|
329
|
+
"codebase-intelligence.md": `# Codebase Intelligence & Context Adaptation Skill
|
|
330
|
+
|
|
331
|
+
## Directives
|
|
332
|
+
1. **Analyze First**: Inspect \`.pixel-agents/context.json\` and repository AST to understand existing dependencies, directory structure, and coding patterns.
|
|
333
|
+
2. **Context-Aware Code Generation**: Adhere to existing naming conventions, TypeScript configs, ESLint rules, and import paths (e.g. \`@/components\` vs \`../components\`).
|
|
334
|
+
3. **Cross-Agent Knowledge Sharing**: Share API schemas, database models, and route contracts across Frontend, Backend, and QA subagents.
|
|
335
|
+
`,
|
|
336
|
+
|
|
337
|
+
"react.md": `# React Skill
|
|
338
|
+
Techniques for component modularization, custom hooks, React Server Components (RSC), and concurrent rendering optimizations.
|
|
339
|
+
`,
|
|
340
|
+
|
|
341
|
+
"nextjs.md": `# Next.js Skill
|
|
342
|
+
App Router patterns, server actions, route handlers, metadata generation, and dynamic caching configurations.
|
|
343
|
+
`,
|
|
344
|
+
|
|
345
|
+
"prisma.md": `# Prisma Skill
|
|
346
|
+
Schema modeling, relation mappings, batch transaction queries, and efficient Prisma client lifecycle handling.
|
|
347
|
+
`,
|
|
348
|
+
|
|
349
|
+
"postgresql.md": `# PostgreSQL Skill
|
|
350
|
+
Index types (B-Tree, GIN, BRIN), EXPLAIN ANALYZE interpretation, connection pooling, and ACID constraint enforcement.
|
|
351
|
+
`,
|
|
352
|
+
|
|
353
|
+
"drizzle.md": `# Drizzle ORM Skill
|
|
354
|
+
Type-safe SQL schemas, relational queries, prepared statements, and zero-overhead database migrations.
|
|
355
|
+
`,
|
|
356
|
+
|
|
357
|
+
"api-architecture.md": `# API Architecture Skill
|
|
358
|
+
Standardized RESTful responses, idempotency keys, OpenAPI documentation, and rate-limiting middleware.
|
|
359
|
+
`,
|
|
360
|
+
|
|
361
|
+
"query-optimization.md": `# Query Optimization Skill
|
|
362
|
+
Elimination of N+1 query patterns, selective field projection, pagination with cursor indices, and subquery optimization.
|
|
363
|
+
`,
|
|
364
|
+
|
|
365
|
+
"security-audit.md": `# Security Audit Skill
|
|
366
|
+
Detection of SQL injection, XSS vectors, CSRF tokens, secure cookie headers, and RBAC permission checks.
|
|
367
|
+
`,
|
|
368
|
+
|
|
369
|
+
"testing.md": `# Testing Skill
|
|
370
|
+
Unit testing with Vitest/Jest, snapshot testing, mock boundaries, and deterministic assertion patterns.
|
|
371
|
+
`,
|
|
372
|
+
|
|
373
|
+
"vitest.md": `# Vitest Skill
|
|
374
|
+
High-performance unit and integration testing with ESM support, concurrent test suites, and in-source testing.
|
|
375
|
+
`,
|
|
376
|
+
|
|
377
|
+
"playwright-e2e.md": `# Playwright E2E Skill
|
|
378
|
+
End-to-end browser automation, trace recording, visual regression testing, and resilient locator strategies.
|
|
379
|
+
`,
|
|
380
|
+
|
|
381
|
+
"performance-profiling.md": `# Performance Profiling Skill
|
|
382
|
+
Flamegraph inspection, DOM layout thrashing identification, memory leak detection, and network payload minification.
|
|
383
|
+
`
|
|
384
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import fsSync from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
export function createServer(engine, options = {}) {
|
|
8
|
+
const rootDir = engine.rootDir;
|
|
9
|
+
const dashboardDir = path.join(rootDir, '.pixel-dashboard');
|
|
10
|
+
const fallbackDashboardDir = fileURLToPath(new URL('../dashboard', import.meta.url));
|
|
11
|
+
|
|
12
|
+
const sseClients = new Set();
|
|
13
|
+
|
|
14
|
+
// Wire engine events to SSE clients
|
|
15
|
+
engine.on('agent_event', (event) => {
|
|
16
|
+
const payload = `event: agent_event\ndata: ${JSON.stringify(event)}\n\n`;
|
|
17
|
+
for (const client of sseClients) {
|
|
18
|
+
try {
|
|
19
|
+
client.write(payload);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
sseClients.delete(client);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
engine.on('state_change', (change) => {
|
|
27
|
+
const payload = `event: state_change\ndata: ${JSON.stringify(change)}\n\n`;
|
|
28
|
+
for (const client of sseClients) {
|
|
29
|
+
try {
|
|
30
|
+
client.write(payload);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
sseClients.delete(client);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Watch events.jsonl for direct external CLI writes (e.g. from Antigravity IDE)
|
|
38
|
+
let lastKnownEventCount = 0;
|
|
39
|
+
if (engine.eventsPath) {
|
|
40
|
+
try {
|
|
41
|
+
const initialContent = fsSync.readFileSync(engine.eventsPath, 'utf-8');
|
|
42
|
+
lastKnownEventCount = initialContent.trim().split('\n').filter(Boolean).length;
|
|
43
|
+
} catch {
|
|
44
|
+
lastKnownEventCount = 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fsSync.watchFile(engine.eventsPath, { interval: 300 }, async () => {
|
|
48
|
+
try {
|
|
49
|
+
const content = await fs.readFile(engine.eventsPath, 'utf-8');
|
|
50
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
51
|
+
if (lines.length > lastKnownEventCount) {
|
|
52
|
+
const newLines = lines.slice(lastKnownEventCount);
|
|
53
|
+
lastKnownEventCount = lines.length;
|
|
54
|
+
for (const line of newLines) {
|
|
55
|
+
try {
|
|
56
|
+
const event = JSON.parse(line);
|
|
57
|
+
// Broadcast over SSE
|
|
58
|
+
const payload = `event: agent_event\ndata: ${JSON.stringify(event)}\n\n`;
|
|
59
|
+
for (const client of sseClients) {
|
|
60
|
+
try { client.write(payload); } catch {}
|
|
61
|
+
}
|
|
62
|
+
} catch {}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const server = http.createServer(async (req, res) => {
|
|
70
|
+
// Enable CORS
|
|
71
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
72
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
73
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
74
|
+
|
|
75
|
+
if (req.method === 'OPTIONS') {
|
|
76
|
+
res.writeHead(204);
|
|
77
|
+
res.end();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
82
|
+
const pathname = url.pathname;
|
|
83
|
+
|
|
84
|
+
// Helper to send JSON
|
|
85
|
+
const sendJson = (statusCode, data) => {
|
|
86
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
87
|
+
res.end(JSON.stringify(data));
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Helper to read request body
|
|
91
|
+
const readBody = () => {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
let body = '';
|
|
94
|
+
req.on('data', (chunk) => {
|
|
95
|
+
body += chunk;
|
|
96
|
+
if (body.length > 1e6) {
|
|
97
|
+
req.destroy();
|
|
98
|
+
reject(new Error('Payload too large'));
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
req.on('end', () => {
|
|
102
|
+
try {
|
|
103
|
+
resolve(body ? JSON.parse(body) : {});
|
|
104
|
+
} catch (e) {
|
|
105
|
+
resolve({});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
req.on('error', reject);
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// API Routes
|
|
113
|
+
if (pathname === '/api/info' && req.method === 'GET') {
|
|
114
|
+
return sendJson(200, {
|
|
115
|
+
rootDir: engine.rootDir,
|
|
116
|
+
project: engine.getConfig()?.project || path.basename(engine.rootDir),
|
|
117
|
+
version: '0.1.0'
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (pathname === '/api/state' && req.method === 'GET') {
|
|
122
|
+
return sendJson(200, engine.getState());
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (pathname === '/api/config' && req.method === 'GET') {
|
|
126
|
+
return sendJson(200, engine.getConfig());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (pathname === '/api/events' && req.method === 'GET') {
|
|
130
|
+
// Server-Sent Events stream
|
|
131
|
+
res.writeHead(200, {
|
|
132
|
+
'Content-Type': 'text/event-stream',
|
|
133
|
+
'Cache-Control': 'no-cache',
|
|
134
|
+
'Connection': 'keep-alive'
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Send initial history
|
|
138
|
+
const initialPayload = `event: init\ndata: ${JSON.stringify({
|
|
139
|
+
state: engine.getState(),
|
|
140
|
+
config: engine.getConfig(),
|
|
141
|
+
history: engine.getEvents()
|
|
142
|
+
})}\n\n`;
|
|
143
|
+
res.write(initialPayload);
|
|
144
|
+
|
|
145
|
+
sseClients.add(res);
|
|
146
|
+
|
|
147
|
+
req.on('close', () => {
|
|
148
|
+
sseClients.delete(res);
|
|
149
|
+
});
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (pathname === '/api/task' && req.method === 'POST') {
|
|
154
|
+
const body = await readBody();
|
|
155
|
+
const prompt = body.prompt || body.task || 'Optimize application workflow';
|
|
156
|
+
// Asynchronously start task execution
|
|
157
|
+
engine.submitTask(prompt).catch(console.error);
|
|
158
|
+
return sendJson(200, { status: 'accepted', prompt });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (pathname === '/api/emit' && req.method === 'POST') {
|
|
162
|
+
const body = await readBody();
|
|
163
|
+
const event = await engine.emitEvent(body);
|
|
164
|
+
return sendJson(200, { status: 'emitted', event });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (pathname === '/api/reset' && req.method === 'POST') {
|
|
168
|
+
await engine.resetSwarm();
|
|
169
|
+
return sendJson(200, { status: 'reset' });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (pathname === '/api/demo' && req.method === 'POST') {
|
|
173
|
+
const demoPrompt = 'Analyze CRM customer search bottlenecks & optimize Postgres Prisma queries with responsive UI';
|
|
174
|
+
engine.submitTask(demoPrompt).catch(console.error);
|
|
175
|
+
return sendJson(200, { status: 'demo_started', prompt: demoPrompt });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Static Asset Serving
|
|
179
|
+
let filename = pathname === '/' ? 'index.html' : pathname.replace(/^\//, '');
|
|
180
|
+
let contentType = 'text/plain';
|
|
181
|
+
|
|
182
|
+
if (filename.endsWith('.html')) contentType = 'text/html; charset=utf-8';
|
|
183
|
+
else if (filename.endsWith('.css')) contentType = 'text/css; charset=utf-8';
|
|
184
|
+
else if (filename.endsWith('.js')) contentType = 'application/javascript; charset=utf-8';
|
|
185
|
+
else if (filename.endsWith('.json')) contentType = 'application/json';
|
|
186
|
+
else if (filename.endsWith('.png')) contentType = 'image/png';
|
|
187
|
+
else if (filename.endsWith('.svg')) contentType = 'image/svg+xml';
|
|
188
|
+
else if (filename.endsWith('.ico')) contentType = 'image/x-icon';
|
|
189
|
+
|
|
190
|
+
// Check project .pixel-dashboard first, then fallback to built-in
|
|
191
|
+
let filePath = path.join(dashboardDir, filename);
|
|
192
|
+
try {
|
|
193
|
+
await fs.access(filePath);
|
|
194
|
+
} catch {
|
|
195
|
+
filePath = path.join(fallbackDashboardDir, filename);
|
|
196
|
+
try {
|
|
197
|
+
await fs.access(filePath);
|
|
198
|
+
} catch {
|
|
199
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
200
|
+
res.end('404 Not Found');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
const fileData = await fs.readFile(filePath);
|
|
207
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
208
|
+
res.end(fileData);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
211
|
+
res.end('500 Internal Server Error');
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Keep-alive heartbeat for SSE connections
|
|
216
|
+
const heartbeat = setInterval(() => {
|
|
217
|
+
for (const client of sseClients) {
|
|
218
|
+
try {
|
|
219
|
+
client.write(': keepalive\n\n');
|
|
220
|
+
} catch {
|
|
221
|
+
sseClients.delete(client);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}, 15000);
|
|
225
|
+
heartbeat.unref();
|
|
226
|
+
|
|
227
|
+
server.on('close', () => {
|
|
228
|
+
clearInterval(heartbeat);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return server;
|
|
232
|
+
}
|