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.
@@ -0,0 +1,372 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'node:path';
4
+ import fs from 'node:fs/promises';
5
+ import fsSync from 'node:fs';
6
+ import { spawn } from 'node:child_process';
7
+ import { initializeProject } from '../src/scaffold/init.js';
8
+ import { OrchestratorEngine } from '../src/orchestrator/engine.js';
9
+ import { createServer } from '../src/server/server.js';
10
+
11
+ const BANNER = `\x1b[36m
12
+ ╔═══════════════════════════════════════════════════════════╗
13
+ ║ PIXEL AGENTS - Multi-Agent Orchestration Framework ║
14
+ ║ [ ◉ _ ◉ ] ║
15
+ ╚═══════════════════════════════════════════════════════════╝\x1b[0m`;
16
+
17
+ function printHelp() {
18
+ console.log(BANNER);
19
+ console.log(`
20
+ \x1b[1mUSAGE:\x1b[0m
21
+ npx pixel-agents <command> [options]
22
+
23
+ \x1b[1mCOMMANDS:\x1b[0m
24
+ \x1b[32minit\x1b[0m Initialize .pixel-agents/ workspace in current project
25
+ \x1b[32manalyze\x1b[0m Inspect and display detected frameworks, ORMs, and architecture
26
+ \x1b[32mstart\x1b[0m Start multi-agent orchestrator & live pixel dashboard
27
+ \x1b[32mdashboard\x1b[0m Launch the visual pixel dashboard UI
28
+ \x1b[32mdemo\x1b[0m Launch server and run an interactive multi-agent demo
29
+ \x1b[32mtask\x1b[0m "<description>" Dispatch an objective to the agent swarm
30
+ \x1b[32memit\x1b[0m [options] Emit an event to the stream (e.g. from external hooks)
31
+ \x1b[32mstatus\x1b[0m Display active swarm state and agent roster
32
+ \x1b[32mhelp\x1b[0m Show this help reference
33
+
34
+ \x1b[1mOPTIONS:\x1b[0m
35
+ --port <number> Set dashboard port (default: 4747)
36
+ --no-open Do not automatically open the browser
37
+ --yes, -y Skip prompts and use defaults during init
38
+ --name <name> Set project name during init
39
+ --agent <name> Specify agent for emit (e.g. frontend, backend, qa)
40
+ --type <type> Event type for emit (spawn, thinking, tool, skill, complete, error)
41
+ --message <msg> Event message for emit
42
+ --skill <skill> Associated skill for emit
43
+
44
+ \x1b[1mEXAMPLES:\x1b[0m
45
+ npx pixel-agents init
46
+ npx pixel-agents start
47
+ npx pixel-agents demo
48
+ npx pixel-agents task "Find slow Prisma queries and optimize indexes"
49
+ npx pixel-agents emit --agent database --type tool --skill prisma --message "Analyzing queries"
50
+ `);
51
+ }
52
+
53
+ function openBrowser(url) {
54
+ const platform = process.platform;
55
+ let cmd = 'open';
56
+ if (platform === 'win32') cmd = 'start';
57
+ else if (platform === 'linux') cmd = 'xdg-open';
58
+
59
+ try {
60
+ spawn(cmd, [url], { detached: true, stdio: 'ignore' }).unref();
61
+ } catch (err) {
62
+ // Ignore browser open errors
63
+ }
64
+ }
65
+
66
+ async function resolveDaemonUrl(rootDir) {
67
+ // 1. Check .pixel-agents/daemon.json
68
+ const daemonPath = path.join(rootDir, '.pixel-agents', 'daemon.json');
69
+ try {
70
+ const raw = await fs.readFile(daemonPath, 'utf-8');
71
+ const info = JSON.parse(raw);
72
+ if (info?.url) {
73
+ const res = await fetch(`${info.url}/api/info`, { signal: AbortSignal.timeout(600) });
74
+ if (res.ok) {
75
+ const serverInfo = await res.json();
76
+ if (!serverInfo.rootDir || serverInfo.rootDir === rootDir) {
77
+ return info.url;
78
+ }
79
+ }
80
+ }
81
+ } catch {}
82
+
83
+ // 2. Scan fallback ports 4747..4755 and verify rootDir
84
+ for (let port = 4747; port <= 4755; port++) {
85
+ try {
86
+ const res = await fetch(`http://localhost:${port}/api/info`, { signal: AbortSignal.timeout(300) });
87
+ if (res.ok) {
88
+ const serverInfo = await res.json();
89
+ if (serverInfo.rootDir && serverInfo.rootDir === rootDir) {
90
+ return `http://localhost:${port}`;
91
+ }
92
+ }
93
+ } catch {}
94
+ }
95
+
96
+ return null;
97
+ }
98
+
99
+ function startServerWithPortFallback(server, initialPort, maxTries = 20) {
100
+ return new Promise((resolve, reject) => {
101
+ let currentPort = initialPort;
102
+ let attempts = 0;
103
+
104
+ function tryListen() {
105
+ server.removeAllListeners('error');
106
+ server.removeAllListeners('listening');
107
+
108
+ server.once('error', (err) => {
109
+ if (err.code === 'EADDRINUSE') {
110
+ attempts++;
111
+ if (attempts >= maxTries) {
112
+ reject(new Error(`Could not find an available port after ${maxTries} attempts (starting at port ${initialPort}).`));
113
+ return;
114
+ }
115
+ currentPort++;
116
+ console.log(`\x1b[33mPort ${currentPort - 1} is already in use. Trying next port ${currentPort}...\x1b[0m`);
117
+ tryListen();
118
+ } else {
119
+ reject(err);
120
+ }
121
+ });
122
+
123
+ server.once('listening', () => {
124
+ resolve(currentPort);
125
+ });
126
+
127
+ server.listen(currentPort);
128
+ }
129
+
130
+ tryListen();
131
+ });
132
+ }
133
+
134
+ async function main() {
135
+ const args = process.argv.slice(2);
136
+ const command = args[0] || 'help';
137
+
138
+ // Parse arguments
139
+ const options = {};
140
+ for (let i = 1; i < args.length; i++) {
141
+ if (args[i] === '--yes' || args[i] === '-y') options.yes = true;
142
+ else if (args[i] === '--no-open') options.noOpen = true;
143
+ else if (args[i] === '--port' && args[i + 1]) options.port = parseInt(args[++i], 10);
144
+ else if (args[i] === '--name' && args[i + 1]) options.name = args[++i];
145
+ else if (args[i] === '--agent' && args[i + 1]) options.agent = args[++i];
146
+ else if (args[i] === '--type' && args[i + 1]) options.type = args[++i];
147
+ else if (args[i] === '--message' && args[i + 1]) options.message = args[++i];
148
+ else if (args[i] === '--skill' && args[i + 1]) options.skill = args[++i];
149
+ else if (!args[i].startsWith('-') && !options.taskPrompt) {
150
+ options.taskPrompt = args[i];
151
+ }
152
+ }
153
+
154
+ const rootDir = process.cwd();
155
+
156
+ switch (command) {
157
+ case 'init': {
158
+ await initializeProject(rootDir, options);
159
+ break;
160
+ }
161
+
162
+ case 'analyze': {
163
+ console.log(BANNER);
164
+ console.log('\n\x1b[36m🔍 Analyzing Codebase Architecture & Context...\x1b[0m\n');
165
+ const { analyzeCodebase } = await import('../src/scaffold/analyzer.js');
166
+ const profile = await analyzeCodebase(rootDir);
167
+
168
+ console.log(`\x1b[1mPROJECT:\x1b[0m ${profile.projectName}`);
169
+ console.log(`\x1b[1mLANGUAGES:\x1b[0m ${profile.languages.join(', ') || 'None'}`);
170
+ console.log(`\x1b[1mFRAMEWORKS:\x1b[0m ${profile.frameworks.join(', ') || 'None'}`);
171
+ console.log(`\x1b[1mBACKEND / API:\x1b[0m ${profile.backend.join(', ') || 'None'}`);
172
+ console.log(`\x1b[1mDATABASE / ORM:\x1b[0m ${profile.database.join(', ') || 'None'}`);
173
+ console.log(`\x1b[1mTESTING SUITE:\x1b[0m ${profile.testing.join(', ') || 'None'}`);
174
+ console.log(`\x1b[1mAUTH / SECURITY:\x1b[0m ${profile.auth.join(', ') || 'None'}`);
175
+ console.log(`\x1b[1mSTYLING SYSTEM:\x1b[0m ${profile.styling.join(', ') || 'None'}`);
176
+ console.log(`\x1b[1mRECOMMENDED SKILLS:\x1b[0m ${profile.recommendedSkills.join(', ')}\n`);
177
+ break;
178
+ }
179
+
180
+ case 'start':
181
+ case 'dev':
182
+ case 'dashboard':
183
+ case 'demo': {
184
+ // Check if project is initialized
185
+ const configPath = path.join(rootDir, '.pixel-agents', 'config.json');
186
+ try {
187
+ await fs.access(configPath);
188
+ } catch {
189
+ console.log('\x1b[33mProject not initialized yet. Running automatic init...\x1b[0m');
190
+ await initializeProject(rootDir, { yes: true });
191
+ }
192
+
193
+ const engine = new OrchestratorEngine(rootDir);
194
+ await engine.initialize();
195
+
196
+ const requestedPort = options.port || engine.getConfig()?.dashboard?.port || 4747;
197
+ const server = createServer(engine, options);
198
+
199
+ try {
200
+ const boundPort = await startServerWithPortFallback(server, requestedPort);
201
+ const url = `http://localhost:${boundPort}`;
202
+
203
+ // Persist daemon discovery file
204
+ const daemonPath = path.join(rootDir, '.pixel-agents', 'daemon.json');
205
+ try {
206
+ await fs.writeFile(daemonPath, JSON.stringify({
207
+ port: boundPort,
208
+ pid: process.pid,
209
+ url,
210
+ startedAt: new Date().toISOString()
211
+ }, null, 2), 'utf-8');
212
+ } catch {}
213
+
214
+ const cleanupDaemon = () => {
215
+ try { fsSync.unlinkSync(daemonPath); } catch {}
216
+ };
217
+ process.on('exit', cleanupDaemon);
218
+ process.on('SIGINT', () => { cleanupDaemon(); process.exit(0); });
219
+ process.on('SIGTERM', () => { cleanupDaemon(); process.exit(0); });
220
+
221
+ console.log(BANNER);
222
+ console.log(`\n\x1b[32m\x1b[1m● PixelCrew Swarm Server Active\x1b[0m`);
223
+ console.log(` Visual Dashboard: \x1b[36m\x1b[4m${url}\x1b[0m`);
224
+ console.log(` Event Stream: \x1b[36m${url}/api/events\x1b[0m\n`);
225
+ console.log('\x1b[90mPress Ctrl+C to stop.\x1b[0m\n');
226
+
227
+ if (!options.noOpen) {
228
+ openBrowser(url);
229
+ }
230
+
231
+ // Auto trigger demo if command was 'demo'
232
+ if (command === 'demo') {
233
+ setTimeout(() => {
234
+ const prompt = `Optimize ${engine.getConfig()?.project || 'app'} APIs, database queries & deploy responsive dashboard`;
235
+ console.log(`\x1b[33m[DEMO]\x1b[0m Dispatching swarm mission: "${prompt}"\n`);
236
+ engine.submitTask(prompt).catch(console.error);
237
+ }, 1200);
238
+ }
239
+ } catch (err) {
240
+ console.error(`\x1b[31mFailed to start server:\x1b[0m`, err.message);
241
+ process.exit(1);
242
+ }
243
+
244
+ // Stream terminal events as they happen
245
+ engine.on('agent_event', (event) => {
246
+ const time = new Date(event.timestamp).toTimeString().split(' ')[0];
247
+ const agentPadded = (event.agent || 'orchestrator').padEnd(12);
248
+ let color = '\x1b[37m';
249
+ if (event.agent === 'orchestrator') color = '\x1b[33m';
250
+ else if (event.agent === 'frontend') color = '\x1b[36m';
251
+ else if (event.agent === 'backend') color = '\x1b[35m';
252
+ else if (event.agent === 'database') color = '\x1b[33m';
253
+ else if (event.agent === 'security') color = '\x1b[31m';
254
+ else if (event.agent === 'performance') color = '\x1b[32m';
255
+ else if (event.agent === 'qa') color = '\x1b[35m';
256
+
257
+ console.log(`\x1b[90m${time}\x1b[0m ${color}${agentPadded}\x1b[0m → ${event.message}`);
258
+ });
259
+
260
+ break;
261
+ }
262
+
263
+ case 'task': {
264
+ const taskPrompt = options.taskPrompt || args[1];
265
+ if (!taskPrompt) {
266
+ console.error('\x1b[31mError: Please provide a task description.\x1b[0m');
267
+ console.log('Example: npx pixel-agents task "Optimize Postgres queries"');
268
+ process.exit(1);
269
+ }
270
+
271
+ // Check if daemon server is running
272
+ const daemonUrl = await resolveDaemonUrl(rootDir);
273
+ if (daemonUrl) {
274
+ try {
275
+ const res = await fetch(`${daemonUrl}/api/task`, {
276
+ method: 'POST',
277
+ headers: { 'Content-Type': 'application/json' },
278
+ body: JSON.stringify({ prompt: taskPrompt })
279
+ });
280
+ if (res.ok) {
281
+ console.log(`\x1b[32m✓ Task dispatched to running swarm daemon (${daemonUrl}):\x1b[0m "${taskPrompt}"`);
282
+ console.log(`Check live status at ${daemonUrl}`);
283
+ return;
284
+ }
285
+ } catch {}
286
+ }
287
+
288
+ console.log(BANNER);
289
+ console.log(`\x1b[33m[SWARM RUNNER]\x1b[0m Dispatching task: "${taskPrompt}"\n`);
290
+ const engine = new OrchestratorEngine(rootDir);
291
+ await engine.initialize();
292
+
293
+ engine.on('agent_event', (event) => {
294
+ const time = new Date(event.timestamp).toTimeString().split(' ')[0];
295
+ const agentPadded = (event.agent || 'orchestrator').padEnd(12);
296
+ console.log(`\x1b[90m${time}\x1b[0m \x1b[36m${agentPadded}\x1b[0m → ${event.message}`);
297
+ });
298
+
299
+ const report = await engine.submitTask(taskPrompt);
300
+ if (report) {
301
+ console.log('\n\x1b[36m' + report + '\x1b[0m\n');
302
+ }
303
+ console.log('\x1b[32m✓ Sprint audit completed successfully.\x1b[0m\n');
304
+ break;
305
+ }
306
+
307
+ case 'emit': {
308
+ const eventData = {
309
+ agent: options.agent || 'orchestrator',
310
+ type: options.type || 'progress',
311
+ message: options.message || 'Manual event trigger',
312
+ skill: options.skill
313
+ };
314
+
315
+ const daemonUrl = await resolveDaemonUrl(rootDir);
316
+ if (daemonUrl) {
317
+ try {
318
+ const res = await fetch(`${daemonUrl}/api/emit`, {
319
+ method: 'POST',
320
+ headers: { 'Content-Type': 'application/json' },
321
+ body: JSON.stringify(eventData)
322
+ });
323
+ if (res.ok) {
324
+ console.log(`\x1b[32m✓ Event emitted to live dashboard (${daemonUrl}):\x1b[0m ${eventData.agent} → ${eventData.message}`);
325
+ return;
326
+ }
327
+ } catch {}
328
+ }
329
+
330
+ // Local append & persist
331
+ const engine = new OrchestratorEngine(rootDir);
332
+ await engine.initialize();
333
+ await engine.emitEvent(eventData);
334
+ console.log(`\x1b[32m✓ Event recorded to .pixel-agents/events.jsonl:\x1b[0m ${eventData.agent} → ${eventData.message}`);
335
+ break;
336
+ }
337
+
338
+ case 'status': {
339
+ const engine = new OrchestratorEngine(rootDir);
340
+ await engine.initialize();
341
+ const state = engine.getState();
342
+ const config = engine.getConfig();
343
+
344
+ console.log(BANNER);
345
+ console.log(`\n\x1b[1mPROJECT:\x1b[0m ${config?.project || 'my-app'}`);
346
+ console.log(`\x1b[1mSTATUS:\x1b[0m ${state.status === 'RUNNING' ? '\x1b[32m● RUNNING\x1b[0m' : '\x1b[33mSTANDBY\x1b[0m'}`);
347
+ console.log(`\x1b[1mTASK:\x1b[0m ${state.activeTask || 'None'}\n`);
348
+ console.log('\x1b[1mAGENT SWARM ROSTER:\x1b[0m');
349
+ console.log('─────────────────────────────────────────────────────────────');
350
+ for (const [key, agent] of Object.entries(config?.agents || {})) {
351
+ const aState = state.agents?.[key] || {};
352
+ const stateColor = aState.state === 'WORKING' ? '\x1b[35m' : (aState.state === 'COMPLETED' ? '\x1b[32m' : '\x1b[90m');
353
+ console.log(` ${key.padEnd(14)} [${stateColor}${(aState.state || 'IDLE').padEnd(10)}\x1b[0m] ${aState.expression || '●_●'} ${aState.currentTask || 'Idle'}`);
354
+ }
355
+ console.log('─────────────────────────────────────────────────────────────\n');
356
+ break;
357
+ }
358
+
359
+ case 'help':
360
+ case '--help':
361
+ case '-h':
362
+ default: {
363
+ printHelp();
364
+ break;
365
+ }
366
+ }
367
+ }
368
+
369
+ main().catch((err) => {
370
+ console.error('\x1b[31mFatal error:\x1b[0m', err);
371
+ process.exit(1);
372
+ });
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "pixelcrew",
3
+ "version": "0.1.0",
4
+ "description": "Local multi-agent orchestration framework with an interactive pixel-art tech startup office visual dashboard",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "pixelcrew": "bin/pixel-agents.js",
9
+ "pixel-crew": "bin/pixel-agents.js",
10
+ "pixel-agents": "bin/pixel-agents.js"
11
+ },
12
+ "files": [
13
+ "bin/",
14
+ "src/",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "start": "node bin/pixel-agents.js start",
20
+ "init": "node bin/pixel-agents.js init",
21
+ "dashboard": "node bin/pixel-agents.js dashboard",
22
+ "demo": "node bin/pixel-agents.js demo",
23
+ "test": "node --test tests/**/*.test.js"
24
+ },
25
+ "keywords": [
26
+ "agents",
27
+ "multi-agent",
28
+ "orchestrator",
29
+ "pixel-art",
30
+ "dashboard",
31
+ "llm",
32
+ "ai",
33
+ "workflow",
34
+ "terminal",
35
+ "startup-office",
36
+ "npx"
37
+ ],
38
+ "author": "Arnel (@hiroqt)",
39
+ "license": "Apache-2.0",
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/hiroqt/PixelCrew.git"
46
+ }
47
+ }