@clsh/agent 0.0.1

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 (53) hide show
  1. package/dist/auth.d.ts +37 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/auth.js +62 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/config.d.ts +23 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +119 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/control-mode-parser.d.ts +68 -0
  10. package/dist/control-mode-parser.d.ts.map +1 -0
  11. package/dist/control-mode-parser.js +111 -0
  12. package/dist/control-mode-parser.js.map +1 -0
  13. package/dist/db.d.ts +44 -0
  14. package/dist/db.d.ts.map +1 -0
  15. package/dist/db.js +63 -0
  16. package/dist/db.js.map +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +114 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/power.d.ts +9 -0
  22. package/dist/power.d.ts.map +1 -0
  23. package/dist/power.js +29 -0
  24. package/dist/power.js.map +1 -0
  25. package/dist/pty-manager.d.ts +110 -0
  26. package/dist/pty-manager.d.ts.map +1 -0
  27. package/dist/pty-manager.js +468 -0
  28. package/dist/pty-manager.js.map +1 -0
  29. package/dist/server.d.ts +24 -0
  30. package/dist/server.d.ts.map +1 -0
  31. package/dist/server.js +160 -0
  32. package/dist/server.js.map +1 -0
  33. package/dist/sse-handler.d.ts +13 -0
  34. package/dist/sse-handler.d.ts.map +1 -0
  35. package/dist/sse-handler.js +76 -0
  36. package/dist/sse-handler.js.map +1 -0
  37. package/dist/tmux.d.ts +34 -0
  38. package/dist/tmux.d.ts.map +1 -0
  39. package/dist/tmux.js +114 -0
  40. package/dist/tmux.js.map +1 -0
  41. package/dist/tunnel.d.ts +43 -0
  42. package/dist/tunnel.d.ts.map +1 -0
  43. package/dist/tunnel.js +294 -0
  44. package/dist/tunnel.js.map +1 -0
  45. package/dist/types.d.ts +76 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +2 -0
  48. package/dist/types.js.map +1 -0
  49. package/dist/ws-handler.d.ts +13 -0
  50. package/dist/ws-handler.d.ts.map +1 -0
  51. package/dist/ws-handler.js +266 -0
  52. package/dist/ws-handler.js.map +1 -0
  53. package/package.json +47 -0
