claudehq 1.0.2 → 1.0.5

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/lib/index.js CHANGED
@@ -28,6 +28,15 @@ const conversation = require('./data/conversation');
28
28
 
29
29
  // Routes
30
30
  const { createRoutes } = require('./routes/api');
31
+ const { createSpawnerRoutes } = require('./routes/spawner');
32
+ const { createOrchestrationRoutes } = require('./routes/orchestration');
33
+
34
+ // Spawner
35
+ const { createSessionSpawner } = require('./spawner');
36
+
37
+ // Orchestration
38
+ const orchestrationData = require('./data/orchestration');
39
+ const orchestrationExecutor = require('./orchestration/executor');
31
40
 
32
41
  // =============================================================================
33
42
  // Module Wiring - Connect modules that need callbacks from other modules
@@ -108,15 +117,12 @@ function registerAllHandlers() {
108
117
  }
109
118
 
110
119
  // =============================================================================
111
- // HTML Template - Loaded from legacy server.js or served inline
120
+ // HTML Template - Loaded from public/index.html
112
121
  // =============================================================================
113
122
 
114
- // For now, we'll read the HTML from the old server.js
115
- // In future iterations, this should be moved to a separate file
116
123
  let HTML = '';
117
124
 
118
125
  function loadHTML() {
119
- // Try to load from a separate HTML file first
120
126
  const htmlPath = path.join(__dirname, '..', 'public', 'index.html');
121
127
  if (fs.existsSync(htmlPath)) {
122
128
  HTML = fs.readFileSync(htmlPath, 'utf-8');
@@ -124,21 +130,8 @@ function loadHTML() {
124
130
  return;
125
131
  }
126
132
 
127
- // Fall back to extracting from old server.js
128
- const oldServerPath = path.join(__dirname, 'server.js');
129
- if (fs.existsSync(oldServerPath)) {
130
- const content = fs.readFileSync(oldServerPath, 'utf-8');
131
- const htmlStart = content.indexOf('const HTML = `<!DOCTYPE html>');
132
- const htmlEnd = content.indexOf('</html>`;', htmlStart);
133
- if (htmlStart !== -1 && htmlEnd !== -1) {
134
- HTML = content.substring(htmlStart + 14, htmlEnd + 7);
135
- console.log(` Extracted HTML template from legacy server.js`);
136
- return;
137
- }
138
- }
139
-
140
- // Minimal fallback HTML
141
- HTML = '<!DOCTYPE html><html><head><title>Claude Tasks Board</title></head><body><h1>Claude Tasks Board</h1><p>HTML template not found. Please check your installation.</p></body></html>';
133
+ // Fallback HTML if public/index.html is missing
134
+ HTML = '<!DOCTYPE html><html><head><title>Claude HQ</title></head><body><h1>Claude HQ</h1><p>HTML template not found. Please check your installation.</p></body></html>';
142
135
  console.log(' Warning: Using minimal fallback HTML template');
143
136
  }
144
137
 
@@ -190,6 +183,33 @@ const routes = createRoutes({
190
183
  sendToTmux: sessionManager.sendToTmux
191
184
  });
192
185
 
186
+ // =============================================================================
187
+ // Session Spawner Setup
188
+ // =============================================================================
189
+
190
+ // Create session spawner instance
191
+ const spawner = createSessionSpawner({
192
+ dataDir: config.EVENTS_DIR
193
+ });
194
+
195
+ // Wire spawner to session manager broadcasts
196
+ spawner.setOnSessionUpdate((sessions) => {
197
+ broadcastUpdate('spawned_sessions', { sessions });
198
+ });
199
+
200
+ spawner.setOnPermissionDetected((sessionId, permission) => {
201
+ broadcastUpdate('spawner_permission', { sessionId, permission });
202
+ });
203
+
204
+ // Create spawner routes
205
+ const spawnerRoutes = createSpawnerRoutes({ spawner });
206
+
207
+ // Create orchestration routes
208
+ const orchestrationRoutes = createOrchestrationRoutes({
209
+ orchestrationData,
210
+ orchestrationExecutor
211
+ });
212
+
193
213
  // =============================================================================
194
214
  // HTTP Server
195
215
  // =============================================================================
@@ -349,6 +369,166 @@ const server = http.createServer((req, res) => {
349
369
  return routes.handleCreateTask(req, res, createMatch[1]);
350
370
  }
351
371
 
372
+ // ==========================================================================
373
+ // Spawner Routes - New session spawning API
374
+ // ==========================================================================
375
+
376
+ // Sessions
377
+ if (url.pathname === '/api/spawner/sessions' && req.method === 'POST') {
378
+ return spawnerRoutes.handleSpawnSession(req, res);
379
+ }
380
+ if (url.pathname === '/api/spawner/sessions' && req.method === 'GET') {
381
+ return spawnerRoutes.handleListSessions(req, res);
382
+ }
383
+ if (url.pathname === '/api/spawner/refresh' && req.method === 'POST') {
384
+ return spawnerRoutes.handleRefresh(req, res);
385
+ }
386
+
387
+ const spawnerSessionMatch = url.pathname.match(/^\/api\/spawner\/sessions\/([^/]+)$/);
388
+ if (spawnerSessionMatch && req.method === 'GET') {
389
+ return spawnerRoutes.handleGetSession(req, res, spawnerSessionMatch[1]);
390
+ }
391
+ if (spawnerSessionMatch && req.method === 'PATCH') {
392
+ return spawnerRoutes.handleUpdateSession(req, res, spawnerSessionMatch[1]);
393
+ }
394
+ if (spawnerSessionMatch && req.method === 'DELETE') {
395
+ return spawnerRoutes.handleKillSession(req, res, spawnerSessionMatch[1]);
396
+ }
397
+
398
+ const spawnerRestartMatch = url.pathname.match(/^\/api\/spawner\/sessions\/([^/]+)\/restart$/);
399
+ if (spawnerRestartMatch && req.method === 'POST') {
400
+ return spawnerRoutes.handleRestartSession(req, res, spawnerRestartMatch[1]);
401
+ }
402
+
403
+ const spawnerPromptMatch = url.pathname.match(/^\/api\/spawner\/sessions\/([^/]+)\/prompt$/);
404
+ if (spawnerPromptMatch && req.method === 'POST') {
405
+ return spawnerRoutes.handleSendPrompt(req, res, spawnerPromptMatch[1]);
406
+ }
407
+
408
+ const spawnerCancelMatch = url.pathname.match(/^\/api\/spawner\/sessions\/([^/]+)\/cancel$/);
409
+ if (spawnerCancelMatch && req.method === 'POST') {
410
+ return spawnerRoutes.handleCancelSession(req, res, spawnerCancelMatch[1]);
411
+ }
412
+
413
+ const spawnerPermissionMatch = url.pathname.match(/^\/api\/spawner\/sessions\/([^/]+)\/permission$/);
414
+ if (spawnerPermissionMatch && req.method === 'POST') {
415
+ return spawnerRoutes.handlePermissionResponse(req, res, spawnerPermissionMatch[1]);
416
+ }
417
+
418
+ // Projects
419
+ if (url.pathname === '/api/spawner/projects' && req.method === 'GET') {
420
+ return spawnerRoutes.handleListProjects(req, res, url);
421
+ }
422
+ if (url.pathname === '/api/spawner/projects' && req.method === 'POST') {
423
+ return spawnerRoutes.handleTrackProject(req, res);
424
+ }
425
+ if (url.pathname === '/api/spawner/projects' && req.method === 'DELETE') {
426
+ return spawnerRoutes.handleRemoveProject(req, res);
427
+ }
428
+ if (url.pathname === '/api/spawner/projects/autocomplete' && req.method === 'GET') {
429
+ return spawnerRoutes.handleAutocomplete(req, res, url);
430
+ }
431
+ if (url.pathname === '/api/spawner/projects/common' && req.method === 'GET') {
432
+ return spawnerRoutes.handleGetCommonDirectories(req, res);
433
+ }
434
+
435
+ // ==========================================================================
436
+ // Orchestration Routes
437
+ // ==========================================================================
438
+
439
+ // Orchestrations CRUD
440
+ if (url.pathname === '/api/orchestrations' && req.method === 'GET') {
441
+ return orchestrationRoutes.handleListOrchestrations(req, res);
442
+ }
443
+ if (url.pathname === '/api/orchestrations' && req.method === 'POST') {
444
+ return orchestrationRoutes.handleCreateOrchestration(req, res);
445
+ }
446
+
447
+ const orchIdMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)$/);
448
+ if (orchIdMatch && req.method === 'GET') {
449
+ return orchestrationRoutes.handleGetOrchestration(req, res, orchIdMatch[1]);
450
+ }
451
+ if (orchIdMatch && req.method === 'PATCH') {
452
+ return orchestrationRoutes.handleUpdateOrchestration(req, res, orchIdMatch[1]);
453
+ }
454
+ if (orchIdMatch && req.method === 'DELETE') {
455
+ return orchestrationRoutes.handleDeleteOrchestration(req, res, orchIdMatch[1]);
456
+ }
457
+
458
+ // Orchestration execution
459
+ const orchStartMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/start$/);
460
+ if (orchStartMatch && req.method === 'POST') {
461
+ return orchestrationRoutes.handleStartOrchestration(req, res, orchStartMatch[1]);
462
+ }
463
+
464
+ const orchStopMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/stop$/);
465
+ if (orchStopMatch && req.method === 'POST') {
466
+ return orchestrationRoutes.handleStopOrchestration(req, res, orchStopMatch[1]);
467
+ }
468
+
469
+ const orchStatsMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/stats$/);
470
+ if (orchStatsMatch && req.method === 'GET') {
471
+ return orchestrationRoutes.handleGetStats(req, res, orchStatsMatch[1]);
472
+ }
473
+
474
+ // Agent routes
475
+ const agentAddMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents$/);
476
+ if (agentAddMatch && req.method === 'POST') {
477
+ return orchestrationRoutes.handleAddAgent(req, res, agentAddMatch[1]);
478
+ }
479
+
480
+ const agentIdMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)$/);
481
+ if (agentIdMatch && req.method === 'PATCH') {
482
+ return orchestrationRoutes.handleUpdateAgent(req, res, agentIdMatch[1], agentIdMatch[2]);
483
+ }
484
+ if (agentIdMatch && req.method === 'DELETE') {
485
+ return orchestrationRoutes.handleRemoveAgent(req, res, agentIdMatch[1], agentIdMatch[2]);
486
+ }
487
+
488
+ const agentStatusMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)\/status$/);
489
+ if (agentStatusMatch && req.method === 'GET') {
490
+ return orchestrationRoutes.handleGetAgentStatus(req, res, agentStatusMatch[1], agentStatusMatch[2]);
491
+ }
492
+
493
+ const agentSpawnMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)\/spawn$/);
494
+ if (agentSpawnMatch && req.method === 'POST') {
495
+ return orchestrationRoutes.handleSpawnAgent(req, res, agentSpawnMatch[1], agentSpawnMatch[2]);
496
+ }
497
+
498
+ const agentKillMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)\/kill$/);
499
+ if (agentKillMatch && req.method === 'POST') {
500
+ return orchestrationRoutes.handleKillAgent(req, res, agentKillMatch[1], agentKillMatch[2]);
501
+ }
502
+
503
+ const agentPromptMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)\/prompt$/);
504
+ if (agentPromptMatch && req.method === 'POST') {
505
+ return orchestrationRoutes.handleSendAgentPrompt(req, res, agentPromptMatch[1], agentPromptMatch[2]);
506
+ }
507
+
508
+ // Dependencies
509
+ const depAddMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)\/dependencies$/);
510
+ if (depAddMatch && req.method === 'POST') {
511
+ return orchestrationRoutes.handleAddDependency(req, res, depAddMatch[1], depAddMatch[2]);
512
+ }
513
+
514
+ const depRemoveMatch = url.pathname.match(/^\/api\/orchestrations\/([a-z0-9-]+)\/agents\/([a-z0-9-]+)\/dependencies\/([a-z0-9-]+)$/);
515
+ if (depRemoveMatch && req.method === 'DELETE') {
516
+ return orchestrationRoutes.handleRemoveDependency(req, res, depRemoveMatch[1], depRemoveMatch[2], depRemoveMatch[3]);
517
+ }
518
+
519
+ // Templates
520
+ if (url.pathname === '/api/orchestration-templates' && req.method === 'GET') {
521
+ return orchestrationRoutes.handleListTemplates(req, res);
522
+ }
523
+
524
+ const templateIdMatch = url.pathname.match(/^\/api\/orchestration-templates\/([a-z0-9-]+)$/);
525
+ if (templateIdMatch && req.method === 'GET') {
526
+ return orchestrationRoutes.handleGetTemplate(req, res, templateIdMatch[1]);
527
+ }
528
+ if (templateIdMatch && req.method === 'POST') {
529
+ return orchestrationRoutes.handleCreateFromTemplate(req, res, templateIdMatch[1]);
530
+ }
531
+
352
532
  // Serve HTML
