@cluesmith/codev 2.0.0-rc.72 → 2.0.0-rc.73

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 (50) hide show
  1. package/dashboard/dist/assets/{index-C7FtNK6Y.css → index-4n9zpWLY.css} +1 -1
  2. package/dashboard/dist/assets/{index-CDAINZKT.js → index-CH_utkcW.js} +32 -27
  3. package/dashboard/dist/assets/index-CH_utkcW.js.map +1 -0
  4. package/dashboard/dist/index.html +2 -2
  5. package/dist/agent-farm/commands/spawn-roles.d.ts +80 -0
  6. package/dist/agent-farm/commands/spawn-roles.d.ts.map +1 -0
  7. package/dist/agent-farm/commands/spawn-roles.js +278 -0
  8. package/dist/agent-farm/commands/spawn-roles.js.map +1 -0
  9. package/dist/agent-farm/commands/spawn-worktree.d.ts +96 -0
  10. package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -0
  11. package/dist/agent-farm/commands/spawn-worktree.js +305 -0
  12. package/dist/agent-farm/commands/spawn-worktree.js.map +1 -0
  13. package/dist/agent-farm/commands/spawn.d.ts +5 -1
  14. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  15. package/dist/agent-farm/commands/spawn.js +65 -725
  16. package/dist/agent-farm/commands/spawn.js.map +1 -1
  17. package/dist/agent-farm/servers/tower-instances.d.ts +82 -0
  18. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -0
  19. package/dist/agent-farm/servers/tower-instances.js +441 -0
  20. package/dist/agent-farm/servers/tower-instances.js.map +1 -0
  21. package/dist/agent-farm/servers/tower-routes.d.ts +34 -0
  22. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -0
  23. package/dist/agent-farm/servers/tower-routes.js +1445 -0
  24. package/dist/agent-farm/servers/tower-routes.js.map +1 -0
  25. package/dist/agent-farm/servers/tower-server.d.ts +5 -2
  26. package/dist/agent-farm/servers/tower-server.d.ts.map +1 -1
  27. package/dist/agent-farm/servers/tower-server.js +74 -2860
  28. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  29. package/dist/agent-farm/servers/tower-terminals.d.ts +119 -0
  30. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -0
  31. package/dist/agent-farm/servers/tower-terminals.js +629 -0
  32. package/dist/agent-farm/servers/tower-terminals.js.map +1 -0
  33. package/dist/agent-farm/servers/tower-tunnel.d.ts +34 -0
  34. package/dist/agent-farm/servers/tower-tunnel.d.ts.map +1 -0
  35. package/dist/agent-farm/servers/tower-tunnel.js +299 -0
  36. package/dist/agent-farm/servers/tower-tunnel.js.map +1 -0
  37. package/dist/agent-farm/servers/tower-types.d.ts +85 -0
  38. package/dist/agent-farm/servers/tower-types.d.ts.map +1 -0
  39. package/dist/agent-farm/servers/tower-types.js +6 -0
  40. package/dist/agent-farm/servers/tower-types.js.map +1 -0
  41. package/dist/agent-farm/servers/tower-utils.d.ts +51 -0
  42. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -0
  43. package/dist/agent-farm/servers/tower-utils.js +161 -0
  44. package/dist/agent-farm/servers/tower-utils.js.map +1 -0
  45. package/dist/agent-farm/servers/tower-websocket.d.ts +25 -0
  46. package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -0
  47. package/dist/agent-farm/servers/tower-websocket.js +171 -0
  48. package/dist/agent-farm/servers/tower-websocket.js.map +1 -0
  49. package/package.json +1 -1
  50. package/dashboard/dist/assets/index-CDAINZKT.js.map +0 -1
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Cloud tunnel management for tower server.
3
+ * Spec 0105: Tower Server Decomposition — Phase 2
4
+ *
5
+ * Contains: tunnel client lifecycle, config file watching,
6
+ * metadata refresh, and tunnel API endpoint handling.
7
+ */
8
+ import http from 'node:http';
9
+ import type { ProjectTerminals, InstanceStatus } from './tower-types.js';
10
+ import type { TerminalManager } from '../../terminal/pty-manager.js';
11
+ /** Minimal dependencies required by the tunnel module */
12
+ export interface TunnelDeps {
13
+ port: number;
14
+ log: (level: 'INFO' | 'ERROR' | 'WARN', message: string) => void;
15
+ projectTerminals: Map<string, ProjectTerminals>;
16
+ terminalManager: TerminalManager | null;
17
+ }
18
+ /**
19
+ * Initialize the tunnel module. Reads cloud config and connects if registered.
20
+ * Starts config file watcher for credential changes.
21
+ */
22
+ export declare function initTunnel(deps: TunnelDeps, callbacks: {
23
+ getInstances: () => Promise<InstanceStatus[]>;
24
+ }): Promise<void>;
25
+ /**
26
+ * Shut down the tunnel module. Disconnects client, stops watchers.
27
+ */
28
+ export declare function shutdownTunnel(): void;
29
+ /**
30
+ * Handle tunnel management endpoints (Spec 0097 Phase 4).
31
+ * Dispatches /api/tunnel/{connect,disconnect,status} requests.
32
+ */
33
+ export declare function handleTunnelEndpoint(req: http.IncomingMessage, res: http.ServerResponse, tunnelSub: string): Promise<void>;
34
+ //# sourceMappingURL=tower-tunnel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tower-tunnel.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-tunnel.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAErE,yDAAyD;AACzD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAChD,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;CACzC;AA+LD;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE;IAAE,YAAY,EAAE,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC,CAAA;CAAE,GAC3D,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAUrC;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAwEf"}
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Cloud tunnel management for tower server.
3
+ * Spec 0105: Tower Server Decomposition — Phase 2
4
+ *
5
+ * Contains: tunnel client lifecycle, config file watching,
6
+ * metadata refresh, and tunnel API endpoint handling.
7
+ */
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { TunnelClient } from '../lib/tunnel-client.js';
11
+ import { readCloudConfig, getCloudConfigPath, maskApiKey } from '../lib/cloud-config.js';
12
+ // ============================================================================
13
+ // Module-private state (lifecycle driven by orchestrator)
14
+ // ============================================================================
15
+ let tunnelClient = null;
16
+ let configWatcher = null;
17
+ let configWatchDebounce = null;
18
+ let metadataRefreshInterval = null;
19
+ const METADATA_REFRESH_MS = 30_000;
20
+ /** Stored references set by initTunnel() */
21
+ let _deps = null;
22
+ let _getInstances = null;
23
+ // ============================================================================
24
+ // Internal functions
25
+ // ============================================================================
26
+ /**
27
+ * Gather current tower metadata (projects + terminals) for codevos.ai.
28
+ */
29
+ async function gatherMetadata() {
30
+ if (!_deps || !_getInstances)
31
+ throw new Error('Tunnel not initialized');
32
+ const instances = await _getInstances();
33
+ const projects = instances.map((i) => ({
34
+ path: i.projectPath,
35
+ name: i.projectName,
36
+ }));
37
+ // Build reverse mapping: terminal ID → project path
38
+ const terminalToProject = new Map();
39
+ for (const [projectPath, entry] of _deps.projectTerminals) {
40
+ if (entry.architect)
41
+ terminalToProject.set(entry.architect, projectPath);
42
+ for (const termId of entry.builders.values())
43
+ terminalToProject.set(termId, projectPath);
44
+ for (const termId of entry.shells.values())
45
+ terminalToProject.set(termId, projectPath);
46
+ }
47
+ const manager = _deps.terminalManager;
48
+ const terminals = [];
49
+ if (manager) {
50
+ for (const session of manager.listSessions()) {
51
+ terminals.push({
52
+ id: session.id,
53
+ projectPath: terminalToProject.get(session.id) ?? '',
54
+ });
55
+ }
56
+ }
57
+ return { projects, terminals };
58
+ }
59
+ /**
60
+ * Start periodic metadata refresh — re-gathers metadata and pushes to codevos.ai
61
+ * every METADATA_REFRESH_MS while the tunnel is connected.
62
+ */
63
+ function startMetadataRefresh() {
64
+ stopMetadataRefresh();
65
+ metadataRefreshInterval = setInterval(async () => {
66
+ try {
67
+ if (tunnelClient && tunnelClient.getState() === 'connected') {
68
+ const metadata = await gatherMetadata();
69
+ tunnelClient.sendMetadata(metadata);
70
+ }
71
+ }
72
+ catch (err) {
73
+ _deps?.log('WARN', `Metadata refresh failed: ${err.message}`);
74
+ }
75
+ }, METADATA_REFRESH_MS);
76
+ }
77
+ /**
78
+ * Stop the periodic metadata refresh.
79
+ */
80
+ function stopMetadataRefresh() {
81
+ if (metadataRefreshInterval) {
82
+ clearInterval(metadataRefreshInterval);
83
+ metadataRefreshInterval = null;
84
+ }
85
+ }
86
+ /**
87
+ * Create or reconnect the tunnel client using the given config.
88
+ * Sets up state change listeners and sends initial metadata.
89
+ */
90
+ async function connectTunnel(config) {
91
+ if (!_deps)
92
+ throw new Error('Tunnel not initialized');
93
+ // Disconnect existing client if any
94
+ if (tunnelClient) {
95
+ tunnelClient.disconnect();
96
+ }
97
+ const client = new TunnelClient({
98
+ serverUrl: config.server_url,
99
+ apiKey: config.api_key,
100
+ towerId: config.tower_id,
101
+ localPort: _deps.port,
102
+ });
103
+ client.onStateChange((state, prev) => {
104
+ _deps.log('INFO', `Tunnel: ${prev} → ${state}`);
105
+ if (state === 'connected') {
106
+ startMetadataRefresh();
107
+ }
108
+ else if (prev === 'connected') {
109
+ stopMetadataRefresh();
110
+ }
111
+ if (state === 'auth_failed') {
112
+ _deps.log('ERROR', 'Cloud connection failed: API key is invalid or revoked. Run \'af tower register --reauth\' to update credentials.');
113
+ }
114
+ });
115
+ // Gather and set initial metadata before connecting
116
+ const metadata = await gatherMetadata();
117
+ client.sendMetadata(metadata);
118
+ tunnelClient = client;
119
+ client.connect();
120
+ // Ensure config watcher is running — the config directory now exists.
121
+ startConfigWatcher();
122
+ return client;
123
+ }
124
+ /**
125
+ * Start watching cloud-config.json for changes.
126
+ * On change: reconnect with new credentials.
127
+ * On delete: disconnect tunnel.
128
+ */
129
+ function startConfigWatcher() {
130
+ stopConfigWatcher();
131
+ const configPath = getCloudConfigPath();
132
+ const configDir = path.dirname(configPath);
133
+ const configFile = path.basename(configPath);
134
+ // Watch the directory (more reliable than watching the file directly)
135
+ try {
136
+ configWatcher = fs.watch(configDir, (eventType, filename) => {
137
+ if (filename !== configFile)
138
+ return;
139
+ // Debounce: multiple events fire for a single write
140
+ if (configWatchDebounce)
141
+ clearTimeout(configWatchDebounce);
142
+ configWatchDebounce = setTimeout(async () => {
143
+ configWatchDebounce = null;
144
+ try {
145
+ const config = readCloudConfig();
146
+ if (config) {
147
+ _deps?.log('INFO', `Cloud config changed, reconnecting tunnel (key: ${maskApiKey(config.api_key)})`);
148
+ // Reset circuit breaker in case previous key was invalid
149
+ if (tunnelClient)
150
+ tunnelClient.resetCircuitBreaker();
151
+ await connectTunnel(config);
152
+ }
153
+ else {
154
+ // Config deleted or invalid
155
+ _deps?.log('INFO', 'Cloud config removed or invalid, disconnecting tunnel');
156
+ if (tunnelClient) {
157
+ tunnelClient.disconnect();
158
+ tunnelClient = null;
159
+ }
160
+ }
161
+ }
162
+ catch (err) {
163
+ _deps?.log('WARN', `Error handling config change: ${err.message}`);
164
+ }
165
+ }, 500);
166
+ });
167
+ }
168
+ catch {
169
+ // Directory doesn't exist yet — that's fine, user hasn't registered
170
+ }
171
+ }
172
+ /**
173
+ * Stop watching cloud-config.json.
174
+ */
175
+ function stopConfigWatcher() {
176
+ if (configWatcher) {
177
+ configWatcher.close();
178
+ configWatcher = null;
179
+ }
180
+ if (configWatchDebounce) {
181
+ clearTimeout(configWatchDebounce);
182
+ configWatchDebounce = null;
183
+ }
184
+ }
185
+ // ============================================================================
186
+ // Public API (called by orchestrator)
187
+ // ============================================================================
188
+ /**
189
+ * Initialize the tunnel module. Reads cloud config and connects if registered.
190
+ * Starts config file watcher for credential changes.
191
+ */
192
+ export async function initTunnel(deps, callbacks) {
193
+ _deps = deps;
194
+ _getInstances = callbacks.getInstances;
195
+ // Auto-connect tunnel if registered
196
+ try {
197
+ const config = readCloudConfig();
198
+ if (config) {
199
+ deps.log('INFO', `Cloud config found, connecting tunnel (tower: ${config.tower_name}, key: ${maskApiKey(config.api_key)})`);
200
+ await connectTunnel(config);
201
+ }
202
+ else {
203
+ deps.log('INFO', 'No cloud config found, operating in local-only mode');
204
+ }
205
+ }
206
+ catch (err) {
207
+ deps.log('WARN', `Failed to read cloud config: ${err.message}. Operating in local-only mode.`);
208
+ }
209
+ // Start watching cloud-config.json for changes
210
+ startConfigWatcher();
211
+ }
212
+ /**
213
+ * Shut down the tunnel module. Disconnects client, stops watchers.
214
+ */
215
+ export function shutdownTunnel() {
216
+ stopMetadataRefresh();
217
+ stopConfigWatcher();
218
+ if (tunnelClient) {
219
+ _deps?.log('INFO', 'Disconnecting tunnel...');
220
+ tunnelClient.disconnect();
221
+ tunnelClient = null;
222
+ }
223
+ _deps = null;
224
+ _getInstances = null;
225
+ }
226
+ /**
227
+ * Handle tunnel management endpoints (Spec 0097 Phase 4).
228
+ * Dispatches /api/tunnel/{connect,disconnect,status} requests.
229
+ */
230
+ export async function handleTunnelEndpoint(req, res, tunnelSub) {
231
+ // POST connect
232
+ if (req.method === 'POST' && tunnelSub === 'connect') {
233
+ if (!_deps) {
234
+ // Module not initialized yet (server still starting up)
235
+ res.writeHead(503, { 'Content-Type': 'application/json' });
236
+ res.end(JSON.stringify({ success: false, error: 'Tower is still starting up. Try again shortly.' }));
237
+ return;
238
+ }
239
+ try {
240
+ const config = readCloudConfig();
241
+ if (!config) {
242
+ res.writeHead(400, { 'Content-Type': 'application/json' });
243
+ res.end(JSON.stringify({ success: false, error: 'Not registered. Run \'af tower register\' first.' }));
244
+ return;
245
+ }
246
+ if (tunnelClient)
247
+ tunnelClient.resetCircuitBreaker();
248
+ const client = await connectTunnel(config);
249
+ res.writeHead(200, { 'Content-Type': 'application/json' });
250
+ res.end(JSON.stringify({ success: true, state: client.getState() }));
251
+ }
252
+ catch (err) {
253
+ _deps?.log('ERROR', `Tunnel connect failed: ${err.message}`);
254
+ res.writeHead(500, { 'Content-Type': 'application/json' });
255
+ res.end(JSON.stringify({ success: false, error: err.message }));
256
+ }
257
+ return;
258
+ }
259
+ // POST disconnect
260
+ if (req.method === 'POST' && tunnelSub === 'disconnect') {
261
+ if (tunnelClient) {
262
+ tunnelClient.disconnect();
263
+ tunnelClient = null;
264
+ }
265
+ res.writeHead(200, { 'Content-Type': 'application/json' });
266
+ res.end(JSON.stringify({ success: true }));
267
+ return;
268
+ }
269
+ // GET status
270
+ if (req.method === 'GET' && tunnelSub === 'status') {
271
+ let config = null;
272
+ try {
273
+ config = readCloudConfig();
274
+ }
275
+ catch {
276
+ // Config file may be corrupted — treat as unregistered
277
+ }
278
+ const state = tunnelClient?.getState() ?? 'disconnected';
279
+ const uptime = tunnelClient?.getUptime() ?? null;
280
+ const response = {
281
+ registered: config !== null,
282
+ state,
283
+ uptime,
284
+ };
285
+ if (config) {
286
+ response.towerId = config.tower_id;
287
+ response.towerName = config.tower_name;
288
+ response.serverUrl = config.server_url;
289
+ response.accessUrl = `${config.server_url}/t/${config.tower_name}/`;
290
+ }
291
+ res.writeHead(200, { 'Content-Type': 'application/json' });
292
+ res.end(JSON.stringify(response));
293
+ return;
294
+ }
295
+ // Unknown tunnel endpoint
296
+ res.writeHead(404, { 'Content-Type': 'application/json' });
297
+ res.end(JSON.stringify({ error: 'Not found' }));
298
+ }
299
+ //# sourceMappingURL=tower-tunnel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tower-tunnel.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-tunnel.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAwC,MAAM,yBAAyB,CAAC;AAC7F,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,UAAU,EAAoB,MAAM,wBAAwB,CAAC;AAY3G,+EAA+E;AAC/E,0DAA0D;AAC1D,+EAA+E;AAE/E,IAAI,YAAY,GAAwB,IAAI,CAAC;AAC7C,IAAI,aAAa,GAAwB,IAAI,CAAC;AAC9C,IAAI,mBAAmB,GAAyC,IAAI,CAAC;AACrE,IAAI,uBAAuB,GAA0C,IAAI,CAAC;AAE1E,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAEnC,4CAA4C;AAC5C,IAAI,KAAK,GAAsB,IAAI,CAAC;AACpC,IAAI,aAAa,GAA6C,IAAI,CAAC;AAEnE,+EAA+E;AAC/E,qBAAqB;AACrB,+EAA+E;AAE/E;;GAEG;AACH,KAAK,UAAU,cAAc;IAC3B,IAAI,CAAC,KAAK,IAAI,CAAC,aAAa;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAExE,MAAM,SAAS,GAAG,MAAM,aAAa,EAAE,CAAC;IACxC,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrC,IAAI,EAAE,CAAC,CAAC,WAAW;QACnB,IAAI,EAAE,CAAC,CAAC,WAAW;KACpB,CAAC,CAAC,CAAC;IAEJ,oDAAoD;IACpD,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAkB,CAAC;IACpD,KAAK,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,gBAAgB,EAAE,CAAC;QAC1D,IAAI,KAAK,CAAC,SAAS;YAAE,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QACzE,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE;YAAE,iBAAiB,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QACzF,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE;YAAE,iBAAiB,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,eAAe,CAAC;IACtC,MAAM,SAAS,GAA+B,EAAE,CAAC;IACjD,IAAI,OAAO,EAAE,CAAC;QACZ,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;YAC7C,SAAS,CAAC,IAAI,CAAC;gBACb,EAAE,EAAE,OAAO,CAAC,EAAE;gBACd,WAAW,EAAE,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;aACrD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;AACjC,CAAC;AAED;;;GAGG;AACH,SAAS,oBAAoB;IAC3B,mBAAmB,EAAE,CAAC;IACtB,uBAAuB,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;QAC/C,IAAI,CAAC;YACH,IAAI,YAAY,IAAI,YAAY,CAAC,QAAQ,EAAE,KAAK,WAAW,EAAE,CAAC;gBAC5D,MAAM,QAAQ,GAAG,MAAM,cAAc,EAAE,CAAC;gBACxC,YAAY,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;YACtC,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,4BAA6B,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC,EAAE,mBAAmB,CAAC,CAAC;AAC1B,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB;IAC1B,IAAI,uBAAuB,EAAE,CAAC;QAC5B,aAAa,CAAC,uBAAuB,CAAC,CAAC;QACvC,uBAAuB,GAAG,IAAI,CAAC;IACjC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,aAAa,CAAC,MAAmB;IAC9C,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAEtD,oCAAoC;IACpC,IAAI,YAAY,EAAE,CAAC;QACjB,YAAY,CAAC,UAAU,EAAE,CAAC;IAC5B,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC;QAC9B,SAAS,EAAE,MAAM,CAAC,UAAU;QAC5B,MAAM,EAAE,MAAM,CAAC,OAAO;QACtB,OAAO,EAAE,MAAM,CAAC,QAAQ;QACxB,SAAS,EAAE,KAAK,CAAC,IAAI;KACtB,CAAC,CAAC;IAEH,MAAM,CAAC,aAAa,CAAC,CAAC,KAAkB,EAAE,IAAiB,EAAE,EAAE;QAC7D,KAAM,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,IAAI,MAAM,KAAK,EAAE,CAAC,CAAC;QACjD,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;YAC1B,oBAAoB,EAAE,CAAC;QACzB,CAAC;aAAM,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;YAChC,mBAAmB,EAAE,CAAC;QACxB,CAAC;QACD,IAAI,KAAK,KAAK,aAAa,EAAE,CAAC;YAC5B,KAAM,CAAC,GAAG,CAAC,OAAO,EAAE,mHAAmH,CAAC,CAAC;QAC3I,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,oDAAoD;IACpD,MAAM,QAAQ,GAAG,MAAM,cAAc,EAAE,CAAC;IACxC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAE9B,YAAY,GAAG,MAAM,CAAC;IACtB,MAAM,CAAC,OAAO,EAAE,CAAC;IAEjB,sEAAsE;IACtE,kBAAkB,EAAE,CAAC;IAErB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB;IACzB,iBAAiB,EAAE,CAAC;IAEpB,MAAM,UAAU,GAAG,kBAAkB,EAAE,CAAC;IACxC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAE7C,sEAAsE;IACtE,IAAI,CAAC;QACH,aAAa,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE;YAC1D,IAAI,QAAQ,KAAK,UAAU;gBAAE,OAAO;YAEpC,oDAAoD;YACpD,IAAI,mBAAmB;gBAAE,YAAY,CAAC,mBAAmB,CAAC,CAAC;YAC3D,mBAAmB,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;gBAC1C,mBAAmB,GAAG,IAAI,CAAC;gBAC3B,IAAI,CAAC;oBACH,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;oBACjC,IAAI,MAAM,EAAE,CAAC;wBACX,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,mDAAmD,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;wBACrG,yDAAyD;wBACzD,IAAI,YAAY;4BAAE,YAAY,CAAC,mBAAmB,EAAE,CAAC;wBACrD,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;oBAC9B,CAAC;yBAAM,CAAC;wBACN,4BAA4B;wBAC5B,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,uDAAuD,CAAC,CAAC;wBAC5E,IAAI,YAAY,EAAE,CAAC;4BACjB,YAAY,CAAC,UAAU,EAAE,CAAC;4BAC1B,YAAY,GAAG,IAAI,CAAC;wBACtB,CAAC;oBACH,CAAC;gBACH,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,iCAAkC,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;gBAChF,CAAC;YACH,CAAC,EAAE,GAAG,CAAC,CAAC;QACV,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,oEAAoE;IACtE,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB;IACxB,IAAI,aAAa,EAAE,CAAC;QAClB,aAAa,CAAC,KAAK,EAAE,CAAC;QACtB,aAAa,GAAG,IAAI,CAAC;IACvB,CAAC;IACD,IAAI,mBAAmB,EAAE,CAAC;QACxB,YAAY,CAAC,mBAAmB,CAAC,CAAC;QAClC,mBAAmB,GAAG,IAAI,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,+EAA+E;AAC/E,sCAAsC;AACtC,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAgB,EAChB,SAA4D;IAE5D,KAAK,GAAG,IAAI,CAAC;IACb,aAAa,GAAG,SAAS,CAAC,YAAY,CAAC;IAEvC,oCAAoC;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;QACjC,IAAI,MAAM,EAAE,CAAC;YACX,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,iDAAiD,MAAM,CAAC,UAAU,UAAU,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC5H,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,qDAAqD,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,gCAAiC,GAAa,CAAC,OAAO,iCAAiC,CAAC,CAAC;IAC5G,CAAC;IAED,+CAA+C;IAC/C,kBAAkB,EAAE,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,mBAAmB,EAAE,CAAC;IACtB,iBAAiB,EAAE,CAAC;IACpB,IAAI,YAAY,EAAE,CAAC;QACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;QAC9C,YAAY,CAAC,UAAU,EAAE,CAAC;QAC1B,YAAY,GAAG,IAAI,CAAC;IACtB,CAAC;IACD,KAAK,GAAG,IAAI,CAAC;IACb,aAAa,GAAG,IAAI,CAAC;AACvB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,GAAyB,EACzB,GAAwB,EACxB,SAAiB;IAEjB,eAAe;IACf,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QACrD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,wDAAwD;YACxD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,gDAAgD,EAAE,CAAC,CAAC,CAAC;YACrG,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;YACjC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,kDAAkD,EAAE,CAAC,CAAC,CAAC;gBACvG,OAAO;YACT,CAAC;YACD,IAAI,YAAY;gBAAE,YAAY,CAAC,mBAAmB,EAAE,CAAC;YACrD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;YAC3C,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;QACvE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,0BAA2B,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YACxE,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;YAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC7E,CAAC;QACD,OAAO;IACT,CAAC;IAED,kBAAkB;IAClB,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,SAAS,KAAK,YAAY,EAAE,CAAC;QACxD,IAAI,YAAY,EAAE,CAAC;YACjB,YAAY,CAAC,UAAU,EAAE,CAAC;YAC1B,YAAY,GAAG,IAAI,CAAC;QACtB,CAAC;QACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC3C,OAAO;IACT,CAAC;IAED,aAAa;IACb,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QACnD,IAAI,MAAM,GAAuB,IAAI,CAAC;QACtC,IAAI,CAAC;YACH,MAAM,GAAG,eAAe,EAAE,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,uDAAuD;QACzD,CAAC;QAED,MAAM,KAAK,GAAG,YAAY,EAAE,QAAQ,EAAE,IAAI,cAAc,CAAC;QACzD,MAAM,MAAM,GAAG,YAAY,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC;QAEjD,MAAM,QAAQ,GAA4B;YACxC,UAAU,EAAE,MAAM,KAAK,IAAI;YAC3B,KAAK;YACL,MAAM;SACP,CAAC;QAEF,IAAI,MAAM,EAAE,CAAC;YACX,QAAQ,CAAC,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC;YACnC,QAAQ,CAAC,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC;YACvC,QAAQ,CAAC,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC;YACvC,QAAQ,CAAC,SAAS,GAAG,GAAG,MAAM,CAAC,UAAU,MAAM,MAAM,CAAC,UAAU,GAAG,CAAC;QACtE,CAAC;QAED,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClC,OAAO;IACT,CAAC;IAED,0BAA0B;IAC1B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Shared types for tower server modules.
3
+ * Spec 0105: Tower Server Decomposition
4
+ */
5
+ import type http from 'node:http';
6
+ import type { WebSocketServer } from 'ws';
7
+ import type Database from 'better-sqlite3';
8
+ import type { TerminalManager } from '../../terminal/pty-manager.js';
9
+ import type { SessionManager } from '../../terminal/session-manager.js';
10
+ import type { GateWatcher } from '../utils/gate-watcher.js';
11
+ import type { TunnelClient } from '../lib/tunnel-client.js';
12
+ import type { FileTab } from '../utils/file-tabs.js';
13
+ import type { GateStatus } from '../utils/gate-status.js';
14
+ /**
15
+ * Shared context passed to all tower modules.
16
+ * The orchestrator (tower-server.ts) owns lifecycle — it creates
17
+ * dependencies in startup order and tears them down in gracefulShutdown.
18
+ */
19
+ export interface TowerContext {
20
+ port: number;
21
+ log: (level: 'INFO' | 'ERROR' | 'WARN', message: string) => void;
22
+ terminalManager: TerminalManager;
23
+ shepherdManager: SessionManager | null;
24
+ projectTerminals: Map<string, ProjectTerminals>;
25
+ db: () => Database.Database;
26
+ gateWatcher: GateWatcher;
27
+ broadcastNotification: (n: {
28
+ type: string;
29
+ title: string;
30
+ body: string;
31
+ project?: string;
32
+ }) => void;
33
+ tunnelClient: TunnelClient | null;
34
+ knownProjects: Set<string>;
35
+ server: http.Server;
36
+ terminalWss: WebSocketServer;
37
+ }
38
+ /** Tracks terminals belonging to a project */
39
+ export interface ProjectTerminals {
40
+ architect?: string;
41
+ builders: Map<string, string>;
42
+ shells: Map<string, string>;
43
+ fileTabs: Map<string, FileTab>;
44
+ }
45
+ /** SSE client connection for push notifications */
46
+ export interface SSEClient {
47
+ res: http.ServerResponse;
48
+ id: string;
49
+ }
50
+ /** Rate limiting entry for activation requests */
51
+ export interface RateLimitEntry {
52
+ count: number;
53
+ windowStart: number;
54
+ }
55
+ /** Terminal entry returned to tower UI */
56
+ export interface TerminalEntry {
57
+ type: 'architect' | 'builder' | 'shell' | 'file';
58
+ id: string;
59
+ label: string;
60
+ url: string;
61
+ active: boolean;
62
+ }
63
+ /** Instance status returned to tower UI */
64
+ export interface InstanceStatus {
65
+ projectPath: string;
66
+ projectName: string;
67
+ running: boolean;
68
+ proxyUrl: string;
69
+ architectUrl: string;
70
+ terminals: TerminalEntry[];
71
+ gateStatus?: GateStatus;
72
+ }
73
+ /** SQLite terminal session row shape */
74
+ export interface DbTerminalSession {
75
+ id: string;
76
+ project_path: string;
77
+ type: 'architect' | 'builder' | 'shell';
78
+ role_id: string | null;
79
+ pid: number | null;
80
+ shepherd_socket: string | null;
81
+ shepherd_pid: number | null;
82
+ shepherd_start_time: number | null;
83
+ created_at: string;
84
+ }
85
+ //# sourceMappingURL=tower-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tower-types.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared types for tower server modules.
3
+ * Spec 0105: Tower Server Decomposition
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=tower-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tower-types.js","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Utility functions for tower server.
3
+ * Spec 0105: Tower Server Decomposition — Phase 1
4
+ *
5
+ * Contains: rate limiting, path normalization, temp directory detection,
6
+ * project name extraction, MIME types, and static file serving.
7
+ */
8
+ import type { ServerResponse } from 'node:http';
9
+ /**
10
+ * Check if a client has exceeded the rate limit for activations.
11
+ * Returns true if rate limit exceeded, false if allowed.
12
+ */
13
+ export declare function isRateLimited(clientIp: string): boolean;
14
+ /**
15
+ * Clean up old rate limit entries.
16
+ */
17
+ export declare function cleanupRateLimits(): void;
18
+ /**
19
+ * Start periodic cleanup of stale rate limit entries.
20
+ * Returns the interval handle so the orchestrator can clear it on shutdown.
21
+ */
22
+ export declare function startRateLimitCleanup(): ReturnType<typeof setInterval>;
23
+ /**
24
+ * Normalize a project path to its canonical form for consistent SQLite storage.
25
+ * Uses realpath to resolve symlinks and relative paths.
26
+ */
27
+ export declare function normalizeProjectPath(projectPath: string): string;
28
+ /**
29
+ * Get project name from path.
30
+ */
31
+ export declare function getProjectName(projectPath: string): string;
32
+ /**
33
+ * Check if a project path points to a temp directory.
34
+ */
35
+ export declare function isTempDirectory(projectPath: string): boolean;
36
+ /**
37
+ * Get language identifier for syntax highlighting.
38
+ */
39
+ export declare function getLanguageForExt(ext: string): string;
40
+ /**
41
+ * Get MIME type for a file path (by extension).
42
+ */
43
+ export declare function getMimeTypeForFile(filePath: string): string;
44
+ /** MIME types for static file serving */
45
+ export declare const MIME_TYPES: Record<string, string>;
46
+ /**
47
+ * Serve a static file from the React dashboard dist.
48
+ * Returns true if the file was served, false otherwise.
49
+ */
50
+ export declare function serveStaticFile(filePath: string, res: ServerResponse): boolean;
51
+ //# sourceMappingURL=tower-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tower-utils.d.ts","sourceRoot":"","sources":["../../../src/agent-farm/servers/tower-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAYhD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAgBvD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAOxC;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,UAAU,CAAC,OAAO,WAAW,CAAC,CAEtE;AAMD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAOhE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE1D;AAYD;;GAEG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAO5D;AAMD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQrD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAS3D;AAMD,yCAAyC;AACzC,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAc7C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAgB9E"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Utility functions for tower server.
3
+ * Spec 0105: Tower Server Decomposition — Phase 1
4
+ *
5
+ * Contains: rate limiting, path normalization, temp directory detection,
6
+ * project name extraction, MIME types, and static file serving.
7
+ */
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { tmpdir } from 'node:os';
11
+ // ============================================================================
12
+ // Rate Limiting
13
+ // ============================================================================
14
+ const RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute
15
+ const RATE_LIMIT_MAX = 10;
16
+ const activationRateLimits = new Map();
17
+ /**
18
+ * Check if a client has exceeded the rate limit for activations.
19
+ * Returns true if rate limit exceeded, false if allowed.
20
+ */
21
+ export function isRateLimited(clientIp) {
22
+ const now = Date.now();
23
+ const entry = activationRateLimits.get(clientIp);
24
+ if (!entry || now - entry.windowStart >= RATE_LIMIT_WINDOW_MS) {
25
+ // New window
26
+ activationRateLimits.set(clientIp, { count: 1, windowStart: now });
27
+ return false;
28
+ }
29
+ if (entry.count >= RATE_LIMIT_MAX) {
30
+ return true;
31
+ }
32
+ entry.count++;
33
+ return false;
34
+ }
35
+ /**
36
+ * Clean up old rate limit entries.
37
+ */
38
+ export function cleanupRateLimits() {
39
+ const now = Date.now();
40
+ for (const [ip, entry] of activationRateLimits.entries()) {
41
+ if (now - entry.windowStart >= RATE_LIMIT_WINDOW_MS * 2) {
42
+ activationRateLimits.delete(ip);
43
+ }
44
+ }
45
+ }
46
+ /**
47
+ * Start periodic cleanup of stale rate limit entries.
48
+ * Returns the interval handle so the orchestrator can clear it on shutdown.
49
+ */
50
+ export function startRateLimitCleanup() {
51
+ return setInterval(cleanupRateLimits, 5 * 60 * 1000);
52
+ }
53
+ // ============================================================================
54
+ // Path Utilities
55
+ // ============================================================================
56
+ /**
57
+ * Normalize a project path to its canonical form for consistent SQLite storage.
58
+ * Uses realpath to resolve symlinks and relative paths.
59
+ */
60
+ export function normalizeProjectPath(projectPath) {
61
+ try {
62
+ return fs.realpathSync(projectPath);
63
+ }
64
+ catch {
65
+ // Path doesn't exist yet, normalize without realpath
66
+ return path.resolve(projectPath);
67
+ }
68
+ }
69
+ /**
70
+ * Get project name from path.
71
+ */
72
+ export function getProjectName(projectPath) {
73
+ return path.basename(projectPath);
74
+ }
75
+ // Resolve once at module load: both symlinked and real temp dir paths
76
+ const _tmpDir = tmpdir();
77
+ const _tmpDirResolved = (() => {
78
+ try {
79
+ return fs.realpathSync(_tmpDir);
80
+ }
81
+ catch {
82
+ return _tmpDir;
83
+ }
84
+ })();
85
+ /**
86
+ * Check if a project path points to a temp directory.
87
+ */
88
+ export function isTempDirectory(projectPath) {
89
+ return (projectPath.startsWith(_tmpDir + '/') ||
90
+ projectPath.startsWith(_tmpDirResolved + '/') ||
91
+ projectPath.startsWith('/tmp/') ||
92
+ projectPath.startsWith('/private/tmp/'));
93
+ }
94
+ // ============================================================================
95
+ // Language & MIME Detection
96
+ // ============================================================================
97
+ /**
98
+ * Get language identifier for syntax highlighting.
99
+ */
100
+ export function getLanguageForExt(ext) {
101
+ const langMap = {
102
+ js: 'javascript', ts: 'typescript', jsx: 'javascript', tsx: 'typescript',
103
+ py: 'python', sh: 'bash', bash: 'bash', md: 'markdown',
104
+ html: 'markup', css: 'css', json: 'json', yaml: 'yaml', yml: 'yaml',
105
+ rs: 'rust', go: 'go', java: 'java', c: 'c', cpp: 'cpp', h: 'c',
106
+ };
107
+ return langMap[ext] || ext || 'plaintext';
108
+ }
109
+ /**
110
+ * Get MIME type for a file path (by extension).
111
+ */
112
+ export function getMimeTypeForFile(filePath) {
113
+ const ext = path.extname(filePath).slice(1).toLowerCase();
114
+ const mimeTypes = {
115
+ png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg',
116
+ gif: 'image/gif', webp: 'image/webp', svg: 'image/svg+xml',
117
+ mp4: 'video/mp4', webm: 'video/webm', mov: 'video/quicktime',
118
+ pdf: 'application/pdf', txt: 'text/plain',
119
+ };
120
+ return mimeTypes[ext] || 'application/octet-stream';
121
+ }
122
+ // ============================================================================
123
+ // Static File Serving
124
+ // ============================================================================
125
+ /** MIME types for static file serving */
126
+ export const MIME_TYPES = {
127
+ '.html': 'text/html',
128
+ '.js': 'application/javascript',
129
+ '.css': 'text/css',
130
+ '.json': 'application/json',
131
+ '.png': 'image/png',
132
+ '.jpg': 'image/jpeg',
133
+ '.gif': 'image/gif',
134
+ '.svg': 'image/svg+xml',
135
+ '.ico': 'image/x-icon',
136
+ '.woff': 'font/woff',
137
+ '.woff2': 'font/woff2',
138
+ '.ttf': 'font/ttf',
139
+ '.map': 'application/json',
140
+ };
141
+ /**
142
+ * Serve a static file from the React dashboard dist.
143
+ * Returns true if the file was served, false otherwise.
144
+ */
145
+ export function serveStaticFile(filePath, res) {
146
+ if (!fs.existsSync(filePath)) {
147
+ return false;
148
+ }
149
+ const ext = path.extname(filePath);
150
+ const contentType = MIME_TYPES[ext] || 'application/octet-stream';
151
+ try {
152
+ const content = fs.readFileSync(filePath);
153
+ res.writeHead(200, { 'Content-Type': contentType });
154
+ res.end(content);
155
+ return true;
156
+ }
157
+ catch {
158
+ return false;
159
+ }
160
+ }
161
+ //# sourceMappingURL=tower-utils.js.map