@@ -0,0 +1,468 @@
1
+ import { spawn } from 'node-pty';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { homedir } from 'node:os';
4
+ import { basename } from 'node:path';
5
+ import { tmuxSessionExists, killTmuxSession, listClshTmuxSessions, capturePaneContent, TMUX_SOCKET } from './tmux.js';
6
+ import { ControlModeLineBuffer, buildSendKeysCommands } from './control-mode-parser.js';
7
+ /** Maximum number of buffer entries retained per session for reconnection replay. */
8
+ const MAX_BUFFER_SIZE = 10_000;
9
+ /** Interval in ms for checking idle status across sessions. */
10
+ const IDLE_CHECK_INTERVAL = 2_000;
11
+ /** Time in ms after last activity before a session is considered idle. */
12
+ const IDLE_THRESHOLD = 2_500;
13
+ /** Environment variables that must never leak into PTY child processes. */
14
+ const SENSITIVE_ENV_VARS = [
15
+ 'NGROK_AUTHTOKEN',
16
+ 'RESEND_API_KEY',
17
+ 'JWT_SECRET',
18
+ 'CLAUDECODE',
19
+ ];
20
+ /** Maps shell types to their executable and arguments. */
21
+ const SHELL_MAP = {
22
+ zsh: ['zsh', ['-l']],
23
+ tmux: ['tmux', ['new-session', '-A', '-s', 'dev']],
24
+ claude: ['claude', []],
25
+ };
26
+ /** Shell types that can be wrapped in tmux for persistence. */
27
+ const TMUX_WRAPPABLE = new Set(['zsh', 'claude']);
28
+ /**
29
+ * Parses OSC 7 escape sequences to extract the current working directory.
30
+ * OSC 7 format: \x1b]7;file:///path\x07 or \x1b]7;file:///path\x1b\\
31
+ */
32
+ function parseOSC7(data) {
33
+ // eslint-disable-next-line no-control-regex
34
+ const match = /\x1b\]7;file:\/\/[^/]*([^\x07\x1b]+)(?:\x07|\x1b\\)/.exec(data);
35
+ if (!match)
36
+ return null;
37
+ try {
38
+ return decodeURIComponent(match[1]);
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ /**
45
+ * Builds a sanitized environment for PTY child processes.
46
+ * Strips sensitive variables and injects terminal-friendly defaults.
47
+ */
48
+ function buildSafeEnv() {
49
+ const env = {};
50
+ for (const [key, value] of Object.entries(process.env)) {
51
+ if (value !== undefined && !SENSITIVE_ENV_VARS.includes(key)) {
52
+ env[key] = value;
53
+ }
54
+ }
55
+ env['FORCE_COLOR'] = '1';
56
+ env['TERM'] = 'xterm-256color';
57
+ return env;
58
+ }
59
+ export class PTYManager {
60
+ sessions = new Map();
61
+ idleCheckInterval = null;
62
+ updateListeners = new Map();
63
+ tmuxEnabled;
64
+ tmuxConfPath;
65
+ db;
66
+ constructor(options) {
67
+ this.tmuxEnabled = options?.tmuxEnabled ?? false;
68
+ this.tmuxConfPath = options?.tmuxConfPath ?? null;
69
+ this.db = options?.dbStatements ?? null;
70
+ this.idleCheckInterval = setInterval(() => {
71
+ this.checkIdleSessions();
72
+ }, IDLE_CHECK_INTERVAL);
73
+ }
74
+ /** Checks all sessions and transitions them to idle if no recent activity. */
75
+ checkIdleSessions() {
76
+ const now = Date.now();
77
+ for (const session of this.sessions.values()) {
78
+ if (session.status === 'run' && now - session.lastActivityAt > IDLE_THRESHOLD) {
79
+ session.status = 'idle';
80
+ this.emitUpdate(session);
81
+ }
82
+ }
83
+ }
84
+ /** Emits an update event to all update listeners for a session. */
85
+ emitUpdate(session) {
86
+ const meta = {
87
+ name: session.name,
88
+ cwd: session.cwd,
89
+ status: session.status,
90
+ };
91
+ const listeners = this.updateListeners.get(session.id);
92
+ if (listeners) {
93
+ for (const listener of listeners) {
94
+ listener(meta);
95
+ }
96
+ }
97
+ }
98
+ /** Whether a shell type should be wrapped in tmux. */
99
+ shouldWrapInTmux(shell) {
100
+ return this.tmuxEnabled && TMUX_WRAPPABLE.has(shell);
101
+ }
102
+ /**
103
+ * Processes raw terminal data for a session: tracks CWD, updates buffer, notifies listeners.
104
+ * Used by both raw and control mode handlers to process actual terminal output.
105
+ */
106
+ processSessionOutput(session, data, dataListeners) {
107
+ session.lastActivityAt = Date.now();
108
+ session.status = 'run';
109
+ const parsedCwd = parseOSC7(data);
110
+ if (parsedCwd) {
111
+ session.cwd = parsedCwd;
112
+ if (!session.userRenamed) {
113
+ session.name = basename(parsedCwd);
114
+ }
115
+ this.emitUpdate(session);
116
+ if (session.tmuxName && this.db) {
117
+ try {
118
+ this.db.updatePtySession.run(session.name, session.cwd, session.id);
119
+ }
120
+ catch { /* non-critical */ }
121
+ }
122
+ }
123
+ session.buffer.push(data);
124
+ if (session.buffer.length > MAX_BUFFER_SIZE) {
125
+ session.buffer.splice(0, session.buffer.length - MAX_BUFFER_SIZE);
126
+ }
127
+ for (const listener of dataListeners) {
128
+ listener(data);
129
+ }
130
+ }
131
+ /** Wires up data/exit handlers for a raw PTY session (no tmux). */
132
+ wireRawHandlers(session, dataListeners, exitListeners) {
133
+ session.pty.onData((data) => {
134
+ this.processSessionOutput(session, data, dataListeners);
135
+ });
136
+ session.pty.onExit((event) => {
137
+ for (const listener of exitListeners) {
138
+ listener(event);
139
+ }
140
+ if (session.tmuxName) {
141
+ killTmuxSession(session.tmuxName);
142
+ if (this.db) {
143
+ try {
144
+ this.db.deletePtySession.run(session.id);
145
+ }
146
+ catch { /* ignore */ }
147
+ }
148
+ }
149
+ this.sessions.delete(session.id);
150
+ this.updateListeners.delete(session.id);
151
+ });
152
+ }
153
+ /**
154
+ * Wires up data/exit handlers for a tmux control mode session.
155
+ * Parses the control mode protocol and extracts raw %output data.
156
+ */
157
+ wireControlModeHandlers(session, dataListeners, exitListeners) {
158
+ const lineBuffer = new ControlModeLineBuffer((event) => {
159
+ if (event.type === 'output') {
160
+ this.processSessionOutput(session, event.data, dataListeners);
161
+ }
162
+ // %exit is handled by pty.onExit below
163
+ });
164
+ session.pty.onData((data) => {
165
+ lineBuffer.feed(data);
166
+ });
167
+ session.pty.onExit((event) => {
168
+ for (const listener of exitListeners) {
169
+ listener(event);
170
+ }
171
+ if (session.tmuxName) {
172
+ killTmuxSession(session.tmuxName);
173
+ if (this.db) {
174
+ try {
175
+ this.db.deletePtySession.run(session.id);
176
+ }
177
+ catch { /* ignore */ }
178
+ }
179
+ }
180
+ this.sessions.delete(session.id);
181
+ this.updateListeners.delete(session.id);
182
+ });
183
+ }
184
+ /**
185
+ * Creates a new PTY session with the specified shell type and dimensions.
186
+ * If tmux is enabled and the shell is wrappable, uses tmux control mode (-CC)
187
+ * for session persistence with proper scrollback support.
188
+ * Falls back to raw PTY if tmux is unavailable.
189
+ */
190
+ create(shell, cols = 80, rows = 24, name) {
191
+ const id = randomUUID();
192
+ const initialCwd = homedir();
193
+ const wrap = this.shouldWrapInTmux(shell);
194
+ const tmuxName = wrap ? `clsh-${id}` : null;
195
+ let cmd;
196
+ let args;
197
+ if (wrap && this.tmuxConfPath) {
198
+ const [innerCmd, innerArgs] = SHELL_MAP[shell];
199
+ cmd = 'tmux';
200
+ args = [
201
+ '-CC',
202
+ '-L', TMUX_SOCKET,
203
+ '-f', this.tmuxConfPath,
204
+ 'new-session',
205
+ '-s', tmuxName,
206
+ '-x', String(cols),
207
+ '-y', String(rows),
208
+ innerCmd, ...innerArgs,
209
+ ];
210
+ }
211
+ else {
212
+ [cmd, args] = SHELL_MAP[shell];
213
+ }
214
+ const pty = spawn(cmd, args, {
215
+ name: 'xterm-256color',
216
+ cols,
217
+ rows,
218
+ cwd: initialCwd,
219
+ env: buildSafeEnv(),
220
+ });
221
+ const buffer = [];
222
+ const dataListeners = [];
223
+ const exitListeners = [];
224
+ this.updateListeners.set(id, []);
225
+ const session = {
226
+ id,
227
+ shell,
228
+ pty,
229
+ buffer,
230
+ name: name ?? shell,
231
+ cwd: initialCwd,
232
+ status: 'idle',
233
+ lastActivityAt: Date.now(),
234
+ tmuxName,
235
+ userRenamed: !!name,
236
+ onData: (callback) => { dataListeners.push(callback); },
237
+ onExit: (callback) => { exitListeners.push(callback); },
238
+ onUpdate: (callback) => {
239
+ const listeners = this.updateListeners.get(id);
240
+ if (listeners)
241
+ listeners.push(callback);
242
+ },
243
+ };
244
+ if (tmuxName) {
245
+ this.wireControlModeHandlers(session, dataListeners, exitListeners);
246
+ }
247
+ else {
248
+ this.wireRawHandlers(session, dataListeners, exitListeners);
249
+ }
250
+ this.sessions.set(id, session);
251
+ // Persist to DB for rediscovery
252
+ if (tmuxName && this.db) {
253
+ try {
254
+ this.db.insertPtySession.run(id, tmuxName, shell, session.name, session.cwd);
255
+ }
256
+ catch { /* non-critical */ }
257
+ }
258
+ return session;
259
+ }
260
+ /**
261
+ * Reattaches to a tmux session that survived a server restart.
262
+ * Uses control mode (-CC) for the attachment and bootstraps the buffer
263
+ * with capture-pane content so the client sees existing scrollback.
264
+ * Returns the restored PTYSession or null if the tmux session is gone.
265
+ */
266
+ reattach(sessionId, tmuxName, shell, savedName, savedCwd, cols = 80, rows = 24) {
267
+ if (!tmuxSessionExists(tmuxName)) {
268
+ // tmux session is gone — clean up DB
269
+ if (this.db) {
270
+ try {
271
+ this.db.deletePtySession.run(sessionId);
272
+ }
273
+ catch { /* ignore */ }
274
+ }
275
+ return null;
276
+ }
277
+ // Capture existing scrollback before attaching (so we don't miss/duplicate anything)
278
+ const capturedContent = capturePaneContent(tmuxName);
279
+ const args = this.tmuxConfPath
280
+ ? ['-CC', '-L', TMUX_SOCKET, '-f', this.tmuxConfPath, 'attach-session', '-t', tmuxName]
281
+ : ['-CC', '-L', TMUX_SOCKET, 'attach-session', '-t', tmuxName];
282
+ const pty = spawn('tmux', args, {
283
+ name: 'xterm-256color',
284
+ cols,
285
+ rows,
286
+ cwd: savedCwd || homedir(),
287
+ env: buildSafeEnv(),
288
+ });
289
+ const buffer = [];
290
+ const dataListeners = [];
291
+ const exitListeners = [];
292
+ this.updateListeners.set(sessionId, []);
293
+ // Bootstrap buffer with captured scrollback content
294
+ if (capturedContent) {
295
+ buffer.push(capturedContent);
296
+ }
297
+ const session = {
298
+ id: sessionId,
299
+ shell,
300
+ pty,
301
+ buffer,
302
+ name: savedName || shell,
303
+ cwd: savedCwd || homedir(),
304
+ status: 'idle',
305
+ lastActivityAt: Date.now(),
306
+ tmuxName,
307
+ userRenamed: false,
308
+ onData: (callback) => { dataListeners.push(callback); },
309
+ onExit: (callback) => { exitListeners.push(callback); },
310
+ onUpdate: (callback) => {
311
+ const listeners = this.updateListeners.get(sessionId);
312
+ if (listeners)
313
+ listeners.push(callback);
314
+ },
315
+ };
316
+ this.wireControlModeHandlers(session, dataListeners, exitListeners);
317
+ this.sessions.set(sessionId, session);
318
+ return session;
319
+ }
320
+ /**
321
+ * Rediscovers sessions from a previous server run.
322
+ * Reads the pty_sessions table, reattaches to any tmux sessions that still exist,
323
+ * and cleans up rows for dead sessions. Also kills zombie tmux sessions.
324
+ * Returns the list of successfully recovered sessions.
325
+ */
326
+ rediscoverAll() {
327
+ if (!this.db)
328
+ return [];
329
+ const recovered = [];
330
+ const dbRows = this.db.listPtySessions.all();
331
+ const dbTmuxNames = new Set();
332
+ for (const row of dbRows) {
333
+ dbTmuxNames.add(row.tmux_name);
334
+ const session = this.reattach(row.id, row.tmux_name, row.shell, row.name, row.cwd);
335
+ if (session) {
336
+ recovered.push(session);
337
+ }
338
+ }
339
+ // Kill zombie tmux sessions (exist in tmux but not in DB)
340
+ const liveTmuxSessions = listClshTmuxSessions();
341
+ for (const tmuxName of liveTmuxSessions) {
342
+ if (!dbTmuxNames.has(tmuxName)) {
343
+ killTmuxSession(tmuxName);
344
+ }
345
+ }
346
+ return recovered;
347
+ }
348
+ /**
349
+ * Writes data to the PTY stdin of the specified session.
350
+ * For control mode sessions, translates input to tmux send-keys -H commands.
351
+ */
352
+ write(id, data) {
353
+ const session = this.sessions.get(id);
354
+ if (!session) {
355
+ throw new Error(`Session not found: ${id}`);
356
+ }
357
+ if (session.tmuxName) {
358
+ // Control mode: send input via tmux send-keys -H (hex-encoded bytes)
359
+ const commands = buildSendKeysCommands(session.tmuxName, data);
360
+ for (const cmd of commands) {
361
+ session.pty.write(cmd + '\n');
362
+ }
363
+ }
364
+ else {
365
+ // Raw PTY: write directly
366
+ session.pty.write(data);
367
+ }
368
+ }
369
+ /** Renames a session and marks it as user-renamed (suppresses OSC 7 name updates). */
370
+ rename(id, name) {
371
+ const session = this.sessions.get(id);
372
+ if (!session) {
373
+ throw new Error(`Session not found: ${id}`);
374
+ }
375
+ session.name = name;
376
+ session.userRenamed = true;
377
+ this.emitUpdate(session);
378
+ if (session.tmuxName && this.db) {
379
+ try {
380
+ this.db.updatePtySession.run(session.name, session.cwd, session.id);
381
+ }
382
+ catch { /* non-critical */ }
383
+ }
384
+ }
385
+ /**
386
+ * Resizes the PTY of the specified session.
387
+ * For control mode sessions, sends refresh-client -C to tmux.
388
+ */
389
+ resize(id, cols, rows) {
390
+ const session = this.sessions.get(id);
391
+ if (!session) {
392
+ throw new Error(`Session not found: ${id}`);
393
+ }
394
+ if (session.tmuxName) {
395
+ // Control mode: tell tmux the new client size
396
+ session.pty.write(`refresh-client -C ${String(cols)},${String(rows)}\n`);
397
+ }
398
+ else {
399
+ // Raw PTY: resize directly
400
+ session.pty.resize(cols, rows);
401
+ }
402
+ }
403
+ /** Destroys a single session by ID, killing the underlying PTY and tmux session. */
404
+ destroy(id) {
405
+ const session = this.sessions.get(id);
406
+ if (!session)
407
+ return;
408
+ session.pty.kill();
409
+ if (session.tmuxName) {
410
+ killTmuxSession(session.tmuxName);
411
+ if (this.db) {
412
+ try {
413
+ this.db.deletePtySession.run(id);
414
+ }
415
+ catch { /* ignore */ }
416
+ }
417
+ }
418
+ this.sessions.delete(id);
419
+ this.updateListeners.delete(id);
420
+ }
421
+ /** Retrieves a session by ID, or undefined if not found. */
422
+ get(id) {
423
+ return this.sessions.get(id);
424
+ }
425
+ /** Returns all active sessions. */
426
+ list() {
427
+ return Array.from(this.sessions.values());
428
+ }
429
+ /**
430
+ * Graceful shutdown — kills node-pty client processes but leaves tmux sessions alive.
431
+ * tmux sessions and DB rows survive for rediscovery on next startup.
432
+ */
433
+ destroyAll() {
434
+ for (const session of this.sessions.values()) {
435
+ session.pty.kill();
436
+ }
437
+ this.sessions.clear();
438
+ this.updateListeners.clear();
439
+ if (this.idleCheckInterval) {
440
+ clearInterval(this.idleCheckInterval);
441
+ this.idleCheckInterval = null;
442
+ }
443
+ }
444
+ /**
445
+ * Full cleanup — kills everything including tmux sessions and DB rows.
446
+ */
447
+ destroyAllIncludingTmux() {
448
+ for (const session of this.sessions.values()) {
449
+ session.pty.kill();
450
+ if (session.tmuxName) {
451
+ killTmuxSession(session.tmuxName);
452
+ }
453
+ }
454
+ this.sessions.clear();
455
+ this.updateListeners.clear();
456
+ if (this.idleCheckInterval) {
457
+ clearInterval(this.idleCheckInterval);
458
+ this.idleCheckInterval = null;
459
+ }
460
+ if (this.db) {
461
+ try {
462
+ this.db.deleteAllPtySessions.run();
463
+ }
464
+ catch { /* ignore */ }
465
+ }
466
+ }
467
+ }
468
+ //# sourceMappingURL=pty-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pty-manager.js","sourceRoot":"","sources":["../src/pty-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { type Express } from 'express';
2
+ import { type Server as HttpServer } from 'node:http';
3
+ import { WebSocketServer } from 'ws';
4
+ import { generateBootstrapToken } from './auth.js';
5
+ import type { DbStatements } from './db.js';
6
+ import type { AgentConfig } from './config.js';
7
+ export interface ServerContext {
8
+ app: Express;
9
+ httpServer: HttpServer;
10
+ wss: WebSocketServer;
11
+ }
12
+ /**
13
+ * Creates and configures the Express app, HTTP server, and WebSocketServer.
14
+ * Mounts auth routes, SSE routes, health check, and static file serving.
15
+ */
16
+ export declare function createAppServer(config: AgentConfig, statements: DbStatements): ServerContext;
17
+ /**
18
+ * Starts the HTTP server on the configured port.
19
+ * If the port is busy, tries up to 10 consecutive ports.
20
+ * Returns the actual port the server is listening on.
21
+ */
22
+ export declare function startServer(httpServer: HttpServer, port: number): Promise<number>;
23
+ export { generateBootstrapToken };
24
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAEhD,OAAO,EAAgB,KAAK,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAC;AAIrC,OAAO,EACL,sBAAsB,EAGvB,MAAM,WAAW,CAAC;AAEnB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,OAAO,CAAC;IACb,UAAU,EAAE,UAAU,CAAC;IACvB,GAAG,EAAE,eAAe,CAAC;CACtB;AA6BD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,YAAY,GACvB,aAAa,CAqEf;AA6DD;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,UAAU,EACtB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
package/dist/server.js ADDED
@@ -0,0 +1,160 @@
1
+ import express from 'express';
2
+ import { createServer as createNetServer } from 'node:net';
3
+ import { createServer } from 'node:http';
4
+ import { WebSocketServer } from 'ws';
5
+ import { join, dirname } from 'node:path';
6
+ import { existsSync } from 'node:fs';
7
+ import { createRequire } from 'node:module';
8
+ import { generateBootstrapToken, verifyBootstrapToken, createSessionJWT, } from './auth.js';
9
+ import { createSSERouter } from './sse-handler.js';
10
+ /**
11
+ * Finds the @clsh/web dist directory by probing multiple candidate paths.
12
+ * Works both in the monorepo (sibling package) and when installed via npm.
13
+ */
14
+ function findWebDist() {
15
+ const candidates = [
16
+ // Monorepo: packages/agent/dist/ -> packages/web/dist/
17
+ join(import.meta.dirname, '..', '..', 'web', 'dist'),
18
+ ];
19
+ // npm install: resolve @clsh/web package and find its dist/
20
+ try {
21
+ const require = createRequire(import.meta.url);
22
+ const webPkg = require.resolve('@clsh/web/package.json');
23
+ candidates.push(join(dirname(webPkg), 'dist'));
24
+ }
25
+ catch {
26
+ // @clsh/web not resolvable as a dependency — that's fine in monorepo
27
+ }
28
+ for (const candidate of candidates) {
29
+ if (existsSync(join(candidate, 'index.html'))) {
30
+ return candidate;
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+ /**
36
+ * Creates and configures the Express app, HTTP server, and WebSocketServer.
37
+ * Mounts auth routes, SSE routes, health check, and static file serving.
38
+ */
39
+ export function createAppServer(config, statements) {
40
+ const app = express();
41
+ // Middleware — CORS for development
42
+ app.use((_req, res, next) => {
43
+ res.header('Access-Control-Allow-Origin', '*');
44
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
45
+ res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
46
+ if (_req.method === 'OPTIONS') {
47
+ res.sendStatus(204);
48
+ return;
49
+ }
50
+ next();
51
+ });
52
+ app.use(express.json());
53
+ // Health check
54
+ app.get('/api/health', (_req, res) => {
55
+ res.json({ status: 'ok', timestamp: new Date().toISOString() });
56
+ });
57
+ // Auth routes
58
+ mountAuthRoutes(app, config, statements);
59
+ // SSE routes
60
+ const sseRouter = createSSERouter();
61
+ app.use('/api/sse', sseRouter);
62
+ // Static file serving (web dist)
63
+ const webDistPath = findWebDist();
64
+ if (webDistPath) {
65
+ app.use(express.static(webDistPath));
66
+ // SPA fallback: serve index.html for non-API routes
67
+ app.get('*', (req, res, next) => {
68
+ if (req.path.startsWith('/api')) {
69
+ next();
70
+ return;
71
+ }
72
+ const indexPath = join(webDistPath, 'index.html');
73
+ if (existsSync(indexPath)) {
74
+ res.sendFile(indexPath);
75
+ }
76
+ else {
77
+ next();
78
+ }
79
+ });
80
+ }
81
+ // Create HTTP server
82
+ const httpServer = createServer(app);
83
+ // Create WebSocket server with native ping to detect dead connections.
84
+ // Clients that don't respond to a ping within 30s are terminated.
85
+ const wss = new WebSocketServer({ server: httpServer });
86
+ const WS_PING_INTERVAL = 30_000;
87
+ const pingInterval = setInterval(() => {
88
+ for (const ws of wss.clients) {
89
+ if (ws.isAlive === false) {
90
+ ws.terminate();
91
+ continue;
92
+ }
93
+ ws.isAlive = false;
94
+ ws.ping();
95
+ }
96
+ }, WS_PING_INTERVAL);
97
+ wss.on('close', () => clearInterval(pingInterval));
98
+ return { app, httpServer, wss };
99
+ }
100
+ function mountAuthRoutes(app, config, statements) {
101
+ // POST /api/auth/bootstrap — exchange a bootstrap token for a JWT
102
+ app.post('/api/auth/bootstrap', async (req, res) => {
103
+ try {
104
+ const { token } = req.body;
105
+ if (!token || typeof token !== 'string') {
106
+ res.status(400).json({ error: 'Missing or invalid token' });
107
+ return;
108
+ }
109
+ const valid = verifyBootstrapToken(statements, token);
110
+ if (!valid) {
111
+ res.status(401).json({ error: 'Invalid or expired bootstrap token' });
112
+ return;
113
+ }
114
+ const jwt = await createSessionJWT({ authMethod: 'bootstrap' }, config.jwtSecret);
115
+ res.json({ token: jwt });
116
+ }
117
+ catch (err) {
118
+ console.error('Bootstrap auth error:', err);
119
+ res.status(500).json({ error: 'Internal server error' });
120
+ }
121
+ });
122
+ }
123
+ /**
124
+ * Checks if a port is available by briefly binding a TCP server to it.
125
+ */
126
+ function isPortFree(port) {
127
+ return new Promise((resolve) => {
128
+ const srv = createNetServer();
129
+ srv.once('error', () => resolve(false));
130
+ srv.listen(port, () => {
131
+ srv.close(() => resolve(true));
132
+ });
133
+ });
134
+ }
135
+ /**
136
+ * Finds the first free port starting from `port`, trying up to 10 consecutive ports.
137
+ */
138
+ async function findFreePort(port) {
139
+ for (let p = port; p < port + 10; p++) {
140
+ if (await isPortFree(p))
141
+ return p;
142
+ console.log(` Port ${String(p)} in use, trying ${String(p + 1)}...`);
143
+ }
144
+ throw new Error(`No free port found in range ${String(port)}-${String(port + 9)}`);
145
+ }
146
+ /**
147
+ * Starts the HTTP server on the configured port.
148
+ * If the port is busy, tries up to 10 consecutive ports.
149
+ * Returns the actual port the server is listening on.
150
+ */
151
+ export async function startServer(httpServer, port) {
152
+ const freePort = await findFreePort(port);
153
+ return new Promise((resolve) => {
154
+ httpServer.listen(freePort, () => {
155
+ resolve(freePort);
156
+ });
157
+ });
158
+ }
159
+ export { generateBootstrapToken };
160
+ //# sourceMappingURL=server.js.map