353
533
  res.writeHead(200, { 'Content-Type': 'text/html' });
354
534
  res.end(HTML);
@@ -375,24 +555,32 @@ function start() {
375
555
  registerAllHandlers();
376
556
  console.log(` EventBus initialized with ${eventBus.getHandlerCount()} handlers`);
377
557
 
558
+ // Initialize session manager (must be before claudeEvents so sessions are loaded before discovery)
559
+ sessionManager.init();
560
+
378
561
  // Initialize Claude events module
379
562
  claudeEvents.init();
380
563
 
381
- // Initialize session manager
382
- sessionManager.init();
383
-
384
564
  // Set up file watchers
385
565
  setupWatchers();
386
566
 
387
567
  // Build plan session cache
388
568
  plans.buildPlanSessionCache();
389
569
 
570
+ // Initialize session spawner
571
+ spawner.init();
572
+ console.log(` Session spawner initialized`);
573
+
574
+ // Initialize orchestration executor
575
+ orchestrationExecutor.init();
576
+ console.log(` Orchestration executor initialized`);
577
+
390
578
  console.log(` Press Ctrl+C to stop\n`);
391
579
  });
392
580
  }
393
581
 
394
582
  // Export for testing
395
- module.exports = { server, start };
583
+ module.exports = { server, start, spawner };
396
584
 
397
585
  // Start if run directly
398
586
  if (require.main === module) {