claudehq 1.0.1 → 1.0.3
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/bin/cli.js +2 -2
- package/lib/core/claude-events.js +308 -0
- package/lib/core/config.js +103 -0
- package/lib/core/event-bus.js +145 -0
- package/lib/core/sse.js +96 -0
- package/lib/core/watchers.js +127 -0
- package/lib/data/conversation.js +195 -0
- package/lib/data/orchestration.js +941 -0
- package/lib/data/plans.js +247 -0
- package/lib/data/tasks.js +263 -0
- package/lib/data/todos.js +205 -0
- package/lib/index.js +481 -0
- package/lib/orchestration/executor.js +635 -0
- package/lib/routes/api.js +1010 -0
- package/lib/sessions/manager.js +870 -0
- package/package.json +10 -4
- package/{lib/server.js → public/index.html} +1984 -2644
package/lib/index.js
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Claude Tasks Board - Main Entry Point
|
|
4
|
+
*
|
|
5
|
+
* This is the main entry point that initializes all modules
|
|
6
|
+
* and starts the HTTP server.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const http = require('http');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
// Core modules
|
|
14
|
+
const config = require('./core/config');
|
|
15
|
+
const { eventBus, EventTypes } = require('./core/event-bus');
|
|
16
|
+
const { sseClients, addClient, removeClient, broadcastUpdate } = require('./core/sse');
|
|
17
|
+
const claudeEvents = require('./core/claude-events');
|
|
18
|
+
const { setupWatchers, setPlanCacheCallbacks } = require('./core/watchers');
|
|
19
|
+
|
|
20
|
+
// Session manager
|
|
21
|
+
const sessionManager = require('./sessions/manager');
|
|
22
|
+
|
|
23
|
+
// Data modules
|
|
24
|
+
const tasks = require('./data/tasks');
|
|
25
|
+
const todos = require('./data/todos');
|
|
26
|
+
const plans = require('./data/plans');
|
|
27
|
+
const conversation = require('./data/conversation');
|
|
28
|
+
const orchestrationData = require('./data/orchestration');
|
|
29
|
+
|
|
30
|
+
// Orchestration
|
|
31
|
+
const orchestrationExecutor = require('./orchestration/executor');
|
|
32
|
+
|
|
33
|
+
// Routes
|
|
34
|
+
const { createRoutes } = require('./routes/api');
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// Module Wiring - Connect modules that need callbacks from other modules
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
// Set up conversation file finder for claude-events module
|
|
41
|
+
claudeEvents.setConversationFileCallback(conversation.findConversationFile);
|
|
42
|
+
|
|
43
|
+
// Set up session update callback for claude-events module
|
|
44
|
+
claudeEvents.setSessionUpdateCallback(sessionManager.updateSessionFromEvent);
|
|
45
|
+
|
|
46
|
+
// Set up metadata callback for session manager
|
|
47
|
+
sessionManager.setMetadataCallback(conversation.getSessionMetadata);
|
|
48
|
+
|
|
49
|
+
// Set up plan cache callbacks for watchers
|
|
50
|
+
setPlanCacheCallbacks(plans.buildPlanSessionCache, plans.getSessionForPlan);
|
|
51
|
+
|
|
52
|
+
// Set up tasks module callbacks
|
|
53
|
+
tasks.setCustomNamesCallback(sessionManager.loadCustomNames);
|
|
54
|
+
tasks.setMetadataCallback(conversation.getSessionMetadata);
|
|
55
|
+
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// EventBus Handlers - Register event handlers for broadcasts and logging
|
|
58
|
+
// =============================================================================
|
|
59
|
+
|
|
60
|
+
function registerBroadcastHandlers() {
|
|
61
|
+
// Task broadcast handlers
|
|
62
|
+
eventBus.on(EventTypes.TASK_CREATED, (payload) =>
|
|
63
|
+
broadcastUpdate('task_created', payload));
|
|
64
|
+
eventBus.on(EventTypes.TASK_UPDATED, (payload) =>
|
|
65
|
+
broadcastUpdate('task_updated', payload));
|
|
66
|
+
eventBus.on(EventTypes.TASK_STATUS_CHANGED, (payload) =>
|
|
67
|
+
broadcastUpdate('task_status_changed', payload));
|
|
68
|
+
eventBus.on(EventTypes.SESSION_RENAMED, (payload) =>
|
|
69
|
+
broadcastUpdate('session_renamed', payload));
|
|
70
|
+
eventBus.on(EventTypes.FILE_CHANGED, () => broadcastUpdate());
|
|
71
|
+
|
|
72
|
+
// Todo broadcast handlers
|
|
73
|
+
eventBus.on(EventTypes.TODOS_CHANGED, (payload) =>
|
|
74
|
+
broadcastUpdate('todos_changed', payload));
|
|
75
|
+
eventBus.on(EventTypes.TODO_CREATED, (payload) =>
|
|
76
|
+
broadcastUpdate('todo_created', payload));
|
|
77
|
+
eventBus.on(EventTypes.TODO_UPDATED, (payload) =>
|
|
78
|
+
broadcastUpdate('todo_updated', payload));
|
|
79
|
+
eventBus.on(EventTypes.TODO_STATUS_CHANGED, (payload) =>
|
|
80
|
+
broadcastUpdate('todo_status_changed', payload));
|
|
81
|
+
|
|
82
|
+
// Plan broadcast handlers
|
|
83
|
+
eventBus.on(EventTypes.PLANS_CHANGED, (payload) =>
|
|
84
|
+
broadcastUpdate('plans_changed', payload));
|
|
85
|
+
eventBus.on(EventTypes.PLAN_CREATED, (payload) =>
|
|
86
|
+
broadcastUpdate('plan_created', payload));
|
|
87
|
+
eventBus.on(EventTypes.PLAN_UPDATED, (payload) =>
|
|
88
|
+
broadcastUpdate('plan_updated', payload));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function registerLoggingHandlers() {
|
|
92
|
+
eventBus.on(EventTypes.TASK_CREATED, (payload) => {
|
|
93
|
+
console.log(`[${new Date().toLocaleTimeString()}] Task created: ${payload.task?.subject || payload.taskId}`);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
eventBus.on(EventTypes.TASK_STATUS_CHANGED, (payload) => {
|
|
97
|
+
console.log(`[${new Date().toLocaleTimeString()}] Task #${payload.taskId} status: ${payload.status}`);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
eventBus.on(EventTypes.PROMPT_SENT, (payload) => {
|
|
101
|
+
console.log(`[${new Date().toLocaleTimeString()}] Prompt sent to ${payload.tmuxSession}`);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
eventBus.on(EventTypes.CLAUDE_EVENT, (payload) => {
|
|
105
|
+
console.log(`[${new Date().toLocaleTimeString()}] Claude event: ${payload.type} (${payload.tool || payload.sessionId})`);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function registerAllHandlers() {
|
|
110
|
+
registerBroadcastHandlers();
|
|
111
|
+
registerLoggingHandlers();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// =============================================================================
|
|
115
|
+
// HTML Template - Loaded from public/index.html
|
|
116
|
+
// =============================================================================
|
|
117
|
+
|
|
118
|
+
let HTML = '';
|
|
119
|
+
|
|
120
|
+
function loadHTML() {
|
|
121
|
+
const htmlPath = path.join(__dirname, '..', 'public', 'index.html');
|
|
122
|
+
if (fs.existsSync(htmlPath)) {
|
|
123
|
+
HTML = fs.readFileSync(htmlPath, 'utf-8');
|
|
124
|
+
console.log(` Loaded HTML from: ${htmlPath}`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Fallback HTML if public/index.html is missing
|
|
129
|
+
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>';
|
|
130
|
+
console.log(' Warning: Using minimal fallback HTML template');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// =============================================================================
|
|
134
|
+
// Create Route Handlers
|
|
135
|
+
// =============================================================================
|
|
136
|
+
|
|
137
|
+
const routes = createRoutes({
|
|
138
|
+
// Core
|
|
139
|
+
sseClients,
|
|
140
|
+
claudeEvents: claudeEvents.claudeEvents,
|
|
141
|
+
addClaudeEvent: claudeEvents.addClaudeEvent,
|
|
142
|
+
broadcastManagedSessions: sessionManager.broadcastManagedSessions,
|
|
143
|
+
|
|
144
|
+
// Sessions
|
|
145
|
+
getManagedSessions: sessionManager.getManagedSessions,
|
|
146
|
+
getManagedSession: sessionManager.getManagedSession,
|
|
147
|
+
createManagedSession: sessionManager.createManagedSession,
|
|
148
|
+
updateManagedSession: sessionManager.updateManagedSession,
|
|
149
|
+
deleteManagedSession: sessionManager.deleteManagedSession,
|
|
150
|
+
sendPromptToManagedSession: sessionManager.sendPromptToManagedSession,
|
|
151
|
+
restartManagedSession: sessionManager.restartManagedSession,
|
|
152
|
+
linkClaudeSession: sessionManager.linkClaudeSession,
|
|
153
|
+
checkSessionHealth: sessionManager.checkSessionHealth,
|
|
154
|
+
saveManagedSessions: sessionManager.saveManagedSessions,
|
|
155
|
+
managedSessions: sessionManager.managedSessions,
|
|
156
|
+
hideSession: sessionManager.hideSession,
|
|
157
|
+
unhideSession: sessionManager.unhideSession,
|
|
158
|
+
permanentDeleteSession: sessionManager.permanentDeleteSession,
|
|
159
|
+
loadHiddenSessions: sessionManager.loadHiddenSessions,
|
|
160
|
+
loadCustomNames: sessionManager.loadCustomNames,
|
|
161
|
+
renameSession: sessionManager.renameSession,
|
|
162
|
+
|
|
163
|
+
// Data
|
|
164
|
+
loadAllTasks: tasks.loadAllTasks,
|
|
165
|
+
updateTask: tasks.updateTask,
|
|
166
|
+
createTask: tasks.createTask,
|
|
167
|
+
loadAllTodos: todos.loadAllTodos,
|
|
168
|
+
getTodosForSession: todos.getTodosForSession,
|
|
169
|
+
updateTodo: todos.updateTodo,
|
|
170
|
+
createTodo: todos.createTodo,
|
|
171
|
+
loadAllPlans: plans.loadAllPlans,
|
|
172
|
+
getPlansForSession: plans.getPlansForSession,
|
|
173
|
+
getPlan: plans.getPlan,
|
|
174
|
+
updatePlan: plans.updatePlan,
|
|
175
|
+
loadConversation: conversation.loadConversation,
|
|
176
|
+
|
|
177
|
+
// Orchestration
|
|
178
|
+
orchestrationData,
|
|
179
|
+
orchestrationExecutor,
|
|
180
|
+
|
|
181
|
+
// Utils
|
|
182
|
+
sendToTmux: sessionManager.sendToTmux
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// =============================================================================
|
|
186
|
+
// HTTP Server
|
|
187
|
+
// =============================================================================
|
|
188
|
+
|
|
189
|
+
const server = http.createServer((req, res) => {
|
|
190
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
191
|
+
|
|
192
|
+
// SSE endpoint
|
|
193
|
+
if (url.pathname === '/events') {
|
|
194
|
+
return routes.handleSSE(req, res);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Health check
|
|
198
|
+
if (url.pathname === '/api/health' && req.method === 'GET') {
|
|
199
|
+
return routes.handleHealth(req, res);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Claude Events
|
|
203
|
+
if (url.pathname === '/api/claude-events' && req.method === 'POST') {
|
|
204
|
+
return routes.handlePostClaudeEvent(req, res);
|
|
205
|
+
}
|
|
206
|
+
if (url.pathname === '/api/claude-events' && req.method === 'GET') {
|
|
207
|
+
return routes.handleGetClaudeEvents(req, res, url);
|
|
208
|
+
}
|
|
209
|
+
if (url.pathname === '/api/claude-events/stats' && req.method === 'GET') {
|
|
210
|
+
return routes.handleGetClaudeEventStats(req, res);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Tasks
|
|
214
|
+
if (url.pathname === '/api/tasks' && req.method === 'GET') {
|
|
215
|
+
return routes.handleGetTasks(req, res);
|
|
216
|
+
}
|
|
217
|
+
if (url.pathname === '/api/tasks/bulk-update' && req.method === 'POST') {
|
|
218
|
+
return routes.handleBulkUpdateTasks(req, res);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Todos
|
|
222
|
+
if (url.pathname === '/api/todos' && req.method === 'GET') {
|
|
223
|
+
return routes.handleGetAllTodos(req, res);
|
|
224
|
+
}
|
|
225
|
+
if (url.pathname === '/api/todos/bulk-update' && req.method === 'POST') {
|
|
226
|
+
return routes.handleBulkUpdateTodos(req, res);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const getTodosMatch = url.pathname.match(/^\/api\/todos\/([a-f0-9-]+)$/);
|
|
230
|
+
if (getTodosMatch && req.method === 'GET') {
|
|
231
|
+
return routes.handleGetTodosForSession(req, res, getTodosMatch[1]);
|
|
232
|
+
}
|
|
233
|
+
if (getTodosMatch && req.method === 'POST') {
|
|
234
|
+
return routes.handleCreateTodo(req, res, getTodosMatch[1]);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const updateTodoMatch = url.pathname.match(/^\/api\/todos\/([a-f0-9-]+)\/(\d+)$/);
|
|
238
|
+
if (updateTodoMatch && req.method === 'PATCH') {
|
|
239
|
+
return routes.handleUpdateTodo(req, res, updateTodoMatch[1], updateTodoMatch[2]);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Plans
|
|
243
|
+
if (url.pathname === '/api/plans' && req.method === 'GET') {
|
|
244
|
+
return routes.handleGetAllPlans(req, res);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const getPlansForSessionMatch = url.pathname.match(/^\/api\/plans\/session\/([a-f0-9-]+)$/);
|
|
248
|
+
if (getPlansForSessionMatch && req.method === 'GET') {
|
|
249
|
+
return routes.handleGetPlansForSession(req, res, getPlansForSessionMatch[1]);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const getPlanMatch = url.pathname.match(/^\/api\/plans\/([a-z0-9-]+)$/);
|
|
253
|
+
if (getPlanMatch && req.method === 'GET') {
|
|
254
|
+
return routes.handleGetPlan(req, res, getPlanMatch[1]);
|
|
255
|
+
}
|
|
256
|
+
if (getPlanMatch && req.method === 'PUT') {
|
|
257
|
+
return routes.handleUpdatePlan(req, res, getPlanMatch[1]);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Managed Sessions
|
|
261
|
+
if (url.pathname === '/api/managed-sessions' && req.method === 'GET') {
|
|
262
|
+
return routes.handleGetManagedSessions(req, res);
|
|
263
|
+
}
|
|
264
|
+
if (url.pathname === '/api/managed-sessions' && req.method === 'POST') {
|
|
265
|
+
return routes.handleCreateManagedSession(req, res);
|
|
266
|
+
}
|
|
267
|
+
if (url.pathname === '/api/managed-sessions/refresh' && req.method === 'POST') {
|
|
268
|
+
return routes.handleRefreshSessions(req, res);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const getManagedMatch = url.pathname.match(/^\/api\/managed-sessions\/([^/]+)$/);
|
|
272
|
+
if (getManagedMatch && req.method === 'GET') {
|
|
273
|
+
return routes.handleGetManagedSession(req, res, getManagedMatch[1]);
|
|
274
|
+
}
|
|
275
|
+
if (getManagedMatch && req.method === 'PATCH') {
|
|
276
|
+
return routes.handleUpdateManagedSession(req, res, getManagedMatch[1]);
|
|
277
|
+
}
|
|
278
|
+
if (getManagedMatch && req.method === 'DELETE') {
|
|
279
|
+
return routes.handleDeleteSession(req, res, getManagedMatch[1]);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const hideManagedMatch = url.pathname.match(/^\/api\/managed-sessions\/([^/]+)\/hide$/);
|
|
283
|
+
if (hideManagedMatch && req.method === 'POST') {
|
|
284
|
+
return routes.handleHideSession(req, res, hideManagedMatch[1]);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const unhideManagedMatch = url.pathname.match(/^\/api\/managed-sessions\/([^/]+)\/unhide$/);
|
|
288
|
+
if (unhideManagedMatch && req.method === 'POST') {
|
|
289
|
+
return routes.handleUnhideSession(req, res, unhideManagedMatch[1]);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (url.pathname === '/api/hidden-sessions' && req.method === 'GET') {
|
|
293
|
+
return routes.handleGetHiddenSessions(req, res);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const permanentDeleteMatch = url.pathname.match(/^\/api\/managed-sessions\/([^/]+)\/permanent$/);
|
|
297
|
+
if (permanentDeleteMatch && req.method === 'DELETE') {
|
|
298
|
+
return routes.handlePermanentDeleteSession(req, res, permanentDeleteMatch[1]);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const promptManagedMatch = url.pathname.match(/^\/api\/managed-sessions\/([^/]+)\/prompt$/);
|
|
302
|
+
if (promptManagedMatch && req.method === 'POST') {
|
|
303
|
+
return routes.handleSendPromptToSession(req, res, promptManagedMatch[1]);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const restartManagedMatch = url.pathname.match(/^\/api\/managed-sessions\/([^/]+)\/restart$/);
|
|
307
|
+
if (restartManagedMatch && req.method === 'POST') {
|
|
308
|
+
return routes.handleRestartSession(req, res, restartManagedMatch[1]);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const linkManagedMatch = url.pathname.match(/^\/api\/managed-sessions\/([^/]+)\/link$/);
|
|
312
|
+
if (linkManagedMatch && req.method === 'POST') {
|
|
313
|
+
return routes.handleLinkSession(req, res, linkManagedMatch[1]);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Conversation
|
|
317
|
+
const convMatch = url.pathname.match(/^\/api\/conversation\/([^/]+)$/);
|
|
318
|
+
if (convMatch && req.method === 'GET') {
|
|
319
|
+
return routes.handleGetConversation(req, res, convMatch[1]);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Prompt
|
|
323
|
+
if (url.pathname === '/api/prompt' && req.method === 'POST') {
|
|
324
|
+
return routes.handleSendPrompt(req, res);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Session rename
|
|
328
|
+
const renameMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/rename$/);
|
|
329
|
+
if (renameMatch && req.method === 'POST') {
|
|
330
|
+
return routes.handleRenameSession(req, res, renameMatch[1]);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Orchestration routes
|
|
334
|
+
if (url.pathname === '/api/orchestrations' && req.method === 'GET') {
|
|
335
|
+
return routes.handleGetOrchestrations(req, res);
|
|
336
|
+
}
|
|
337
|
+
if (url.pathname === '/api/orchestrations' && req.method === 'POST') {
|
|
338
|
+
return routes.handleCreateOrchestration(req, res);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const orchestrationMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)$/);
|
|
342
|
+
if (orchestrationMatch && req.method === 'GET') {
|
|
343
|
+
return routes.handleGetOrchestration(req, res, orchestrationMatch[1]);
|
|
344
|
+
}
|
|
345
|
+
if (orchestrationMatch && req.method === 'PATCH') {
|
|
346
|
+
return routes.handleUpdateOrchestration(req, res, orchestrationMatch[1]);
|
|
347
|
+
}
|
|
348
|
+
if (orchestrationMatch && req.method === 'DELETE') {
|
|
349
|
+
return routes.handleDeleteOrchestration(req, res, orchestrationMatch[1]);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const orchestrationStartMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/start$/);
|
|
353
|
+
if (orchestrationStartMatch && req.method === 'POST') {
|
|
354
|
+
return routes.handleStartOrchestration(req, res, orchestrationStartMatch[1]);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const orchestrationStopMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/stop$/);
|
|
358
|
+
if (orchestrationStopMatch && req.method === 'POST') {
|
|
359
|
+
return routes.handleStopOrchestration(req, res, orchestrationStopMatch[1]);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Agent routes
|
|
363
|
+
const agentsMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents$/);
|
|
364
|
+
if (agentsMatch && req.method === 'POST') {
|
|
365
|
+
return routes.handleAddAgent(req, res, agentsMatch[1]);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const agentMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)$/);
|
|
369
|
+
if (agentMatch && req.method === 'PATCH') {
|
|
370
|
+
return routes.handleUpdateAgent(req, res, agentMatch[1], agentMatch[2]);
|
|
371
|
+
}
|
|
372
|
+
if (agentMatch && req.method === 'DELETE') {
|
|
373
|
+
return routes.handleRemoveAgent(req, res, agentMatch[1], agentMatch[2]);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const agentSpawnMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)\/spawn$/);
|
|
377
|
+
if (agentSpawnMatch && req.method === 'POST') {
|
|
378
|
+
return routes.handleSpawnAgent(req, res, agentSpawnMatch[1], agentSpawnMatch[2]);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const agentKillMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)\/kill$/);
|
|
382
|
+
if (agentKillMatch && req.method === 'POST') {
|
|
383
|
+
return routes.handleKillAgent(req, res, agentKillMatch[1], agentKillMatch[2]);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const agentPromptMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)\/prompt$/);
|
|
387
|
+
if (agentPromptMatch && req.method === 'POST') {
|
|
388
|
+
return routes.handleSendPromptToAgent(req, res, agentPromptMatch[1], agentPromptMatch[2]);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const agentStatusMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)\/status$/);
|
|
392
|
+
if (agentStatusMatch && req.method === 'GET') {
|
|
393
|
+
return routes.handleGetAgentStatus(req, res, agentStatusMatch[1], agentStatusMatch[2]);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const agentDependencyMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)\/dependencies$/);
|
|
397
|
+
if (agentDependencyMatch && req.method === 'POST') {
|
|
398
|
+
return routes.handleAddAgentDependency(req, res, agentDependencyMatch[1], agentDependencyMatch[2]);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const agentDependencyRemoveMatch = url.pathname.match(/^\/api\/orchestrations\/([^/]+)\/agents\/([^/]+)\/dependencies\/([^/]+)$/);
|
|
402
|
+
if (agentDependencyRemoveMatch && req.method === 'DELETE') {
|
|
403
|
+
return routes.handleRemoveAgentDependency(req, res, agentDependencyRemoveMatch[1], agentDependencyRemoveMatch[2], agentDependencyRemoveMatch[3]);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Orchestration template routes
|
|
407
|
+
if (url.pathname === '/api/orchestration-templates' && req.method === 'GET') {
|
|
408
|
+
return routes.handleGetTemplates(req, res);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const templateMatch = url.pathname.match(/^\/api\/orchestration-templates\/([^/]+)$/);
|
|
412
|
+
if (templateMatch && req.method === 'GET') {
|
|
413
|
+
return routes.handleGetTemplate(req, res, templateMatch[1]);
|
|
414
|
+
}
|
|
415
|
+
if (templateMatch && req.method === 'POST') {
|
|
416
|
+
return routes.handleCreateFromTemplate(req, res, templateMatch[1]);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Task routes
|
|
420
|
+
const taskMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)\/(\d+)$/);
|
|
421
|
+
if (taskMatch && req.method === 'PATCH') {
|
|
422
|
+
return routes.handleUpdateTask(req, res, taskMatch[1], taskMatch[2]);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const createMatch = url.pathname.match(/^\/api\/tasks\/([^/]+)$/);
|
|
426
|
+
if (createMatch && req.method === 'POST') {
|
|
427
|
+
return routes.handleCreateTask(req, res, createMatch[1]);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Serve HTML
|
|
431
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
432
|
+
res.end(HTML);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// =============================================================================
|
|
436
|
+
// Server Startup
|
|
437
|
+
// =============================================================================
|
|
438
|
+
|
|
439
|
+
function start() {
|
|
440
|
+
const { PORT, TASKS_DIR, TMUX_SESSION } = config;
|
|
441
|
+
|
|
442
|
+
server.listen(PORT, () => {
|
|
443
|
+
console.log(`\n Claude Tasks running at:\n`);
|
|
444
|
+
console.log(` → http://localhost:${PORT}\n`);
|
|
445
|
+
console.log(` Reading tasks from: ${TASKS_DIR}`);
|
|
446
|
+
console.log(` Sending prompts to tmux session: ${TMUX_SESSION}`);
|
|
447
|
+
console.log(` (Set TMUX_SESSION env var to change)\n`);
|
|
448
|
+
|
|
449
|
+
// Load HTML template
|
|
450
|
+
loadHTML();
|
|
451
|
+
|
|
452
|
+
// Initialize EventBus handlers
|
|
453
|
+
registerAllHandlers();
|
|
454
|
+
console.log(` EventBus initialized with ${eventBus.getHandlerCount()} handlers`);
|
|
455
|
+
|
|
456
|
+
// Initialize Claude events module
|
|
457
|
+
claudeEvents.init();
|
|
458
|
+
|
|
459
|
+
// Initialize session manager
|
|
460
|
+
sessionManager.init();
|
|
461
|
+
|
|
462
|
+
// Initialize orchestration executor
|
|
463
|
+
orchestrationExecutor.init();
|
|
464
|
+
|
|
465
|
+
// Set up file watchers
|
|
466
|
+
setupWatchers();
|
|
467
|
+
|
|
468
|
+
// Build plan session cache
|
|
469
|
+
plans.buildPlanSessionCache();
|
|
470
|
+
|
|
471
|
+
console.log(` Press Ctrl+C to stop\n`);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Export for testing
|
|
476
|
+
module.exports = { server, start };
|
|
477
|
+
|
|
478
|
+
// Start if run directly
|
|
479
|
+
if (require.main === module) {
|
|
480
|
+
start();
|
|
481
|
+
}
|