@ebowwa/terminal 0.2.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/src/api.ts ADDED
@@ -0,0 +1,752 @@
1
+ /**
2
+ * API routes for Multi-Node Tmux Session Manager
3
+ * Provides REST API for managing tmux sessions across multiple VPS nodes
4
+ */
5
+
6
+ import { Hono } from "hono";
7
+ import { z } from "zod";
8
+ import { addActivity } from "../lib/activities";
9
+ import {
10
+ getTmuxManager,
11
+ type Node,
12
+ type BatchOperationResult,
13
+ type DetailedTmuxSession,
14
+ } from "./tmux-manager.js";
15
+ import {
16
+ TmuxManagerAddNodeSchema,
17
+ TmuxManagerCreateSessionSchema,
18
+ TmuxManagerAttachSessionSchema,
19
+ TmuxManagerKillSessionSchema,
20
+ TmuxManagerSendCommandSchema,
21
+ TmuxManagerBatchSendCommandSchema,
22
+ TmuxManagerSplitPaneSchema,
23
+ TmuxManagerCapturePaneSchema,
24
+ TmuxManagerGetHistorySchema,
25
+ TmuxManagerListWindowsSchema,
26
+ TmuxManagerListPanesSchema,
27
+ TmuxManagerSwitchWindowSchema,
28
+ TmuxManagerSwitchPaneSchema,
29
+ TmuxManagerRenameWindowSchema,
30
+ TmuxManagerKillPaneSchema,
31
+ TmuxManagerCleanupOldSessionsSchema,
32
+ TmuxManagerDetailedSessionSchema,
33
+ TmuxManagerListSessionsQuerySchema,
34
+ } from "@ebowwa/codespaces-types/runtime/api";
35
+
36
+ const tmuxApi = new Hono();
37
+
38
+ /**
39
+ * Helper function to validate request body against Zod schema
40
+ */
41
+ function validateRequest<T>(
42
+ schema: z.ZodSchema<T>,
43
+ data: unknown
44
+ ): { success: true; data: T } | { success: false; error: string } {
45
+ try {
46
+ const result = schema.safeParse(data);
47
+ if (result.success) {
48
+ return { success: true, data: result.data };
49
+ } else {
50
+ return {
51
+ success: false,
52
+ error: result.error.issues
53
+ .map((e) => `${e.path.join(".")}: ${e.message}`)
54
+ .join(", "),
55
+ };
56
+ }
57
+ } catch (error) {
58
+ return { success: false, error: String(error) };
59
+ }
60
+ }
61
+
62
+ // ============================================
63
+ // Node Management
64
+ // ============================================
65
+
66
+ /**
67
+ * POST /api/tmux/manager/nodes - Add a node to the manager
68
+ */
69
+ tmuxApi.post("/manager/nodes", async (c) => {
70
+ const body = await c.req.json();
71
+ const validation = validateRequest(TmuxManagerAddNodeSchema, body);
72
+
73
+ if (!validation.success) {
74
+ return c.json({ success: false, error: validation.error }, 400);
75
+ }
76
+
77
+ try {
78
+ const manager = getTmuxManager();
79
+ manager.addNode(validation.data);
80
+ return c.json({ success: true, node: validation.data });
81
+ } catch (error) {
82
+ return c.json({ success: false, error: String(error) }, 500);
83
+ }
84
+ });
85
+
86
+ /**
87
+ * GET /api/tmux/manager/nodes - List all nodes
88
+ */
89
+ tmuxApi.get("/manager/nodes", async (c) => {
90
+ try {
91
+ const manager = getTmuxManager();
92
+ const nodes = manager.getAllNodes();
93
+ return c.json({ success: true, nodes });
94
+ } catch (error) {
95
+ return c.json({ success: false, error: String(error) }, 500);
96
+ }
97
+ });
98
+
99
+ /**
100
+ * GET /api/tmux/manager/nodes/:id - Get a specific node
101
+ */
102
+ tmuxApi.get("/manager/nodes/:id", async (c) => {
103
+ const id = c.req.param("id");
104
+ if (!id) {
105
+ return c.json({ success: false, error: "Node ID is required" }, 400);
106
+ }
107
+
108
+ try {
109
+ const manager = getTmuxManager();
110
+ const node = manager.getNode(id);
111
+ if (!node) {
112
+ return c.json({ success: false, error: "Node not found" }, 404);
113
+ }
114
+ return c.json({ success: true, node });
115
+ } catch (error) {
116
+ return c.json({ success: false, error: String(error) }, 500);
117
+ }
118
+ });
119
+
120
+ /**
121
+ * DELETE /api/tmux/manager/nodes/:id - Remove a node
122
+ */
123
+ tmuxApi.delete("/manager/nodes/:id", async (c) => {
124
+ const id = c.req.param("id");
125
+ if (!id) {
126
+ return c.json({ success: false, error: "Node ID is required" }, 400);
127
+ }
128
+
129
+ try {
130
+ const manager = getTmuxManager();
131
+ const node = manager.getNode(id);
132
+ if (!node) {
133
+ return c.json({ success: false, error: "Node not found" }, 404);
134
+ }
135
+ manager.removeNode(id);
136
+ return c.json({ success: true });
137
+ } catch (error) {
138
+ return c.json({ success: false, error: String(error) }, 500);
139
+ }
140
+ });
141
+
142
+ // ============================================
143
+ // Session Management
144
+ // ============================================
145
+
146
+ /**
147
+ * POST /api/tmux/manager/sessions - Create a new session
148
+ */
149
+ tmuxApi.post("/manager/sessions", async (c) => {
150
+ const body = await c.req.json();
151
+ const validation = validateRequest(TmuxManagerCreateSessionSchema, body);
152
+
153
+ if (!validation.success) {
154
+ return c.json({ success: false, error: validation.error }, 400);
155
+ }
156
+
157
+ try {
158
+ const manager = getTmuxManager();
159
+ const result = await manager.createSession(
160
+ validation.data.nodeId,
161
+ validation.data
162
+ );
163
+
164
+ if (result.success) {
165
+ const node = manager.getNode(validation.data.nodeId);
166
+ if (node) {
167
+ addActivity({
168
+ environmentId: node.id,
169
+ action: "tmux_session_created",
170
+ environmentName: node.name,
171
+ details: result.sessionName
172
+ ? `Session "${result.sessionName}" created`
173
+ : "Session created",
174
+ });
175
+ }
176
+ }
177
+
178
+ return c.json(result);
179
+ } catch (error) {
180
+ return c.json({ success: false, error: String(error) }, 500);
181
+ }
182
+ });
183
+
184
+ /**
185
+ * GET /api/tmux/manager/sessions - List all sessions
186
+ */
187
+ tmuxApi.get("/manager/sessions", async (c) => {
188
+ try {
189
+ const manager = getTmuxManager();
190
+
191
+ // Parse query parameters
192
+ const nodeIds = c.req.queries()["nodeIds"];
193
+ const tags = c.req.queries()["tags"];
194
+ const detailed = c.req.query("detailed") === "true";
195
+ const includeInactive = c.req.query("includeInactive") === "true";
196
+
197
+ const sessions = await manager.listSessions({
198
+ nodeIds,
199
+ tags,
200
+ detailed,
201
+ includeInactive,
202
+ });
203
+
204
+ return c.json({ success: true, sessions });
205
+ } catch (error) {
206
+ return c.json({ success: false, error: String(error) }, 500);
207
+ }
208
+ });
209
+
210
+ /**
211
+ * POST /api/tmux/manager/sessions/attach - Attach to a session
212
+ */
213
+ tmuxApi.post("/manager/sessions/attach", async (c) => {
214
+ const body = await c.req.json();
215
+ const validation = validateRequest(TmuxManagerAttachSessionSchema, body);
216
+
217
+ if (!validation.success) {
218
+ return c.json({ success: false, error: validation.error }, 400);
219
+ }
220
+
221
+ try {
222
+ const manager = getTmuxManager();
223
+ const result = await manager.attachSession(
224
+ validation.data.nodeId,
225
+ validation.data.sessionName
226
+ );
227
+
228
+ if (result.success) {
229
+ const node = manager.getNode(validation.data.nodeId);
230
+ if (node) {
231
+ addActivity({
232
+ environmentId: node.id,
233
+ action: "tmux_session_attached",
234
+ environmentName: node.name,
235
+ details: `Attached to session "${result.sessionName}"`,
236
+ });
237
+ }
238
+ }
239
+
240
+ return c.json(result);
241
+ } catch (error) {
242
+ return c.json({ success: false, error: String(error) }, 500);
243
+ }
244
+ });
245
+
246
+ /**
247
+ * DELETE /api/tmux/manager/sessions - Kill a session
248
+ */
249
+ tmuxApi.delete("/manager/sessions", async (c) => {
250
+ const body = await c.req.json();
251
+ const validation = validateRequest(TmuxManagerKillSessionSchema, body);
252
+
253
+ if (!validation.success) {
254
+ return c.json({ success: false, error: validation.error }, 400);
255
+ }
256
+
257
+ try {
258
+ const manager = getTmuxManager();
259
+ const node = manager.getNode(validation.data.nodeId);
260
+ const result = await manager.killSession(
261
+ validation.data.nodeId,
262
+ validation.data.sessionName
263
+ );
264
+
265
+ if (result.success && node) {
266
+ addActivity({
267
+ environmentId: node.id,
268
+ action: "tmux_session_killed",
269
+ environmentName: node.name,
270
+ details: `Session "${validation.data.sessionName}" killed`,
271
+ });
272
+ }
273
+
274
+ return c.json(result);
275
+ } catch (error) {
276
+ return c.json({ success: false, error: String(error) }, 500);
277
+ }
278
+ });
279
+
280
+ // ============================================
281
+ // Command Execution
282
+ // ============================================
283
+
284
+ /**
285
+ * POST /api/tmux/manager/sessions/command - Send command to a session
286
+ */
287
+ tmuxApi.post("/manager/sessions/command", async (c) => {
288
+ const body = await c.req.json();
289
+ const validation = validateRequest(TmuxManagerSendCommandSchema, body);
290
+
291
+ if (!validation.success) {
292
+ return c.json({ success: false, error: validation.error }, 400);
293
+ }
294
+
295
+ try {
296
+ const manager = getTmuxManager();
297
+ const node = manager.getNode(validation.data.nodeId);
298
+ const result = await manager.sendCommand(
299
+ validation.data.nodeId,
300
+ validation.data.sessionName,
301
+ validation.data.command,
302
+ validation.data.paneIndex
303
+ );
304
+
305
+ if (result.success && node) {
306
+ addActivity({
307
+ environmentId: node.id,
308
+ action: "tmux_command_sent",
309
+ environmentName: node.name,
310
+ details: `Command: ${validation.data.command.slice(0, 100)}${validation.data.command.length > 100 ? "..." : ""}`,
311
+ });
312
+ }
313
+
314
+ return c.json(result);
315
+ } catch (error) {
316
+ return c.json({ success: false, error: String(error) }, 500);
317
+ }
318
+ });
319
+
320
+ /**
321
+ * POST /api/tmux/manager/sessions/command/batch - Send command to multiple nodes
322
+ */
323
+ tmuxApi.post("/manager/sessions/command/batch", async (c) => {
324
+ const body = await c.req.json();
325
+ const validation = validateRequest(TmuxManagerBatchSendCommandSchema, body);
326
+
327
+ if (!validation.success) {
328
+ return c.json({ success: false, error: validation.error }, 400);
329
+ }
330
+
331
+ try {
332
+ const manager = getTmuxManager();
333
+ const result: BatchOperationResult = await manager.sendCommandToNodes(
334
+ validation.data.nodeIds,
335
+ validation.data.sessionName,
336
+ validation.data.command,
337
+ {
338
+ paneIndex: validation.data.paneIndex,
339
+ parallel: validation.data.parallel,
340
+ continueOnError: validation.data.continueOnError,
341
+ timeout: validation.data.timeout,
342
+ }
343
+ );
344
+
345
+ // Log activity per node
346
+ for (const r of result.results) {
347
+ if (r.success) {
348
+ const node = manager.getNode(r.nodeId);
349
+ if (node) {
350
+ addActivity({
351
+ environmentId: node.id,
352
+ action: "tmux_batch_command_sent",
353
+ environmentName: node.name,
354
+ details: `Batch command to ${result.total} nodes: ${validation.data.command.slice(0, 80)}${validation.data.command.length > 80 ? "..." : ""}`,
355
+ });
356
+ }
357
+ }
358
+ }
359
+
360
+ return c.json({ success: true, result });
361
+ } catch (error) {
362
+ return c.json({ success: false, error: String(error) }, 500);
363
+ }
364
+ });
365
+
366
+ // ============================================
367
+ // Pane Operations
368
+ // ============================================
369
+
370
+ /**
371
+ * POST /api/tmux/manager/sessions/split - Split a pane
372
+ */
373
+ tmuxApi.post("/manager/sessions/split", async (c) => {
374
+ const body = await c.req.json();
375
+ const validation = validateRequest(TmuxManagerSplitPaneSchema, body);
376
+
377
+ if (!validation.success) {
378
+ return c.json({ success: false, error: validation.error }, 400);
379
+ }
380
+
381
+ try {
382
+ const manager = getTmuxManager();
383
+ const node = manager.getNode(validation.data.nodeId);
384
+ const result = await manager.splitPaneOnNode(
385
+ validation.data.nodeId,
386
+ validation.data.sessionName,
387
+ validation.data.direction,
388
+ validation.data.command,
389
+ validation.data.windowIndex
390
+ );
391
+
392
+ if (result.success && node) {
393
+ addActivity({
394
+ environmentId: node.id,
395
+ action: "tmux_pane_split",
396
+ environmentName: node.name,
397
+ details: `Split pane ${validation.data.direction === "h" ? "horizontally" : "vertically"}${validation.data.command ? ` with command: ${validation.data.command.slice(0, 50)}...` : ""}`,
398
+ });
399
+ }
400
+
401
+ return c.json(result);
402
+ } catch (error) {
403
+ return c.json({ success: false, error: String(error) }, 500);
404
+ }
405
+ });
406
+
407
+ /**
408
+ * POST /api/tmux/manager/sessions/capture - Capture pane output
409
+ */
410
+ tmuxApi.post("/manager/sessions/capture", async (c) => {
411
+ const body = await c.req.json();
412
+ const validation = validateRequest(TmuxManagerCapturePaneSchema, body);
413
+
414
+ if (!validation.success) {
415
+ return c.json({ success: false, error: validation.error }, 400);
416
+ }
417
+
418
+ try {
419
+ const manager = getTmuxManager();
420
+ const result = await manager.capturePaneOutput(
421
+ validation.data.nodeId,
422
+ validation.data.sessionName,
423
+ validation.data.paneIndex
424
+ );
425
+ return c.json(result);
426
+ } catch (error) {
427
+ return c.json({ success: false, error: String(error) }, 500);
428
+ }
429
+ });
430
+
431
+ /**
432
+ * POST /api/tmux/manager/sessions/history - Get pane history
433
+ */
434
+ tmuxApi.post("/manager/sessions/history", async (c) => {
435
+ const body = await c.req.json();
436
+ const validation = validateRequest(TmuxManagerGetHistorySchema, body);
437
+
438
+ if (!validation.success) {
439
+ return c.json({ success: false, error: validation.error }, 400);
440
+ }
441
+
442
+ try {
443
+ const manager = getTmuxManager();
444
+ const result = await manager.getPaneHistory(
445
+ validation.data.nodeId,
446
+ validation.data.sessionName,
447
+ validation.data.paneIndex,
448
+ validation.data.lines
449
+ );
450
+ return c.json(result);
451
+ } catch (error) {
452
+ return c.json({ success: false, error: String(error) }, 500);
453
+ }
454
+ });
455
+
456
+ /**
457
+ * DELETE /api/tmux/manager/sessions/pane - Kill a pane
458
+ */
459
+ tmuxApi.delete("/manager/sessions/pane", async (c) => {
460
+ const body = await c.req.json();
461
+ const validation = validateRequest(TmuxManagerKillPaneSchema, body);
462
+
463
+ if (!validation.success) {
464
+ return c.json({ success: false, error: validation.error }, 400);
465
+ }
466
+
467
+ try {
468
+ const manager = getTmuxManager();
469
+ const node = manager.getNode(validation.data.nodeId);
470
+ const result = await manager.killPaneInSession(
471
+ validation.data.nodeId,
472
+ validation.data.sessionName,
473
+ validation.data.paneIndex
474
+ );
475
+
476
+ if (result.success && node) {
477
+ addActivity({
478
+ environmentId: node.id,
479
+ action: "tmux_pane_killed",
480
+ environmentName: node.name,
481
+ details: `Pane ${validation.data.paneIndex} killed in session "${validation.data.sessionName}"`,
482
+ });
483
+ }
484
+
485
+ return c.json(result);
486
+ } catch (error) {
487
+ return c.json({ success: false, error: String(error) }, 500);
488
+ }
489
+ });
490
+
491
+ // ============================================
492
+ // Window Operations
493
+ // ============================================
494
+
495
+ /**
496
+ * POST /api/tmux/manager/sessions/windows - List windows in a session
497
+ */
498
+ tmuxApi.post("/manager/sessions/windows", async (c) => {
499
+ const body = await c.req.json();
500
+ const validation = validateRequest(TmuxManagerListWindowsSchema, body);
501
+
502
+ if (!validation.success) {
503
+ return c.json({ success: false, error: validation.error }, 400);
504
+ }
505
+
506
+ try {
507
+ const manager = getTmuxManager();
508
+ const result = await manager.listWindows(
509
+ validation.data.nodeId,
510
+ validation.data.sessionName
511
+ );
512
+ return c.json(result);
513
+ } catch (error) {
514
+ return c.json({ success: false, error: String(error) }, 500);
515
+ }
516
+ });
517
+
518
+ /**
519
+ * POST /api/tmux/manager/sessions/panes - List panes in a window
520
+ */
521
+ tmuxApi.post("/manager/sessions/panes", async (c) => {
522
+ const body = await c.req.json();
523
+ const validation = validateRequest(TmuxManagerListPanesSchema, body);
524
+
525
+ if (!validation.success) {
526
+ return c.json({ success: false, error: validation.error }, 400);
527
+ }
528
+
529
+ try {
530
+ const manager = getTmuxManager();
531
+ const result = await manager.listPanes(
532
+ validation.data.nodeId,
533
+ validation.data.sessionName,
534
+ validation.data.windowIndex
535
+ );
536
+ return c.json(result);
537
+ } catch (error) {
538
+ return c.json({ success: false, error: String(error) }, 500);
539
+ }
540
+ });
541
+
542
+ /**
543
+ * POST /api/tmux/manager/sessions/windows/switch - Switch to a window
544
+ */
545
+ tmuxApi.post("/manager/sessions/windows/switch", async (c) => {
546
+ const body = await c.req.json();
547
+ const validation = validateRequest(TmuxManagerSwitchWindowSchema, body);
548
+
549
+ if (!validation.success) {
550
+ return c.json({ success: false, error: validation.error }, 400);
551
+ }
552
+
553
+ try {
554
+ const manager = getTmuxManager();
555
+ const node = manager.getNode(validation.data.nodeId);
556
+ const result = await manager.switchToWindow(
557
+ validation.data.nodeId,
558
+ validation.data.sessionName,
559
+ validation.data.windowIndex
560
+ );
561
+
562
+ if (result.success && node) {
563
+ addActivity({
564
+ environmentId: node.id,
565
+ action: "tmux_window_switched",
566
+ environmentName: node.name,
567
+ details: `Switched to window ${validation.data.windowIndex} in session "${validation.data.sessionName}"`,
568
+ });
569
+ }
570
+
571
+ return c.json(result);
572
+ } catch (error) {
573
+ return c.json({ success: false, error: String(error) }, 500);
574
+ }
575
+ });
576
+
577
+ /**
578
+ * POST /api/tmux/manager/sessions/panes/switch - Switch to a pane
579
+ */
580
+ tmuxApi.post("/manager/sessions/panes/switch", async (c) => {
581
+ const body = await c.req.json();
582
+ const validation = validateRequest(TmuxManagerSwitchPaneSchema, body);
583
+
584
+ if (!validation.success) {
585
+ return c.json({ success: false, error: validation.error }, 400);
586
+ }
587
+
588
+ try {
589
+ const manager = getTmuxManager();
590
+ const node = manager.getNode(validation.data.nodeId);
591
+ const result = await manager.switchToPane(
592
+ validation.data.nodeId,
593
+ validation.data.sessionName,
594
+ validation.data.paneIndex
595
+ );
596
+
597
+ if (result.success && node) {
598
+ addActivity({
599
+ environmentId: node.id,
600
+ action: "tmux_pane_switched",
601
+ environmentName: node.name,
602
+ details: `Switched to pane ${validation.data.paneIndex} in session "${validation.data.sessionName}"`,
603
+ });
604
+ }
605
+
606
+ return c.json(result);
607
+ } catch (error) {
608
+ return c.json({ success: false, error: String(error) }, 500);
609
+ }
610
+ });
611
+
612
+ /**
613
+ * PUT /api/tmux/manager/sessions/windows/rename - Rename a window
614
+ */
615
+ tmuxApi.put("/manager/sessions/windows/rename", async (c) => {
616
+ const body = await c.req.json();
617
+ const validation = validateRequest(TmuxManagerRenameWindowSchema, body);
618
+
619
+ if (!validation.success) {
620
+ return c.json({ success: false, error: validation.error }, 400);
621
+ }
622
+
623
+ try {
624
+ const manager = getTmuxManager();
625
+ const node = manager.getNode(validation.data.nodeId);
626
+ const result = await manager.renameWindowInSession(
627
+ validation.data.nodeId,
628
+ validation.data.sessionName,
629
+ validation.data.windowIndex,
630
+ validation.data.newName
631
+ );
632
+
633
+ if (result.success && node) {
634
+ addActivity({
635
+ environmentId: node.id,
636
+ action: "tmux_window_renamed",
637
+ environmentName: node.name,
638
+ details: `Renamed window ${validation.data.windowIndex} to "${validation.data.newName}" in session "${validation.data.sessionName}"`,
639
+ });
640
+ }
641
+
642
+ return c.json(result);
643
+ } catch (error) {
644
+ return c.json({ success: false, error: String(error) }, 500);
645
+ }
646
+ });
647
+
648
+ // ============================================
649
+ // Maintenance & Monitoring
650
+ // ============================================
651
+
652
+ /**
653
+ * GET /api/tmux/manager/sessions/detailed - Get detailed session info
654
+ */
655
+ tmuxApi.get("/manager/sessions/detailed", async (c) => {
656
+ const nodeId = c.req.query("nodeId");
657
+ const sessionName = c.req.query("sessionName");
658
+
659
+ if (!nodeId || !sessionName) {
660
+ return c.json({ success: false, error: "nodeId and sessionName are required" }, 400);
661
+ }
662
+
663
+ try {
664
+ const manager = getTmuxManager();
665
+ const result = await manager.getDetailedSession(nodeId, sessionName);
666
+ return c.json({ success: result !== null, session: result });
667
+ } catch (error) {
668
+ return c.json({ success: false, error: String(error) }, 500);
669
+ }
670
+ });
671
+
672
+ /**
673
+ * POST /api/tmux/manager/sessions/cleanup - Cleanup old sessions
674
+ */
675
+ tmuxApi.post("/manager/sessions/cleanup", async (c) => {
676
+ const body = await c.req.json();
677
+ const validation = validateRequest(TmuxManagerCleanupOldSessionsSchema, body);
678
+
679
+ if (!validation.success) {
680
+ return c.json({ success: false, error: validation.error }, 400);
681
+ }
682
+
683
+ try {
684
+ const manager = getTmuxManager();
685
+ const node = manager.getNode(validation.data.nodeId);
686
+ const result = await manager.cleanupOldSessions(
687
+ validation.data.nodeId,
688
+ validation.data.ageLimitMs
689
+ );
690
+
691
+ if (result.success && result.cleaned && result.cleaned > 0 && node) {
692
+ addActivity({
693
+ environmentId: node.id,
694
+ action: "tmux_sessions_cleaned",
695
+ environmentName: node.name,
696
+ details: `Cleaned up ${result.cleaned} old tmux sessions`,
697
+ });
698
+ }
699
+
700
+ return c.json(result);
701
+ } catch (error) {
702
+ return c.json({ success: false, error: String(error) }, 500);
703
+ }
704
+ });
705
+
706
+ /**
707
+ * GET /api/tmux/manager/nodes/:id/resources - Get resource usage for a node
708
+ */
709
+ tmuxApi.get("/manager/nodes/:id/resources", async (c) => {
710
+ const id = c.req.param("id");
711
+ if (!id) {
712
+ return c.json({ success: false, error: "Node ID is required" }, 400);
713
+ }
714
+
715
+ try {
716
+ const manager = getTmuxManager();
717
+ const result = await manager.getResourceUsage(id);
718
+ return c.json(result);
719
+ } catch (error) {
720
+ return c.json({ success: false, error: String(error) }, 500);
721
+ }
722
+ });
723
+
724
+ /**
725
+ * GET /api/tmux/manager/summary - Get summary statistics
726
+ */
727
+ tmuxApi.get("/manager/summary", async (c) => {
728
+ try {
729
+ const manager = getTmuxManager();
730
+ const summary = await manager.getSummary();
731
+ return c.json({ success: true, summary });
732
+ } catch (error) {
733
+ return c.json({ success: false, error: String(error) }, 500);
734
+ }
735
+ });
736
+
737
+ /**
738
+ * POST /api/tmux/manager/cache/invalidate - Invalidate session cache
739
+ */
740
+ tmuxApi.post("/manager/cache/invalidate", async (c) => {
741
+ try {
742
+ const manager = getTmuxManager();
743
+ const body = await c.req.json().catch(() => ({}));
744
+ const nodeId = body?.nodeId;
745
+ manager.invalidateCache(nodeId);
746
+ return c.json({ success: true });
747
+ } catch (error) {
748
+ return c.json({ success: false, error: String(error) }, 500);
749
+ }
750
+ });
751
+
752
+ export { tmuxApi };