247-cli 0.3.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.
Files changed (179) hide show
  1. package/README.md +112 -0
  2. package/agent/dist/config.d.ts +29 -0
  3. package/agent/dist/config.d.ts.map +1 -0
  4. package/agent/dist/config.js +56 -0
  5. package/agent/dist/config.js.map +1 -0
  6. package/agent/dist/db/environments.d.ts +60 -0
  7. package/agent/dist/db/environments.d.ts.map +1 -0
  8. package/agent/dist/db/environments.js +235 -0
  9. package/agent/dist/db/environments.js.map +1 -0
  10. package/agent/dist/db/history.d.ts +37 -0
  11. package/agent/dist/db/history.d.ts.map +1 -0
  12. package/agent/dist/db/history.js +98 -0
  13. package/agent/dist/db/history.js.map +1 -0
  14. package/agent/dist/db/index.d.ts +37 -0
  15. package/agent/dist/db/index.d.ts.map +1 -0
  16. package/agent/dist/db/index.js +225 -0
  17. package/agent/dist/db/index.js.map +1 -0
  18. package/agent/dist/db/schema.d.ts +70 -0
  19. package/agent/dist/db/schema.d.ts.map +1 -0
  20. package/agent/dist/db/schema.js +79 -0
  21. package/agent/dist/db/schema.js.map +1 -0
  22. package/agent/dist/db/sessions.d.ts +75 -0
  23. package/agent/dist/db/sessions.d.ts.map +1 -0
  24. package/agent/dist/db/sessions.js +244 -0
  25. package/agent/dist/db/sessions.js.map +1 -0
  26. package/agent/dist/editor.d.ts +18 -0
  27. package/agent/dist/editor.d.ts.map +1 -0
  28. package/agent/dist/editor.js +222 -0
  29. package/agent/dist/editor.js.map +1 -0
  30. package/agent/dist/environments.d.ts +59 -0
  31. package/agent/dist/environments.d.ts.map +1 -0
  32. package/agent/dist/environments.js +229 -0
  33. package/agent/dist/environments.js.map +1 -0
  34. package/agent/dist/git.d.ts +39 -0
  35. package/agent/dist/git.d.ts.map +1 -0
  36. package/agent/dist/git.js +436 -0
  37. package/agent/dist/git.js.map +1 -0
  38. package/agent/dist/index.d.ts +2 -0
  39. package/agent/dist/index.d.ts.map +1 -0
  40. package/agent/dist/index.js +18 -0
  41. package/agent/dist/index.js.map +1 -0
  42. package/agent/dist/logger.d.ts +15 -0
  43. package/agent/dist/logger.d.ts.map +1 -0
  44. package/agent/dist/logger.js +44 -0
  45. package/agent/dist/logger.js.map +1 -0
  46. package/agent/dist/routes/editor.d.ts +9 -0
  47. package/agent/dist/routes/editor.d.ts.map +1 -0
  48. package/agent/dist/routes/editor.js +63 -0
  49. package/agent/dist/routes/editor.js.map +1 -0
  50. package/agent/dist/routes/environments.d.ts +6 -0
  51. package/agent/dist/routes/environments.d.ts.map +1 -0
  52. package/agent/dist/routes/environments.js +94 -0
  53. package/agent/dist/routes/environments.js.map +1 -0
  54. package/agent/dist/routes/files.d.ts +6 -0
  55. package/agent/dist/routes/files.d.ts.map +1 -0
  56. package/agent/dist/routes/files.js +84 -0
  57. package/agent/dist/routes/files.js.map +1 -0
  58. package/agent/dist/routes/hooks.d.ts +6 -0
  59. package/agent/dist/routes/hooks.d.ts.map +1 -0
  60. package/agent/dist/routes/hooks.js +80 -0
  61. package/agent/dist/routes/hooks.js.map +1 -0
  62. package/agent/dist/routes/index.d.ts +10 -0
  63. package/agent/dist/routes/index.d.ts.map +1 -0
  64. package/agent/dist/routes/index.js +10 -0
  65. package/agent/dist/routes/index.js.map +1 -0
  66. package/agent/dist/routes/projects.d.ts +6 -0
  67. package/agent/dist/routes/projects.d.ts.map +1 -0
  68. package/agent/dist/routes/projects.js +69 -0
  69. package/agent/dist/routes/projects.js.map +1 -0
  70. package/agent/dist/routes/sessions.d.ts +6 -0
  71. package/agent/dist/routes/sessions.d.ts.map +1 -0
  72. package/agent/dist/routes/sessions.js +194 -0
  73. package/agent/dist/routes/sessions.js.map +1 -0
  74. package/agent/dist/server.d.ts +6 -0
  75. package/agent/dist/server.d.ts.map +1 -0
  76. package/agent/dist/server.js +129 -0
  77. package/agent/dist/server.js.map +1 -0
  78. package/agent/dist/status.d.ts +42 -0
  79. package/agent/dist/status.d.ts.map +1 -0
  80. package/agent/dist/status.js +134 -0
  81. package/agent/dist/status.js.map +1 -0
  82. package/agent/dist/terminal.d.ts +15 -0
  83. package/agent/dist/terminal.d.ts.map +1 -0
  84. package/agent/dist/terminal.js +135 -0
  85. package/agent/dist/terminal.js.map +1 -0
  86. package/agent/dist/websocket-handlers.d.ts +13 -0
  87. package/agent/dist/websocket-handlers.d.ts.map +1 -0
  88. package/agent/dist/websocket-handlers.js +266 -0
  89. package/agent/dist/websocket-handlers.js.map +1 -0
  90. package/agent/node_modules/247-shared/dist/index.d.ts +2 -0
  91. package/agent/node_modules/247-shared/dist/index.d.ts.map +1 -0
  92. package/agent/node_modules/247-shared/dist/index.js +2 -0
  93. package/agent/node_modules/247-shared/dist/index.js.map +1 -0
  94. package/agent/node_modules/247-shared/dist/types/index.d.ts +215 -0
  95. package/agent/node_modules/247-shared/dist/types/index.d.ts.map +1 -0
  96. package/agent/node_modules/247-shared/dist/types/index.js +48 -0
  97. package/agent/node_modules/247-shared/dist/types/index.js.map +1 -0
  98. package/agent/node_modules/247-shared/package.json +29 -0
  99. package/dist/commands/doctor.d.ts +3 -0
  100. package/dist/commands/doctor.d.ts.map +1 -0
  101. package/dist/commands/doctor.js +279 -0
  102. package/dist/commands/doctor.js.map +1 -0
  103. package/dist/commands/hooks.d.ts +3 -0
  104. package/dist/commands/hooks.d.ts.map +1 -0
  105. package/dist/commands/hooks.js +127 -0
  106. package/dist/commands/hooks.js.map +1 -0
  107. package/dist/commands/init.d.ts +3 -0
  108. package/dist/commands/init.d.ts.map +1 -0
  109. package/dist/commands/init.js +130 -0
  110. package/dist/commands/init.js.map +1 -0
  111. package/dist/commands/logs.d.ts +3 -0
  112. package/dist/commands/logs.d.ts.map +1 -0
  113. package/dist/commands/logs.js +38 -0
  114. package/dist/commands/logs.js.map +1 -0
  115. package/dist/commands/profile.d.ts +3 -0
  116. package/dist/commands/profile.d.ts.map +1 -0
  117. package/dist/commands/profile.js +156 -0
  118. package/dist/commands/profile.js.map +1 -0
  119. package/dist/commands/service.d.ts +3 -0
  120. package/dist/commands/service.d.ts.map +1 -0
  121. package/dist/commands/service.js +235 -0
  122. package/dist/commands/service.js.map +1 -0
  123. package/dist/commands/start.d.ts +3 -0
  124. package/dist/commands/start.d.ts.map +1 -0
  125. package/dist/commands/start.js +120 -0
  126. package/dist/commands/start.js.map +1 -0
  127. package/dist/commands/status.d.ts +3 -0
  128. package/dist/commands/status.d.ts.map +1 -0
  129. package/dist/commands/status.js +62 -0
  130. package/dist/commands/status.js.map +1 -0
  131. package/dist/commands/stop.d.ts +3 -0
  132. package/dist/commands/stop.d.ts.map +1 -0
  133. package/dist/commands/stop.js +23 -0
  134. package/dist/commands/stop.js.map +1 -0
  135. package/dist/commands/update.d.ts +3 -0
  136. package/dist/commands/update.d.ts.map +1 -0
  137. package/dist/commands/update.js +121 -0
  138. package/dist/commands/update.js.map +1 -0
  139. package/dist/hooks/installer.d.ts +36 -0
  140. package/dist/hooks/installer.d.ts.map +1 -0
  141. package/dist/hooks/installer.js +175 -0
  142. package/dist/hooks/installer.js.map +1 -0
  143. package/dist/index.d.ts +5 -0
  144. package/dist/index.d.ts.map +1 -0
  145. package/dist/index.js +43 -0
  146. package/dist/index.js.map +1 -0
  147. package/dist/lib/config.d.ts +71 -0
  148. package/dist/lib/config.d.ts.map +1 -0
  149. package/dist/lib/config.js +161 -0
  150. package/dist/lib/config.js.map +1 -0
  151. package/dist/lib/paths.d.ts +34 -0
  152. package/dist/lib/paths.d.ts.map +1 -0
  153. package/dist/lib/paths.js +76 -0
  154. package/dist/lib/paths.js.map +1 -0
  155. package/dist/lib/prerequisites.d.ts +36 -0
  156. package/dist/lib/prerequisites.d.ts.map +1 -0
  157. package/dist/lib/prerequisites.js +181 -0
  158. package/dist/lib/prerequisites.js.map +1 -0
  159. package/dist/lib/process.d.ts +40 -0
  160. package/dist/lib/process.d.ts.map +1 -0
  161. package/dist/lib/process.js +192 -0
  162. package/dist/lib/process.js.map +1 -0
  163. package/dist/service/index.d.ts +44 -0
  164. package/dist/service/index.d.ts.map +1 -0
  165. package/dist/service/index.js +18 -0
  166. package/dist/service/index.js.map +1 -0
  167. package/dist/service/launchd.d.ts +18 -0
  168. package/dist/service/launchd.d.ts.map +1 -0
  169. package/dist/service/launchd.js +208 -0
  170. package/dist/service/launchd.js.map +1 -0
  171. package/dist/service/systemd.d.ts +18 -0
  172. package/dist/service/systemd.d.ts.map +1 -0
  173. package/dist/service/systemd.js +196 -0
  174. package/dist/service/systemd.js.map +1 -0
  175. package/hooks/.claude-plugin/plugin.json +5 -0
  176. package/hooks/hooks/hooks.json +66 -0
  177. package/hooks/scripts/check-duplication.sh +91 -0
  178. package/hooks/scripts/notify-status.sh +89 -0
  179. package/package.json +77 -0
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Main server entry point - Express HTTP server with WebSocket support.
3
+ * Routes and handlers are split into separate modules for maintainability.
4
+ */
5
+ export declare function createServer(): Promise<import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>>;
6
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA+BH,wBAAsB,YAAY,gHAgIjC"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Main server entry point - Express HTTP server with WebSocket support.
3
+ * Routes and handlers are split into separate modules for maintainability.
4
+ */
5
+ import express from 'express';
6
+ import cors from 'cors';
7
+ import { WebSocketServer } from 'ws';
8
+ import { createServer as createHttpServer } from 'http';
9
+ import httpProxy from 'http-proxy';
10
+ import { initEditor, shutdownAllEditors } from './editor.js';
11
+ import { initDatabase, closeDatabase, migrateEnvironmentsFromJson } from './db/index.js';
12
+ import { ensureDefaultEnvironment } from './db/environments.js';
13
+ import * as sessionsDb from './db/sessions.js';
14
+ import { config } from './config.js';
15
+ // Routes
16
+ import { createProjectRoutes, createEnvironmentRoutes, createSessionRoutes, createHooksRoutes, createEditorRoutes, createFilesRoutes, isProjectAllowed, updateEditorActivity, getOrStartEditor, } from './routes/index.js';
17
+ // Status and WebSocket
18
+ import { tmuxSessionStatus, cleanupStatusMaps, getActiveTmuxSessions } from './status.js';
19
+ import { handleTerminalConnection, handleStatusConnection } from './websocket-handlers.js';
20
+ export async function createServer() {
21
+ const app = express();
22
+ app.use(cors());
23
+ app.use(express.json());
24
+ const server = createHttpServer(app);
25
+ const wss = new WebSocketServer({ noServer: true });
26
+ // Initialize editor manager
27
+ const typedConfig = config;
28
+ await initEditor(typedConfig.editor, config.projects.basePath);
29
+ // Initialize SQLite database
30
+ const db = initDatabase();
31
+ migrateEnvironmentsFromJson(db);
32
+ ensureDefaultEnvironment();
33
+ // Reconcile sessions with active tmux sessions
34
+ const activeTmuxSessions = getActiveTmuxSessions();
35
+ sessionsDb.reconcileWithTmux(activeTmuxSessions);
36
+ // Populate in-memory Map from database
37
+ const dbSessions = sessionsDb.getAllSessions();
38
+ for (const session of dbSessions) {
39
+ tmuxSessionStatus.set(session.name, sessionsDb.toHookStatus(session));
40
+ }
41
+ console.log(`[DB] Loaded ${dbSessions.length} sessions from database`);
42
+ // Create proxy for code-server
43
+ const editorProxy = httpProxy.createProxyServer({ ws: true, changeOrigin: true });
44
+ editorProxy.on('error', (err, _req, res) => {
45
+ console.error('[Editor Proxy] HTTP Error:', err.message);
46
+ if (res && 'writeHead' in res) {
47
+ res.writeHead(502, { 'Content-Type': 'application/json' });
48
+ res.end(JSON.stringify({ error: 'Editor proxy error', message: err.message }));
49
+ }
50
+ });
51
+ // Mount API routes
52
+ app.use('/api', createProjectRoutes());
53
+ app.use('/api/environments', createEnvironmentRoutes());
54
+ app.use('/api/sessions', createSessionRoutes());
55
+ app.use('/api/hooks', createHooksRoutes());
56
+ app.use('/api/editor', createEditorRoutes());
57
+ app.use('/api/files', createFilesRoutes());
58
+ // Editor proxy middleware
59
+ app.use('/editor/:project', async (req, res) => {
60
+ const { project } = req.params;
61
+ if (!isProjectAllowed(project)) {
62
+ return res.status(403).json({ error: 'Project not allowed' });
63
+ }
64
+ if (!typedConfig.editor?.enabled) {
65
+ return res.status(400).json({ error: 'Editor is disabled in config' });
66
+ }
67
+ try {
68
+ const editor = await getOrStartEditor(project);
69
+ updateEditorActivity(project);
70
+ req.url = req.url.replace(`/editor/${project}`, '') || '/';
71
+ editorProxy.web(req, res, { target: `http://127.0.0.1:${editor.port}` });
72
+ }
73
+ catch (err) {
74
+ console.error('[Editor Proxy] Failed:', err);
75
+ res.status(502).json({ error: 'Failed to proxy to editor' });
76
+ }
77
+ });
78
+ // Handle WebSocket upgrades
79
+ server.on('upgrade', async (req, socket, head) => {
80
+ const url = new URL(req.url, `http://${req.headers.host}`);
81
+ if (url.pathname === '/terminal') {
82
+ wss.handleUpgrade(req, socket, head, (ws) => {
83
+ handleTerminalConnection(ws, url);
84
+ });
85
+ return;
86
+ }
87
+ if (url.pathname === '/status') {
88
+ wss.handleUpgrade(req, socket, head, (ws) => {
89
+ handleStatusConnection(ws);
90
+ });
91
+ return;
92
+ }
93
+ if (url.pathname.startsWith('/editor/')) {
94
+ const pathParts = url.pathname.split('/');
95
+ const project = pathParts[2];
96
+ if (!project || !isProjectAllowed(project) || !typedConfig.editor?.enabled) {
97
+ socket.destroy();
98
+ return;
99
+ }
100
+ try {
101
+ const editor = await getOrStartEditor(project);
102
+ updateEditorActivity(project);
103
+ const rewrittenPath = url.pathname.replace(`/editor/${project}`, '') || '/';
104
+ req.url = rewrittenPath + url.search;
105
+ editorProxy.ws(req, socket, head, { target: `http://127.0.0.1:${editor.port}` });
106
+ }
107
+ catch (err) {
108
+ console.error('[Editor WS] Failed:', err);
109
+ socket.destroy();
110
+ }
111
+ return;
112
+ }
113
+ socket.destroy();
114
+ });
115
+ // Periodic cleanup
116
+ setInterval(cleanupStatusMaps, 60 * 60 * 1000);
117
+ // Graceful shutdown
118
+ const shutdown = () => {
119
+ console.log('[Server] Shutting down...');
120
+ shutdownAllEditors();
121
+ closeDatabase();
122
+ server.close();
123
+ process.exit(0);
124
+ };
125
+ process.on('SIGTERM', shutdown);
126
+ process.on('SIGINT', shutdown);
127
+ return server;
128
+ }
129
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Session status management and WebSocket broadcast utilities.
3
+ * Handles real-time status updates between Claude Code hooks and the dashboard.
4
+ */
5
+ import { WebSocket } from 'ws';
6
+ import type { SessionStatus, AttentionReason, WSSessionInfo } from '247-shared';
7
+ export interface HookStatus {
8
+ status: SessionStatus;
9
+ attentionReason?: AttentionReason;
10
+ lastEvent: string;
11
+ lastActivity: number;
12
+ lastStatusChange: number;
13
+ project?: string;
14
+ }
15
+ export declare const tmuxSessionStatus: Map<string, HookStatus>;
16
+ export declare const activeConnections: Map<string, Set<WebSocket>>;
17
+ export declare const statusSubscribers: Set<WebSocket>;
18
+ /**
19
+ * Broadcast status update to all subscribers
20
+ */
21
+ export declare function broadcastStatusUpdate(session: WSSessionInfo): void;
22
+ /**
23
+ * Broadcast session removed to all subscribers
24
+ */
25
+ export declare function broadcastSessionRemoved(sessionName: string): void;
26
+ /**
27
+ * Broadcast session archived to all subscribers
28
+ */
29
+ export declare function broadcastSessionArchived(sessionName: string, session: WSSessionInfo): void;
30
+ /**
31
+ * Generate human-readable session names with project prefix
32
+ */
33
+ export declare function generateSessionName(project: string): string;
34
+ /**
35
+ * Clean up stale status entries (called periodically)
36
+ */
37
+ export declare function cleanupStatusMaps(): void;
38
+ /**
39
+ * Get active tmux sessions from the system
40
+ */
41
+ export declare function getActiveTmuxSessions(): Set<string>;
42
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAE/B,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,aAAa,EAEd,MAAM,YAAY,CAAC;AAMpB,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,aAAa,CAAC;IACtB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAGD,eAAO,MAAM,iBAAiB,yBAAgC,CAAC;AAG/D,eAAO,MAAM,iBAAiB,6BAAoC,CAAC;AAGnE,eAAO,MAAM,iBAAiB,gBAAuB,CAAC;AAEtD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAclE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAYjE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI,CAY1F;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkB3D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CA0CxC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,GAAG,CAAC,MAAM,CAAC,CASnD"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Session status management and WebSocket broadcast utilities.
3
+ * Handles real-time status updates between Claude Code hooks and the dashboard.
4
+ */
5
+ import { WebSocket } from 'ws';
6
+ import { execSync } from 'child_process';
7
+ import { RETENTION_CONFIG } from './db/index.js';
8
+ import * as sessionsDb from './db/sessions.js';
9
+ import * as historyDb from './db/history.js';
10
+ // Store by tmux session name - single source of truth for status
11
+ export const tmuxSessionStatus = new Map();
12
+ // Track active WebSocket connections per session
13
+ export const activeConnections = new Map();
14
+ // Track WebSocket subscribers for status updates (real-time push)
15
+ export const statusSubscribers = new Set();
16
+ /**
17
+ * Broadcast status update to all subscribers
18
+ */
19
+ export function broadcastStatusUpdate(session) {
20
+ if (statusSubscribers.size === 0)
21
+ return;
22
+ const message = { type: 'status-update', session };
23
+ const messageStr = JSON.stringify(message);
24
+ for (const ws of statusSubscribers) {
25
+ if (ws.readyState === WebSocket.OPEN) {
26
+ ws.send(messageStr);
27
+ }
28
+ }
29
+ console.log(`[Status WS] Broadcast status update for ${session.name}: ${session.status} to ${statusSubscribers.size} subscribers`);
30
+ }
31
+ /**
32
+ * Broadcast session removed to all subscribers
33
+ */
34
+ export function broadcastSessionRemoved(sessionName) {
35
+ if (statusSubscribers.size === 0)
36
+ return;
37
+ const message = { type: 'session-removed', sessionName };
38
+ const messageStr = JSON.stringify(message);
39
+ for (const ws of statusSubscribers) {
40
+ if (ws.readyState === WebSocket.OPEN) {
41
+ ws.send(messageStr);
42
+ }
43
+ }
44
+ console.log(`[Status WS] Broadcast session removed: ${sessionName}`);
45
+ }
46
+ /**
47
+ * Broadcast session archived to all subscribers
48
+ */
49
+ export function broadcastSessionArchived(sessionName, session) {
50
+ if (statusSubscribers.size === 0)
51
+ return;
52
+ const message = { type: 'session-archived', sessionName, session };
53
+ const messageStr = JSON.stringify(message);
54
+ for (const ws of statusSubscribers) {
55
+ if (ws.readyState === WebSocket.OPEN) {
56
+ ws.send(messageStr);
57
+ }
58
+ }
59
+ console.log(`[Status WS] Broadcast session archived: ${sessionName}`);
60
+ }
61
+ /**
62
+ * Generate human-readable session names with project prefix
63
+ */
64
+ export function generateSessionName(project) {
65
+ const adjectives = [
66
+ 'brave',
67
+ 'swift',
68
+ 'calm',
69
+ 'bold',
70
+ 'wise',
71
+ 'keen',
72
+ 'fair',
73
+ 'wild',
74
+ 'bright',
75
+ 'cool',
76
+ ];
77
+ const nouns = ['lion', 'hawk', 'wolf', 'bear', 'fox', 'owl', 'deer', 'lynx', 'eagle', 'tiger'];
78
+ const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
79
+ const noun = nouns[Math.floor(Math.random() * nouns.length)];
80
+ const num = Math.floor(Math.random() * 100);
81
+ return `${project}--${adj}-${noun}-${num}`;
82
+ }
83
+ /**
84
+ * Clean up stale status entries (called periodically)
85
+ */
86
+ export function cleanupStatusMaps() {
87
+ const now = Date.now();
88
+ const STALE_THRESHOLD = RETENTION_CONFIG.sessionMaxAge;
89
+ let cleanedTmux = 0;
90
+ // Get active tmux sessions
91
+ let activeSessions = new Set();
92
+ try {
93
+ const output = execSync('tmux list-sessions -F "#{session_name}" 2>/dev/null', {
94
+ encoding: 'utf-8',
95
+ });
96
+ activeSessions = new Set(output.trim().split('\n').filter(Boolean));
97
+ }
98
+ catch {
99
+ // No tmux sessions exist
100
+ }
101
+ // Clean tmuxSessionStatus - remove if session doesn't exist OR is stale
102
+ for (const [sessionName, status] of tmuxSessionStatus) {
103
+ const sessionExists = activeSessions.has(sessionName);
104
+ const isStale = now - status.lastActivity > STALE_THRESHOLD;
105
+ if (!sessionExists || isStale) {
106
+ tmuxSessionStatus.delete(sessionName);
107
+ cleanedTmux++;
108
+ }
109
+ }
110
+ if (cleanedTmux > 0) {
111
+ console.log(`[Status Cleanup] Removed ${cleanedTmux} stale status entries from memory`);
112
+ }
113
+ // Also cleanup SQLite database
114
+ const dbSessionsCleaned = sessionsDb.cleanupStaleSessions(STALE_THRESHOLD, RETENTION_CONFIG.archivedMaxAge);
115
+ const dbHistoryCleaned = historyDb.cleanupOldHistory(RETENTION_CONFIG.historyMaxAge);
116
+ if (dbSessionsCleaned > 0 || dbHistoryCleaned > 0) {
117
+ console.log(`[DB Cleanup] Sessions: ${dbSessionsCleaned}, History: ${dbHistoryCleaned}`);
118
+ }
119
+ }
120
+ /**
121
+ * Get active tmux sessions from the system
122
+ */
123
+ export function getActiveTmuxSessions() {
124
+ try {
125
+ const output = execSync('tmux list-sessions -F "#{session_name}" 2>/dev/null', {
126
+ encoding: 'utf-8',
127
+ });
128
+ return new Set(output.trim().split('\n').filter(Boolean));
129
+ }
130
+ catch {
131
+ return new Set();
132
+ }
133
+ }
134
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export interface Terminal {
2
+ write(data: string): void;
3
+ resize(cols: number, rows: number): void;
4
+ onData(callback: (data: string) => void): void;
5
+ onExit(callback: (info: {
6
+ exitCode: number;
7
+ }) => void): void;
8
+ kill(): void;
9
+ detach(): void;
10
+ captureHistory(lines?: number): Promise<string>;
11
+ isExistingSession(): boolean;
12
+ onReady(callback: () => void): void;
13
+ }
14
+ export declare function createTerminal(cwd: string, sessionName: string, customEnvVars?: Record<string, string>): Terminal;
15
+ //# sourceMappingURL=terminal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC/C,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAC7D,IAAI,IAAI,IAAI,CAAC;IACb,MAAM,IAAI,IAAI,CAAC;IACf,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,iBAAiB,IAAI,OAAO,CAAC;IAC7B,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CACrC;AAED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,aAAa,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACzC,QAAQ,CAsJV"}
@@ -0,0 +1,135 @@
1
+ import * as pty from '@homebridge/node-pty-prebuilt-multiarch';
2
+ import { exec, execSync } from 'child_process';
3
+ import { promisify } from 'util';
4
+ const execAsync = promisify(exec);
5
+ export function createTerminal(cwd, sessionName, customEnvVars = {}) {
6
+ // Check if session already exists before spawning
7
+ let existingSession = false;
8
+ try {
9
+ execSync(`tmux has-session -t "${sessionName}" 2>/dev/null`);
10
+ existingSession = true;
11
+ console.log(`[Terminal] Session '${sessionName}' exists, will attach`);
12
+ }
13
+ catch {
14
+ existingSession = false;
15
+ console.log(`[Terminal] Session '${sessionName}' does not exist, will create`);
16
+ }
17
+ if (Object.keys(customEnvVars).length > 0) {
18
+ console.log(`[Terminal] Custom env vars for injection: ${Object.keys(customEnvVars).join(', ')}`);
19
+ }
20
+ // Use tmux for session persistence
21
+ // For existing sessions: use attach-session (more reliable)
22
+ // For new sessions: use new-session with -A flag
23
+ // Note: We DON'T use -e flags here because they pollute tmux's global environment
24
+ const tmuxArgs = existingSession
25
+ ? ['attach-session', '-t', sessionName]
26
+ : ['new-session', '-A', '-s', sessionName, '-c', cwd];
27
+ console.log(`[Terminal] Spawning: tmux ${tmuxArgs.join(' ')}`);
28
+ const shell = pty.spawn('tmux', tmuxArgs, {
29
+ name: 'xterm-256color',
30
+ cols: 120,
31
+ rows: 30,
32
+ cwd,
33
+ env: {
34
+ ...process.env,
35
+ // Note: customEnvVars are NOT included here to avoid polluting the pty environment
36
+ // They will be injected per-session using tmux send-keys
37
+ TERM: 'xterm-256color',
38
+ CLAUDE_TMUX_SESSION: sessionName, // Always set for hook detection
39
+ PATH: `/opt/homebrew/bin:${process.env.PATH}`,
40
+ },
41
+ });
42
+ // Debug: log any immediate output or errors
43
+ let initialOutput = '';
44
+ const debugHandler = (data) => {
45
+ initialOutput += data;
46
+ if (initialOutput.length < 500) {
47
+ console.log(`[Terminal] Initial output: ${data.substring(0, 100)}`);
48
+ }
49
+ };
50
+ shell.onData(debugHandler);
51
+ // Remove debug handler after 2 seconds to prevent memory leak
52
+ setTimeout(() => {
53
+ shell.removeListener('data', debugHandler);
54
+ }, 2000);
55
+ // Debug: log when shell exits
56
+ shell.onExit(({ exitCode, signal }) => {
57
+ console.log(`[Terminal] Shell exited: code=${exitCode}, signal=${signal}, session='${sessionName}'`);
58
+ });
59
+ // Track terminal readiness state for onReady callback
60
+ let isReady = existingSession; // Existing sessions are ready immediately
61
+ const readyCallbacks = [];
62
+ const fireReadyCallbacks = () => {
63
+ isReady = true;
64
+ readyCallbacks.forEach((cb) => cb());
65
+ readyCallbacks.length = 0; // Clear the array
66
+ };
67
+ // Configure tmux options and inject environment variables
68
+ if (!existingSession) {
69
+ setTimeout(() => {
70
+ exec(`tmux set-option -t "${sessionName}" history-limit 10000`);
71
+ exec(`tmux set-option -t "${sessionName}" mouse on`);
72
+ // ALWAYS inject CLAUDE_TMUX_SESSION into the shell for hook detection
73
+ // This is critical for hooks to identify which session they belong to
74
+ const baseExport = `export CLAUDE_TMUX_SESSION="${sessionName}"`;
75
+ // Add custom environment variables if present (filter out empty values)
76
+ const nonEmptyVars = Object.entries(customEnvVars).filter(([, value]) => value && value.trim() !== '');
77
+ const allExports = nonEmptyVars.length > 0
78
+ ? `${baseExport}; ${nonEmptyVars
79
+ .map(([key, value]) => `export ${key}="${value.replace(/"/g, '\\"')}"`)
80
+ .join('; ')}`
81
+ : baseExport;
82
+ console.log(`[Terminal] Injecting CLAUDE_TMUX_SESSION and ${nonEmptyVars.length} custom vars into NEW session '${sessionName}'`);
83
+ // Séquences ANSI pour effacer les lignes après exécution
84
+ // \033[1A = remonter d'une ligne, \033[2K = effacer la ligne
85
+ // On efface 2 lignes: la commande tapée + le prompt précédent
86
+ const clearSequence = `printf '\\033[1A\\033[2K\\033[1A\\033[2K'`;
87
+ exec(`tmux send-keys -t "${sessionName}" "${allExports}; ${clearSequence}" C-m`, () => {
88
+ // Fire ready callbacks after init commands are sent
89
+ fireReadyCallbacks();
90
+ });
91
+ }, 100);
92
+ }
93
+ else {
94
+ // For existing sessions, just ensure mouse is enabled
95
+ // Environment variables were already injected when the session was created
96
+ // isReady is already true for existing sessions (set above)
97
+ setTimeout(() => {
98
+ exec(`tmux set-option -t "${sessionName}" mouse on`);
99
+ }, 100);
100
+ }
101
+ return {
102
+ write: (data) => shell.write(data),
103
+ resize: (cols, rows) => shell.resize(cols, rows),
104
+ onData: (callback) => shell.onData(callback),
105
+ onExit: (callback) => shell.onExit(callback),
106
+ kill: () => shell.kill(),
107
+ detach: () => {
108
+ // Send tmux detach command (Ctrl+B, d)
109
+ shell.write('\x02d');
110
+ },
111
+ isExistingSession: () => existingSession,
112
+ onReady: (callback) => {
113
+ if (isReady) {
114
+ callback();
115
+ }
116
+ else {
117
+ readyCallbacks.push(callback);
118
+ }
119
+ },
120
+ captureHistory: async (lines = 10000) => {
121
+ try {
122
+ // Capture scrollback buffer from tmux
123
+ // -p = print to stdout
124
+ // -S -N = start from N lines back (negative = from start of history)
125
+ // -J = preserve trailing spaces for proper formatting
126
+ const { stdout } = await execAsync(`tmux capture-pane -t "${sessionName}" -p -S -${lines} -J 2>/dev/null`);
127
+ return stdout;
128
+ }
129
+ catch {
130
+ return '';
131
+ }
132
+ },
133
+ };
134
+ }
135
+ //# sourceMappingURL=terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../src/terminal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * WebSocket handlers for terminal connections and status subscriptions.
3
+ */
4
+ import { WebSocket } from 'ws';
5
+ /**
6
+ * Handle terminal WebSocket connections
7
+ */
8
+ export declare function handleTerminalConnection(ws: WebSocket, url: URL): void;
9
+ /**
10
+ * Handle status WebSocket connections (real-time session updates)
11
+ */
12
+ export declare function handleStatusConnection(ws: WebSocket): void;
13
+ //# sourceMappingURL=websocket-handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-handlers.d.ts","sourceRoot":"","sources":["../src/websocket-handlers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AA0B/B;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CA2JtE;AAsDD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI,CAwE1D"}