@launchsecure/launch-kit 0.0.34 → 0.0.35

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.
@@ -0,0 +1,4078 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __commonJS = (cb, mod) => function __require() {
13
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
14
+ };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
28
+ // If the importer is in node compatibility mode or this is not an ESM
29
+ // file that has been converted to a CommonJS file using a Babel-
30
+ // compatible transform (i.e. "__esModule" has not been set), then set
31
+ // "default" to the CommonJS "module.exports" for node compatibility.
32
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
33
+ mod
34
+ ));
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
+
37
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/max.js
38
+ var max_default;
39
+ var init_max = __esm({
40
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/max.js"() {
41
+ max_default = "ffffffff-ffff-ffff-ffff-ffffffffffff";
42
+ }
43
+ });
44
+
45
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/nil.js
46
+ var nil_default;
47
+ var init_nil = __esm({
48
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/nil.js"() {
49
+ nil_default = "00000000-0000-0000-0000-000000000000";
50
+ }
51
+ });
52
+
53
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/regex.js
54
+ var regex_default;
55
+ var init_regex = __esm({
56
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/regex.js"() {
57
+ regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
58
+ }
59
+ });
60
+
61
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/validate.js
62
+ function validate(uuid) {
63
+ return typeof uuid === "string" && regex_default.test(uuid);
64
+ }
65
+ var validate_default;
66
+ var init_validate = __esm({
67
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/validate.js"() {
68
+ init_regex();
69
+ validate_default = validate;
70
+ }
71
+ });
72
+
73
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/parse.js
74
+ function parse(uuid) {
75
+ if (!validate_default(uuid)) {
76
+ throw TypeError("Invalid UUID");
77
+ }
78
+ let v;
79
+ const arr = new Uint8Array(16);
80
+ arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24;
81
+ arr[1] = v >>> 16 & 255;
82
+ arr[2] = v >>> 8 & 255;
83
+ arr[3] = v & 255;
84
+ arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8;
85
+ arr[5] = v & 255;
86
+ arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8;
87
+ arr[7] = v & 255;
88
+ arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8;
89
+ arr[9] = v & 255;
90
+ arr[10] = (v = parseInt(uuid.slice(24, 36), 16)) / 1099511627776 & 255;
91
+ arr[11] = v / 4294967296 & 255;
92
+ arr[12] = v >>> 24 & 255;
93
+ arr[13] = v >>> 16 & 255;
94
+ arr[14] = v >>> 8 & 255;
95
+ arr[15] = v & 255;
96
+ return arr;
97
+ }
98
+ var parse_default;
99
+ var init_parse = __esm({
100
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/parse.js"() {
101
+ init_validate();
102
+ parse_default = parse;
103
+ }
104
+ });
105
+
106
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/stringify.js
107
+ function unsafeStringify(arr, offset = 0) {
108
+ return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
109
+ }
110
+ function stringify(arr, offset = 0) {
111
+ const uuid = unsafeStringify(arr, offset);
112
+ if (!validate_default(uuid)) {
113
+ throw TypeError("Stringified UUID is invalid");
114
+ }
115
+ return uuid;
116
+ }
117
+ var byteToHex, stringify_default;
118
+ var init_stringify = __esm({
119
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/stringify.js"() {
120
+ init_validate();
121
+ byteToHex = [];
122
+ for (let i = 0; i < 256; ++i) {
123
+ byteToHex.push((i + 256).toString(16).slice(1));
124
+ }
125
+ stringify_default = stringify;
126
+ }
127
+ });
128
+
129
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/rng.js
130
+ function rng() {
131
+ if (poolPtr > rnds8Pool.length - 16) {
132
+ import_node_crypto.default.randomFillSync(rnds8Pool);
133
+ poolPtr = 0;
134
+ }
135
+ return rnds8Pool.slice(poolPtr, poolPtr += 16);
136
+ }
137
+ var import_node_crypto, rnds8Pool, poolPtr;
138
+ var init_rng = __esm({
139
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/rng.js"() {
140
+ import_node_crypto = __toESM(require("node:crypto"));
141
+ rnds8Pool = new Uint8Array(256);
142
+ poolPtr = rnds8Pool.length;
143
+ }
144
+ });
145
+
146
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v1.js
147
+ function v1(options, buf, offset) {
148
+ let i = buf && offset || 0;
149
+ const b = buf || new Array(16);
150
+ options = options || {};
151
+ let node = options.node;
152
+ let clockseq = options.clockseq;
153
+ if (!options._v6) {
154
+ if (!node) {
155
+ node = _nodeId;
156
+ }
157
+ if (clockseq == null) {
158
+ clockseq = _clockseq;
159
+ }
160
+ }
161
+ if (node == null || clockseq == null) {
162
+ const seedBytes = options.random || (options.rng || rng)();
163
+ if (node == null) {
164
+ node = [seedBytes[0], seedBytes[1], seedBytes[2], seedBytes[3], seedBytes[4], seedBytes[5]];
165
+ if (!_nodeId && !options._v6) {
166
+ node[0] |= 1;
167
+ _nodeId = node;
168
+ }
169
+ }
170
+ if (clockseq == null) {
171
+ clockseq = (seedBytes[6] << 8 | seedBytes[7]) & 16383;
172
+ if (_clockseq === void 0 && !options._v6) {
173
+ _clockseq = clockseq;
174
+ }
175
+ }
176
+ }
177
+ let msecs = options.msecs !== void 0 ? options.msecs : Date.now();
178
+ let nsecs = options.nsecs !== void 0 ? options.nsecs : _lastNSecs + 1;
179
+ const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 1e4;
180
+ if (dt < 0 && options.clockseq === void 0) {
181
+ clockseq = clockseq + 1 & 16383;
182
+ }
183
+ if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === void 0) {
184
+ nsecs = 0;
185
+ }
186
+ if (nsecs >= 1e4) {
187
+ throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");
188
+ }
189
+ _lastMSecs = msecs;
190
+ _lastNSecs = nsecs;
191
+ _clockseq = clockseq;
192
+ msecs += 122192928e5;
193
+ const tl = ((msecs & 268435455) * 1e4 + nsecs) % 4294967296;
194
+ b[i++] = tl >>> 24 & 255;
195
+ b[i++] = tl >>> 16 & 255;
196
+ b[i++] = tl >>> 8 & 255;
197
+ b[i++] = tl & 255;
198
+ const tmh = msecs / 4294967296 * 1e4 & 268435455;
199
+ b[i++] = tmh >>> 8 & 255;
200
+ b[i++] = tmh & 255;
201
+ b[i++] = tmh >>> 24 & 15 | 16;
202
+ b[i++] = tmh >>> 16 & 255;
203
+ b[i++] = clockseq >>> 8 | 128;
204
+ b[i++] = clockseq & 255;
205
+ for (let n = 0; n < 6; ++n) {
206
+ b[i + n] = node[n];
207
+ }
208
+ return buf || unsafeStringify(b);
209
+ }
210
+ var _nodeId, _clockseq, _lastMSecs, _lastNSecs, v1_default;
211
+ var init_v1 = __esm({
212
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v1.js"() {
213
+ init_rng();
214
+ init_stringify();
215
+ _lastMSecs = 0;
216
+ _lastNSecs = 0;
217
+ v1_default = v1;
218
+ }
219
+ });
220
+
221
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v1ToV6.js
222
+ function v1ToV6(uuid) {
223
+ const v1Bytes = typeof uuid === "string" ? parse_default(uuid) : uuid;
224
+ const v6Bytes = _v1ToV6(v1Bytes);
225
+ return typeof uuid === "string" ? unsafeStringify(v6Bytes) : v6Bytes;
226
+ }
227
+ function _v1ToV6(v1Bytes, randomize = false) {
228
+ return Uint8Array.of((v1Bytes[6] & 15) << 4 | v1Bytes[7] >> 4 & 15, (v1Bytes[7] & 15) << 4 | (v1Bytes[4] & 240) >> 4, (v1Bytes[4] & 15) << 4 | (v1Bytes[5] & 240) >> 4, (v1Bytes[5] & 15) << 4 | (v1Bytes[0] & 240) >> 4, (v1Bytes[0] & 15) << 4 | (v1Bytes[1] & 240) >> 4, (v1Bytes[1] & 15) << 4 | (v1Bytes[2] & 240) >> 4, 96 | v1Bytes[2] & 15, v1Bytes[3], v1Bytes[8], v1Bytes[9], v1Bytes[10], v1Bytes[11], v1Bytes[12], v1Bytes[13], v1Bytes[14], v1Bytes[15]);
229
+ }
230
+ var init_v1ToV6 = __esm({
231
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v1ToV6.js"() {
232
+ init_parse();
233
+ init_stringify();
234
+ }
235
+ });
236
+
237
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v35.js
238
+ function stringToBytes(str) {
239
+ str = unescape(encodeURIComponent(str));
240
+ const bytes = [];
241
+ for (let i = 0; i < str.length; ++i) {
242
+ bytes.push(str.charCodeAt(i));
243
+ }
244
+ return bytes;
245
+ }
246
+ function v35(name, version2, hashfunc) {
247
+ function generateUUID(value, namespace, buf, offset) {
248
+ var _namespace;
249
+ if (typeof value === "string") {
250
+ value = stringToBytes(value);
251
+ }
252
+ if (typeof namespace === "string") {
253
+ namespace = parse_default(namespace);
254
+ }
255
+ if (((_namespace = namespace) === null || _namespace === void 0 ? void 0 : _namespace.length) !== 16) {
256
+ throw TypeError("Namespace must be array-like (16 iterable integer values, 0-255)");
257
+ }
258
+ let bytes = new Uint8Array(16 + value.length);
259
+ bytes.set(namespace);
260
+ bytes.set(value, namespace.length);
261
+ bytes = hashfunc(bytes);
262
+ bytes[6] = bytes[6] & 15 | version2;
263
+ bytes[8] = bytes[8] & 63 | 128;
264
+ if (buf) {
265
+ offset = offset || 0;
266
+ for (let i = 0; i < 16; ++i) {
267
+ buf[offset + i] = bytes[i];
268
+ }
269
+ return buf;
270
+ }
271
+ return unsafeStringify(bytes);
272
+ }
273
+ try {
274
+ generateUUID.name = name;
275
+ } catch (err) {
276
+ }
277
+ generateUUID.DNS = DNS;
278
+ generateUUID.URL = URL2;
279
+ return generateUUID;
280
+ }
281
+ var DNS, URL2;
282
+ var init_v35 = __esm({
283
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v35.js"() {
284
+ init_stringify();
285
+ init_parse();
286
+ DNS = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
287
+ URL2 = "6ba7b811-9dad-11d1-80b4-00c04fd430c8";
288
+ }
289
+ });
290
+
291
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/md5.js
292
+ function md5(bytes) {
293
+ if (Array.isArray(bytes)) {
294
+ bytes = Buffer.from(bytes);
295
+ } else if (typeof bytes === "string") {
296
+ bytes = Buffer.from(bytes, "utf8");
297
+ }
298
+ return import_node_crypto2.default.createHash("md5").update(bytes).digest();
299
+ }
300
+ var import_node_crypto2, md5_default;
301
+ var init_md5 = __esm({
302
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/md5.js"() {
303
+ import_node_crypto2 = __toESM(require("node:crypto"));
304
+ md5_default = md5;
305
+ }
306
+ });
307
+
308
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v3.js
309
+ var v3, v3_default;
310
+ var init_v3 = __esm({
311
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v3.js"() {
312
+ init_v35();
313
+ init_md5();
314
+ v3 = v35("v3", 48, md5_default);
315
+ v3_default = v3;
316
+ }
317
+ });
318
+
319
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/native.js
320
+ var import_node_crypto3, native_default;
321
+ var init_native = __esm({
322
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/native.js"() {
323
+ import_node_crypto3 = __toESM(require("node:crypto"));
324
+ native_default = {
325
+ randomUUID: import_node_crypto3.default.randomUUID
326
+ };
327
+ }
328
+ });
329
+
330
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v4.js
331
+ function v4(options, buf, offset) {
332
+ if (native_default.randomUUID && !buf && !options) {
333
+ return native_default.randomUUID();
334
+ }
335
+ options = options || {};
336
+ const rnds = options.random || (options.rng || rng)();
337
+ rnds[6] = rnds[6] & 15 | 64;
338
+ rnds[8] = rnds[8] & 63 | 128;
339
+ if (buf) {
340
+ offset = offset || 0;
341
+ for (let i = 0; i < 16; ++i) {
342
+ buf[offset + i] = rnds[i];
343
+ }
344
+ return buf;
345
+ }
346
+ return unsafeStringify(rnds);
347
+ }
348
+ var v4_default;
349
+ var init_v4 = __esm({
350
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v4.js"() {
351
+ init_native();
352
+ init_rng();
353
+ init_stringify();
354
+ v4_default = v4;
355
+ }
356
+ });
357
+
358
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/sha1.js
359
+ function sha1(bytes) {
360
+ if (Array.isArray(bytes)) {
361
+ bytes = Buffer.from(bytes);
362
+ } else if (typeof bytes === "string") {
363
+ bytes = Buffer.from(bytes, "utf8");
364
+ }
365
+ return import_node_crypto4.default.createHash("sha1").update(bytes).digest();
366
+ }
367
+ var import_node_crypto4, sha1_default;
368
+ var init_sha1 = __esm({
369
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/sha1.js"() {
370
+ import_node_crypto4 = __toESM(require("node:crypto"));
371
+ sha1_default = sha1;
372
+ }
373
+ });
374
+
375
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v5.js
376
+ var v5, v5_default;
377
+ var init_v5 = __esm({
378
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v5.js"() {
379
+ init_v35();
380
+ init_sha1();
381
+ v5 = v35("v5", 80, sha1_default);
382
+ v5_default = v5;
383
+ }
384
+ });
385
+
386
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v6.js
387
+ function v6(options = {}, buf, offset = 0) {
388
+ let bytes = v1_default({
389
+ ...options,
390
+ _v6: true
391
+ }, new Uint8Array(16));
392
+ bytes = v1ToV6(bytes);
393
+ if (buf) {
394
+ for (let i = 0; i < 16; i++) {
395
+ buf[offset + i] = bytes[i];
396
+ }
397
+ return buf;
398
+ }
399
+ return unsafeStringify(bytes);
400
+ }
401
+ var init_v6 = __esm({
402
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v6.js"() {
403
+ init_stringify();
404
+ init_v1();
405
+ init_v1ToV6();
406
+ }
407
+ });
408
+
409
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v6ToV1.js
410
+ function v6ToV1(uuid) {
411
+ const v6Bytes = typeof uuid === "string" ? parse_default(uuid) : uuid;
412
+ const v1Bytes = _v6ToV1(v6Bytes);
413
+ return typeof uuid === "string" ? unsafeStringify(v1Bytes) : v1Bytes;
414
+ }
415
+ function _v6ToV1(v6Bytes) {
416
+ return Uint8Array.of((v6Bytes[3] & 15) << 4 | v6Bytes[4] >> 4 & 15, (v6Bytes[4] & 15) << 4 | (v6Bytes[5] & 240) >> 4, (v6Bytes[5] & 15) << 4 | v6Bytes[6] & 15, v6Bytes[7], (v6Bytes[1] & 15) << 4 | (v6Bytes[2] & 240) >> 4, (v6Bytes[2] & 15) << 4 | (v6Bytes[3] & 240) >> 4, 16 | (v6Bytes[0] & 240) >> 4, (v6Bytes[0] & 15) << 4 | (v6Bytes[1] & 240) >> 4, v6Bytes[8], v6Bytes[9], v6Bytes[10], v6Bytes[11], v6Bytes[12], v6Bytes[13], v6Bytes[14], v6Bytes[15]);
417
+ }
418
+ var init_v6ToV1 = __esm({
419
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v6ToV1.js"() {
420
+ init_parse();
421
+ init_stringify();
422
+ }
423
+ });
424
+
425
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v7.js
426
+ function v7(options, buf, offset) {
427
+ options = options || {};
428
+ let i = buf && offset || 0;
429
+ const b = buf || new Uint8Array(16);
430
+ const rnds = options.random || (options.rng || rng)();
431
+ const msecs = options.msecs !== void 0 ? options.msecs : Date.now();
432
+ let seq = options.seq !== void 0 ? options.seq : null;
433
+ let seqHigh = _seqHigh;
434
+ let seqLow = _seqLow;
435
+ if (msecs > _msecs && options.msecs === void 0) {
436
+ _msecs = msecs;
437
+ if (seq !== null) {
438
+ seqHigh = null;
439
+ seqLow = null;
440
+ }
441
+ }
442
+ if (seq !== null) {
443
+ if (seq > 2147483647) {
444
+ seq = 2147483647;
445
+ }
446
+ seqHigh = seq >>> 19 & 4095;
447
+ seqLow = seq & 524287;
448
+ }
449
+ if (seqHigh === null || seqLow === null) {
450
+ seqHigh = rnds[6] & 127;
451
+ seqHigh = seqHigh << 8 | rnds[7];
452
+ seqLow = rnds[8] & 63;
453
+ seqLow = seqLow << 8 | rnds[9];
454
+ seqLow = seqLow << 5 | rnds[10] >>> 3;
455
+ }
456
+ if (msecs + 1e4 > _msecs && seq === null) {
457
+ if (++seqLow > 524287) {
458
+ seqLow = 0;
459
+ if (++seqHigh > 4095) {
460
+ seqHigh = 0;
461
+ _msecs++;
462
+ }
463
+ }
464
+ } else {
465
+ _msecs = msecs;
466
+ }
467
+ _seqHigh = seqHigh;
468
+ _seqLow = seqLow;
469
+ b[i++] = _msecs / 1099511627776 & 255;
470
+ b[i++] = _msecs / 4294967296 & 255;
471
+ b[i++] = _msecs / 16777216 & 255;
472
+ b[i++] = _msecs / 65536 & 255;
473
+ b[i++] = _msecs / 256 & 255;
474
+ b[i++] = _msecs & 255;
475
+ b[i++] = seqHigh >>> 4 & 15 | 112;
476
+ b[i++] = seqHigh & 255;
477
+ b[i++] = seqLow >>> 13 & 63 | 128;
478
+ b[i++] = seqLow >>> 5 & 255;
479
+ b[i++] = seqLow << 3 & 255 | rnds[10] & 7;
480
+ b[i++] = rnds[11];
481
+ b[i++] = rnds[12];
482
+ b[i++] = rnds[13];
483
+ b[i++] = rnds[14];
484
+ b[i++] = rnds[15];
485
+ return buf || unsafeStringify(b);
486
+ }
487
+ var _seqLow, _seqHigh, _msecs, v7_default;
488
+ var init_v7 = __esm({
489
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/v7.js"() {
490
+ init_rng();
491
+ init_stringify();
492
+ _seqLow = null;
493
+ _seqHigh = null;
494
+ _msecs = 0;
495
+ v7_default = v7;
496
+ }
497
+ });
498
+
499
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/version.js
500
+ function version(uuid) {
501
+ if (!validate_default(uuid)) {
502
+ throw TypeError("Invalid UUID");
503
+ }
504
+ return parseInt(uuid.slice(14, 15), 16);
505
+ }
506
+ var version_default;
507
+ var init_version = __esm({
508
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/version.js"() {
509
+ init_validate();
510
+ version_default = version;
511
+ }
512
+ });
513
+
514
+ // ../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/index.js
515
+ var esm_node_exports = {};
516
+ __export(esm_node_exports, {
517
+ MAX: () => max_default,
518
+ NIL: () => nil_default,
519
+ parse: () => parse_default,
520
+ stringify: () => stringify_default,
521
+ v1: () => v1_default,
522
+ v1ToV6: () => v1ToV6,
523
+ v3: () => v3_default,
524
+ v4: () => v4_default,
525
+ v5: () => v5_default,
526
+ v6: () => v6,
527
+ v6ToV1: () => v6ToV1,
528
+ v7: () => v7_default,
529
+ validate: () => validate_default,
530
+ version: () => version_default
531
+ });
532
+ var init_esm_node = __esm({
533
+ "../../node_modules/.pnpm/uuid@10.0.0/node_modules/uuid/dist/esm-node/index.js"() {
534
+ init_max();
535
+ init_nil();
536
+ init_parse();
537
+ init_stringify();
538
+ init_v1();
539
+ init_v1ToV6();
540
+ init_v3();
541
+ init_v4();
542
+ init_v5();
543
+ init_v6();
544
+ init_v6ToV1();
545
+ init_v7();
546
+ init_validate();
547
+ init_version();
548
+ }
549
+ });
550
+
551
+ // ../claude-code-web/src/claude-bridge.js
552
+ var require_claude_bridge = __commonJS({
553
+ "../claude-code-web/src/claude-bridge.js"(exports2, module2) {
554
+ "use strict";
555
+ var { spawn } = require("node-pty");
556
+ var path = require("path");
557
+ var fs = require("fs");
558
+ var ClaudeBridge = class {
559
+ constructor() {
560
+ this.sessions = /* @__PURE__ */ new Map();
561
+ this.claudeCommand = this.findClaudeCommand();
562
+ }
563
+ findClaudeCommand() {
564
+ const possibleCommands = [
565
+ "/home/ec2-user/.claude/local/claude",
566
+ "claude",
567
+ "claude-code",
568
+ path.join(process.env.HOME || "/", ".claude", "local", "claude"),
569
+ path.join(process.env.HOME || "/", ".local", "bin", "claude"),
570
+ "/usr/local/bin/claude",
571
+ "/usr/bin/claude"
572
+ ];
573
+ for (const cmd of possibleCommands) {
574
+ try {
575
+ if (fs.existsSync(cmd) || this.commandExists(cmd)) {
576
+ console.log(`Found Claude command at: ${cmd}`);
577
+ return cmd;
578
+ }
579
+ } catch (error) {
580
+ continue;
581
+ }
582
+ }
583
+ console.error('Claude command not found, using default "claude"');
584
+ return "claude";
585
+ }
586
+ commandExists(command) {
587
+ try {
588
+ require("child_process").execFileSync("which", [command], { stdio: "ignore" });
589
+ return true;
590
+ } catch (error) {
591
+ return false;
592
+ }
593
+ }
594
+ async startSession(sessionId, options = {}) {
595
+ if (this.sessions.has(sessionId)) {
596
+ throw new Error(`Session ${sessionId} already exists`);
597
+ }
598
+ const {
599
+ workingDir = process.cwd(),
600
+ dangerouslySkipPermissions = false,
601
+ permissionMode,
602
+ allowedTools,
603
+ disallowedTools,
604
+ appendSystemPrompt,
605
+ initialPrompt,
606
+ mcpConfig,
607
+ strictMcpConfig = false,
608
+ onOutput = () => {
609
+ },
610
+ onExit = () => {
611
+ },
612
+ onError = () => {
613
+ },
614
+ cols = 80,
615
+ rows = 24,
616
+ // When true, spawn `claude --resume <sessionId>` instead of starting a
617
+ // fresh session. The conversation history is read from the existing
618
+ // ~/.claude/projects/<flat-cwd>/<sessionId>.jsonl file. Mutually
619
+ // exclusive with the implicit --session-id flag added for fresh
620
+ // sessions (passing both would cause a CLI argument conflict).
621
+ resume = false
622
+ } = options;
623
+ try {
624
+ console.log(`Starting Claude session ${sessionId}`);
625
+ console.log(`Command: ${this.claudeCommand}`);
626
+ console.log(`Working directory: ${workingDir}`);
627
+ console.log(`Terminal size: ${cols}x${rows}`);
628
+ if (dangerouslySkipPermissions) {
629
+ console.log(`\u26A0\uFE0F WARNING: Skipping permissions with --dangerously-skip-permissions flag`);
630
+ }
631
+ if (appendSystemPrompt) {
632
+ console.log(`System prompt injected (${appendSystemPrompt.length} chars)`);
633
+ }
634
+ const args = [];
635
+ if (resume) {
636
+ args.push("--resume", sessionId);
637
+ } else {
638
+ args.push("--session-id", sessionId);
639
+ }
640
+ if (dangerouslySkipPermissions) args.push("--dangerously-skip-permissions");
641
+ if (permissionMode) args.push("--permission-mode", permissionMode);
642
+ if (Array.isArray(allowedTools) && allowedTools.length > 0) {
643
+ args.push("--allowedTools", allowedTools.join(","));
644
+ }
645
+ if (Array.isArray(disallowedTools) && disallowedTools.length > 0) {
646
+ args.push("--disallowedTools", disallowedTools.join(","));
647
+ }
648
+ if (mcpConfig) args.push("--mcp-config", mcpConfig);
649
+ if (strictMcpConfig) args.push("--strict-mcp-config");
650
+ if (appendSystemPrompt) args.push("--append-system-prompt", appendSystemPrompt);
651
+ if (initialPrompt) args.push(initialPrompt);
652
+ const claudeProcess = spawn(this.claudeCommand, args, {
653
+ cwd: workingDir,
654
+ env: {
655
+ ...process.env,
656
+ TERM: "xterm-256color",
657
+ FORCE_COLOR: "1",
658
+ COLORTERM: "truecolor"
659
+ },
660
+ cols,
661
+ rows,
662
+ name: "xterm-color"
663
+ });
664
+ const session = {
665
+ process: claudeProcess,
666
+ workingDir,
667
+ created: /* @__PURE__ */ new Date(),
668
+ active: true,
669
+ killTimeout: null
670
+ };
671
+ this.sessions.set(sessionId, session);
672
+ let trustPromptHandled = false;
673
+ let autoStartSent = !!initialPrompt;
674
+ let dataBuffer = "";
675
+ claudeProcess.onData((data) => {
676
+ if (process.env.DEBUG) {
677
+ console.log(`Session ${sessionId} output:`, data);
678
+ }
679
+ dataBuffer += data;
680
+ if (!trustPromptHandled && dataBuffer.includes("Do you trust the files in this folder?")) {
681
+ trustPromptHandled = true;
682
+ console.log(`Auto-accepting trust prompt for session ${sessionId}`);
683
+ setTimeout(() => {
684
+ claudeProcess.write("\r");
685
+ console.log(`Sent Enter to accept trust prompt for session ${sessionId}`);
686
+ }, 500);
687
+ }
688
+ if (!autoStartSent && appendSystemPrompt && dataBuffer.includes("\u276F")) {
689
+ autoStartSent = true;
690
+ console.log(`Auto-starting agent in session ${sessionId}`);
691
+ setTimeout(() => {
692
+ claudeProcess.write("Begin.\r");
693
+ }, 500);
694
+ }
695
+ if (dataBuffer.length > 1e4) {
696
+ dataBuffer = dataBuffer.slice(-5e3);
697
+ }
698
+ onOutput(data);
699
+ });
700
+ claudeProcess.onExit((exitCode, signal) => {
701
+ console.log(`Claude session ${sessionId} exited with code ${exitCode}, signal ${signal}`);
702
+ if (session.killTimeout) {
703
+ clearTimeout(session.killTimeout);
704
+ session.killTimeout = null;
705
+ }
706
+ session.active = false;
707
+ this.sessions.delete(sessionId);
708
+ onExit(exitCode, signal);
709
+ });
710
+ claudeProcess.on("error", (error) => {
711
+ console.error(`Claude session ${sessionId} error:`, error);
712
+ if (session.killTimeout) {
713
+ clearTimeout(session.killTimeout);
714
+ session.killTimeout = null;
715
+ }
716
+ session.active = false;
717
+ this.sessions.delete(sessionId);
718
+ onError(error);
719
+ });
720
+ console.log(`Claude session ${sessionId} started successfully`);
721
+ return session;
722
+ } catch (error) {
723
+ console.error(`Failed to start Claude session ${sessionId}:`, error);
724
+ throw new Error(`Failed to start Claude Code: ${error.message}`);
725
+ }
726
+ }
727
+ async sendInput(sessionId, data) {
728
+ const session = this.sessions.get(sessionId);
729
+ if (!session || !session.active) {
730
+ throw new Error(`Session ${sessionId} not found or not active`);
731
+ }
732
+ try {
733
+ session.process.write(data);
734
+ } catch (error) {
735
+ throw new Error(`Failed to send input to session ${sessionId}: ${error.message}`);
736
+ }
737
+ }
738
+ async resize(sessionId, cols, rows) {
739
+ const session = this.sessions.get(sessionId);
740
+ if (!session || !session.active) {
741
+ throw new Error(`Session ${sessionId} not found or not active`);
742
+ }
743
+ try {
744
+ session.process.resize(cols, rows);
745
+ } catch (error) {
746
+ console.warn(`Failed to resize session ${sessionId}:`, error.message);
747
+ }
748
+ }
749
+ async stopSession(sessionId) {
750
+ const session = this.sessions.get(sessionId);
751
+ if (!session) {
752
+ return;
753
+ }
754
+ try {
755
+ if (session.killTimeout) {
756
+ clearTimeout(session.killTimeout);
757
+ session.killTimeout = null;
758
+ }
759
+ if (session.active && session.process) {
760
+ session.process.kill("SIGTERM");
761
+ session.killTimeout = setTimeout(() => {
762
+ if (session.active && session.process) {
763
+ session.process.kill("SIGKILL");
764
+ }
765
+ }, 5e3);
766
+ }
767
+ } catch (error) {
768
+ console.warn(`Error stopping session ${sessionId}:`, error.message);
769
+ }
770
+ session.active = false;
771
+ this.sessions.delete(sessionId);
772
+ }
773
+ getSession(sessionId) {
774
+ return this.sessions.get(sessionId);
775
+ }
776
+ getAllSessions() {
777
+ return Array.from(this.sessions.entries()).map(([id, session]) => ({
778
+ id,
779
+ workingDir: session.workingDir,
780
+ created: session.created,
781
+ active: session.active
782
+ }));
783
+ }
784
+ async cleanup() {
785
+ const sessionIds = Array.from(this.sessions.keys());
786
+ for (const sessionId of sessionIds) {
787
+ await this.stopSession(sessionId);
788
+ }
789
+ }
790
+ };
791
+ module2.exports = ClaudeBridge;
792
+ }
793
+ });
794
+
795
+ // ../claude-code-web/src/codex-bridge.js
796
+ var require_codex_bridge = __commonJS({
797
+ "../claude-code-web/src/codex-bridge.js"(exports2, module2) {
798
+ "use strict";
799
+ var { spawn } = require("node-pty");
800
+ var path = require("path");
801
+ var fs = require("fs");
802
+ var CodexBridge = class {
803
+ constructor() {
804
+ this.sessions = /* @__PURE__ */ new Map();
805
+ this.codexCommand = this.findCodexCommand();
806
+ }
807
+ findCodexCommand() {
808
+ const possibleCommands = [
809
+ path.join(process.env.HOME || "/", ".codex", "local", "codex"),
810
+ "codex",
811
+ "codex-code",
812
+ path.join(process.env.HOME || "/", ".local", "bin", "codex"),
813
+ "/usr/local/bin/codex",
814
+ "/usr/bin/codex"
815
+ ];
816
+ for (const cmd of possibleCommands) {
817
+ try {
818
+ if (fs.existsSync(cmd) || this.commandExists(cmd)) {
819
+ console.log(`Found Codex command at: ${cmd}`);
820
+ return cmd;
821
+ }
822
+ } catch (error) {
823
+ continue;
824
+ }
825
+ }
826
+ console.error('Codex command not found, using default "codex"');
827
+ return "codex";
828
+ }
829
+ commandExists(command) {
830
+ try {
831
+ require("child_process").execFileSync("which", [command], { stdio: "ignore" });
832
+ return true;
833
+ } catch (error) {
834
+ return false;
835
+ }
836
+ }
837
+ async startSession(sessionId, options = {}) {
838
+ if (this.sessions.has(sessionId)) {
839
+ throw new Error(`Session ${sessionId} already exists`);
840
+ }
841
+ const {
842
+ workingDir = process.cwd(),
843
+ dangerouslySkipPermissions = false,
844
+ onOutput = () => {
845
+ },
846
+ onExit = () => {
847
+ },
848
+ onError = () => {
849
+ },
850
+ cols = 80,
851
+ rows = 24
852
+ } = options;
853
+ try {
854
+ console.log(`Starting Codex session ${sessionId}`);
855
+ console.log(`Command: ${this.codexCommand}`);
856
+ console.log(`Working directory: ${workingDir}`);
857
+ console.log(`Terminal size: ${cols}x${rows}`);
858
+ if (dangerouslySkipPermissions) {
859
+ console.log(`\u26A0\uFE0F WARNING: Bypassing approvals and sandbox with --dangerously-bypass-approvals-and-sandbox flag`);
860
+ }
861
+ const args = dangerouslySkipPermissions ? ["--dangerously-bypass-approvals-and-sandbox"] : [];
862
+ const codexProcess = spawn(this.codexCommand, args, {
863
+ cwd: workingDir,
864
+ env: {
865
+ ...process.env,
866
+ TERM: "xterm-256color",
867
+ FORCE_COLOR: "1",
868
+ COLORTERM: "truecolor"
869
+ },
870
+ cols,
871
+ rows,
872
+ name: "xterm-color"
873
+ });
874
+ const session = {
875
+ process: codexProcess,
876
+ workingDir,
877
+ created: /* @__PURE__ */ new Date(),
878
+ active: true,
879
+ killTimeout: null
880
+ };
881
+ this.sessions.set(sessionId, session);
882
+ let dataBuffer = "";
883
+ codexProcess.onData((data) => {
884
+ if (process.env.DEBUG) {
885
+ console.log(`Codex session ${sessionId} output:`, data);
886
+ }
887
+ dataBuffer += data;
888
+ if (dataBuffer.length > 1e4) {
889
+ dataBuffer = dataBuffer.slice(-5e3);
890
+ }
891
+ onOutput(data);
892
+ });
893
+ codexProcess.onExit((exitCode, signal) => {
894
+ console.log(`Codex session ${sessionId} exited with code ${exitCode}, signal ${signal}`);
895
+ if (session.killTimeout) {
896
+ clearTimeout(session.killTimeout);
897
+ session.killTimeout = null;
898
+ }
899
+ session.active = false;
900
+ this.sessions.delete(sessionId);
901
+ onExit(exitCode, signal);
902
+ });
903
+ codexProcess.on("error", (error) => {
904
+ console.error(`Codex session ${sessionId} error:`, error);
905
+ if (session.killTimeout) {
906
+ clearTimeout(session.killTimeout);
907
+ session.killTimeout = null;
908
+ }
909
+ session.active = false;
910
+ this.sessions.delete(sessionId);
911
+ onError(error);
912
+ });
913
+ console.log(`Codex session ${sessionId} started successfully`);
914
+ return session;
915
+ } catch (error) {
916
+ console.error(`Failed to start Codex session ${sessionId}:`, error);
917
+ throw new Error(`Failed to start Codex Code: ${error.message}`);
918
+ }
919
+ }
920
+ async sendInput(sessionId, data) {
921
+ const session = this.sessions.get(sessionId);
922
+ if (!session || !session.active) {
923
+ throw new Error(`Session ${sessionId} not found or not active`);
924
+ }
925
+ try {
926
+ session.process.write(data);
927
+ } catch (error) {
928
+ throw new Error(`Failed to send input to session ${sessionId}: ${error.message}`);
929
+ }
930
+ }
931
+ async resize(sessionId, cols, rows) {
932
+ const session = this.sessions.get(sessionId);
933
+ if (!session || !session.active) {
934
+ throw new Error(`Session ${sessionId} not found or not active`);
935
+ }
936
+ try {
937
+ session.process.resize(cols, rows);
938
+ } catch (error) {
939
+ console.warn(`Failed to resize session ${sessionId}:`, error.message);
940
+ }
941
+ }
942
+ async stopSession(sessionId) {
943
+ const session = this.sessions.get(sessionId);
944
+ if (!session) {
945
+ return;
946
+ }
947
+ try {
948
+ if (session.killTimeout) {
949
+ clearTimeout(session.killTimeout);
950
+ session.killTimeout = null;
951
+ }
952
+ if (session.active && session.process) {
953
+ session.process.kill("SIGTERM");
954
+ session.killTimeout = setTimeout(() => {
955
+ if (session.active && session.process) {
956
+ session.process.kill("SIGKILL");
957
+ }
958
+ }, 5e3);
959
+ }
960
+ } catch (error) {
961
+ console.warn(`Error stopping codex session ${sessionId}:`, error.message);
962
+ }
963
+ session.active = false;
964
+ this.sessions.delete(sessionId);
965
+ }
966
+ getSession(sessionId) {
967
+ return this.sessions.get(sessionId);
968
+ }
969
+ getAllSessions() {
970
+ return Array.from(this.sessions.entries()).map(([id, session]) => ({
971
+ id,
972
+ workingDir: session.workingDir,
973
+ created: session.created,
974
+ active: session.active
975
+ }));
976
+ }
977
+ async cleanup() {
978
+ const sessionIds = Array.from(this.sessions.keys());
979
+ for (const sessionId of sessionIds) {
980
+ await this.stopSession(sessionId);
981
+ }
982
+ }
983
+ };
984
+ module2.exports = CodexBridge;
985
+ }
986
+ });
987
+
988
+ // ../claude-code-web/src/agent-bridge.js
989
+ var require_agent_bridge = __commonJS({
990
+ "../claude-code-web/src/agent-bridge.js"(exports2, module2) {
991
+ "use strict";
992
+ var { spawn } = require("node-pty");
993
+ var path = require("path");
994
+ var fs = require("fs");
995
+ var AgentBridge = class {
996
+ constructor() {
997
+ this.sessions = /* @__PURE__ */ new Map();
998
+ this.agentCommand = this.findAgentCommand();
999
+ }
1000
+ findAgentCommand() {
1001
+ const possibleCommands = [
1002
+ path.join(process.env.HOME || "/", ".cursor", "local", "cursor-agent"),
1003
+ "cursor-agent",
1004
+ path.join(process.env.HOME || "/", ".local", "bin", "cursor-agent"),
1005
+ "/usr/local/bin/cursor-agent",
1006
+ "/usr/bin/cursor-agent"
1007
+ ];
1008
+ for (const cmd of possibleCommands) {
1009
+ try {
1010
+ if (fs.existsSync(cmd) || this.commandExists(cmd)) {
1011
+ console.log(`Found Agent command at: ${cmd}`);
1012
+ return cmd;
1013
+ }
1014
+ } catch (error) {
1015
+ continue;
1016
+ }
1017
+ }
1018
+ console.error('Agent command not found, using default "cursor-agent"');
1019
+ return "cursor-agent";
1020
+ }
1021
+ commandExists(command) {
1022
+ try {
1023
+ require("child_process").execFileSync("which", [command], { stdio: "ignore" });
1024
+ return true;
1025
+ } catch (error) {
1026
+ return false;
1027
+ }
1028
+ }
1029
+ async startSession(sessionId, options = {}) {
1030
+ if (this.sessions.has(sessionId)) {
1031
+ throw new Error(`Session ${sessionId} already exists`);
1032
+ }
1033
+ const {
1034
+ workingDir = process.cwd(),
1035
+ onOutput = () => {
1036
+ },
1037
+ onExit = () => {
1038
+ },
1039
+ onError = () => {
1040
+ },
1041
+ cols = 80,
1042
+ rows = 24
1043
+ } = options;
1044
+ try {
1045
+ console.log(`Starting Agent session ${sessionId}`);
1046
+ console.log(`Command: ${this.agentCommand}`);
1047
+ console.log(`Working directory: ${workingDir}`);
1048
+ console.log(`Terminal size: ${cols}x${rows}`);
1049
+ const agentProcess = spawn(this.agentCommand, [], {
1050
+ cwd: workingDir,
1051
+ env: {
1052
+ ...process.env,
1053
+ TERM: "xterm-256color",
1054
+ FORCE_COLOR: "1",
1055
+ COLORTERM: "truecolor"
1056
+ },
1057
+ cols,
1058
+ rows,
1059
+ name: "xterm-color"
1060
+ });
1061
+ const session = {
1062
+ process: agentProcess,
1063
+ workingDir,
1064
+ created: /* @__PURE__ */ new Date(),
1065
+ active: true,
1066
+ killTimeout: null
1067
+ };
1068
+ this.sessions.set(sessionId, session);
1069
+ let dataBuffer = "";
1070
+ agentProcess.onData((data) => {
1071
+ if (process.env.DEBUG) {
1072
+ console.log(`Agent session ${sessionId} output:`, data);
1073
+ }
1074
+ dataBuffer += data;
1075
+ if (dataBuffer.length > 1e4) {
1076
+ dataBuffer = dataBuffer.slice(-5e3);
1077
+ }
1078
+ onOutput(data);
1079
+ });
1080
+ agentProcess.onExit((exitCode, signal) => {
1081
+ console.log(`Agent session ${sessionId} exited with code ${exitCode}, signal ${signal}`);
1082
+ if (session.killTimeout) {
1083
+ clearTimeout(session.killTimeout);
1084
+ session.killTimeout = null;
1085
+ }
1086
+ session.active = false;
1087
+ this.sessions.delete(sessionId);
1088
+ onExit(exitCode, signal);
1089
+ });
1090
+ agentProcess.on("error", (error) => {
1091
+ console.error(`Agent session ${sessionId} error:`, error);
1092
+ if (session.killTimeout) {
1093
+ clearTimeout(session.killTimeout);
1094
+ session.killTimeout = null;
1095
+ }
1096
+ session.active = false;
1097
+ this.sessions.delete(sessionId);
1098
+ onError(error);
1099
+ });
1100
+ console.log(`Agent session ${sessionId} started successfully`);
1101
+ return session;
1102
+ } catch (error) {
1103
+ console.error(`Failed to start Agent session ${sessionId}:`, error);
1104
+ throw new Error(`Failed to start Agent: ${error.message}`);
1105
+ }
1106
+ }
1107
+ async sendInput(sessionId, data) {
1108
+ const session = this.sessions.get(sessionId);
1109
+ if (!session || !session.active) {
1110
+ throw new Error(`Session ${sessionId} not found or not active`);
1111
+ }
1112
+ try {
1113
+ session.process.write(data);
1114
+ } catch (error) {
1115
+ throw new Error(`Failed to send input to session ${sessionId}: ${error.message}`);
1116
+ }
1117
+ }
1118
+ async resize(sessionId, cols, rows) {
1119
+ const session = this.sessions.get(sessionId);
1120
+ if (!session || !session.active) {
1121
+ throw new Error(`Session ${sessionId} not found or not active`);
1122
+ }
1123
+ try {
1124
+ session.process.resize(cols, rows);
1125
+ } catch (error) {
1126
+ console.warn(`Failed to resize session ${sessionId}:`, error.message);
1127
+ }
1128
+ }
1129
+ async stopSession(sessionId) {
1130
+ const session = this.sessions.get(sessionId);
1131
+ if (!session) {
1132
+ return;
1133
+ }
1134
+ try {
1135
+ if (session.killTimeout) {
1136
+ clearTimeout(session.killTimeout);
1137
+ session.killTimeout = null;
1138
+ }
1139
+ if (session.active && session.process) {
1140
+ session.process.kill("SIGTERM");
1141
+ session.killTimeout = setTimeout(() => {
1142
+ if (session.active && session.process) {
1143
+ session.process.kill("SIGKILL");
1144
+ }
1145
+ }, 5e3);
1146
+ }
1147
+ } catch (error) {
1148
+ console.warn(`Error stopping agent session ${sessionId}:`, error.message);
1149
+ }
1150
+ session.active = false;
1151
+ this.sessions.delete(sessionId);
1152
+ }
1153
+ getSession(sessionId) {
1154
+ return this.sessions.get(sessionId);
1155
+ }
1156
+ getAllSessions() {
1157
+ return Array.from(this.sessions.entries()).map(([id, session]) => ({
1158
+ id,
1159
+ workingDir: session.workingDir,
1160
+ created: session.created,
1161
+ active: session.active
1162
+ }));
1163
+ }
1164
+ async cleanup() {
1165
+ const sessionIds = Array.from(this.sessions.keys());
1166
+ for (const sessionId of sessionIds) {
1167
+ await this.stopSession(sessionId);
1168
+ }
1169
+ }
1170
+ };
1171
+ module2.exports = AgentBridge;
1172
+ }
1173
+ });
1174
+
1175
+ // ../claude-code-web/src/script-bridge.js
1176
+ var require_script_bridge = __commonJS({
1177
+ "../claude-code-web/src/script-bridge.js"(exports2, module2) {
1178
+ "use strict";
1179
+ var { spawn } = require("node-pty");
1180
+ var ScriptBridge = class {
1181
+ constructor() {
1182
+ this.sessions = /* @__PURE__ */ new Map();
1183
+ }
1184
+ async startSession(sessionId, options = {}) {
1185
+ if (this.sessions.has(sessionId)) {
1186
+ throw new Error(`Script session ${sessionId} already exists`);
1187
+ }
1188
+ const {
1189
+ command,
1190
+ args = [],
1191
+ workingDir = process.cwd(),
1192
+ env = {},
1193
+ onOutput = () => {
1194
+ },
1195
+ onExit = () => {
1196
+ },
1197
+ onError = () => {
1198
+ },
1199
+ cols = 80,
1200
+ rows = 24
1201
+ } = options;
1202
+ if (!command) {
1203
+ throw new Error("ScriptBridge.startSession requires a command");
1204
+ }
1205
+ try {
1206
+ console.log(`Starting script session ${sessionId}: ${command} ${args.join(" ")}`);
1207
+ console.log(`Working directory: ${workingDir}`);
1208
+ const proc = spawn(command, args, {
1209
+ cwd: workingDir,
1210
+ env: {
1211
+ ...process.env,
1212
+ ...env,
1213
+ TERM: "xterm-256color",
1214
+ FORCE_COLOR: "1",
1215
+ COLORTERM: "truecolor"
1216
+ },
1217
+ cols,
1218
+ rows,
1219
+ name: "xterm-color"
1220
+ });
1221
+ const session = {
1222
+ process: proc,
1223
+ command,
1224
+ workingDir,
1225
+ created: /* @__PURE__ */ new Date(),
1226
+ active: true,
1227
+ killTimeout: null
1228
+ };
1229
+ this.sessions.set(sessionId, session);
1230
+ proc.onData((data) => {
1231
+ if (process.env.DEBUG) {
1232
+ console.log(`Script session ${sessionId} output:`, data);
1233
+ }
1234
+ onOutput(data);
1235
+ });
1236
+ proc.onExit((exitCode, signal) => {
1237
+ console.log(`Script session ${sessionId} exited with code ${exitCode}, signal ${signal}`);
1238
+ if (session.killTimeout) {
1239
+ clearTimeout(session.killTimeout);
1240
+ session.killTimeout = null;
1241
+ }
1242
+ session.active = false;
1243
+ this.sessions.delete(sessionId);
1244
+ onExit(exitCode, signal);
1245
+ });
1246
+ proc.on("error", (error) => {
1247
+ console.error(`Script session ${sessionId} error:`, error);
1248
+ if (session.killTimeout) {
1249
+ clearTimeout(session.killTimeout);
1250
+ session.killTimeout = null;
1251
+ }
1252
+ session.active = false;
1253
+ this.sessions.delete(sessionId);
1254
+ onError(error);
1255
+ });
1256
+ console.log(`Script session ${sessionId} started successfully`);
1257
+ return session;
1258
+ } catch (error) {
1259
+ console.error(`Failed to start script session ${sessionId}:`, error);
1260
+ throw new Error(`Failed to start script: ${error.message}`);
1261
+ }
1262
+ }
1263
+ async sendInput(sessionId, data) {
1264
+ const session = this.sessions.get(sessionId);
1265
+ if (!session || !session.active) {
1266
+ throw new Error(`Script session ${sessionId} not found or not active`);
1267
+ }
1268
+ try {
1269
+ session.process.write(data);
1270
+ } catch (error) {
1271
+ throw new Error(`Failed to send input to script session ${sessionId}: ${error.message}`);
1272
+ }
1273
+ }
1274
+ async resize(sessionId, cols, rows) {
1275
+ const session = this.sessions.get(sessionId);
1276
+ if (!session || !session.active) {
1277
+ throw new Error(`Script session ${sessionId} not found or not active`);
1278
+ }
1279
+ try {
1280
+ session.process.resize(cols, rows);
1281
+ } catch (error) {
1282
+ console.warn(`Failed to resize script session ${sessionId}:`, error.message);
1283
+ }
1284
+ }
1285
+ async stopSession(sessionId) {
1286
+ const session = this.sessions.get(sessionId);
1287
+ if (!session) return;
1288
+ try {
1289
+ if (session.killTimeout) {
1290
+ clearTimeout(session.killTimeout);
1291
+ session.killTimeout = null;
1292
+ }
1293
+ if (session.active && session.process) {
1294
+ session.process.kill("SIGTERM");
1295
+ session.killTimeout = setTimeout(() => {
1296
+ if (session.active && session.process) {
1297
+ session.process.kill("SIGKILL");
1298
+ }
1299
+ }, 5e3);
1300
+ }
1301
+ } catch (error) {
1302
+ console.warn(`Error stopping script session ${sessionId}:`, error.message);
1303
+ }
1304
+ session.active = false;
1305
+ this.sessions.delete(sessionId);
1306
+ }
1307
+ getSession(sessionId) {
1308
+ return this.sessions.get(sessionId);
1309
+ }
1310
+ getAllSessions() {
1311
+ return Array.from(this.sessions.entries()).map(([id, session]) => ({
1312
+ id,
1313
+ command: session.command,
1314
+ workingDir: session.workingDir,
1315
+ created: session.created,
1316
+ active: session.active
1317
+ }));
1318
+ }
1319
+ async cleanup() {
1320
+ const sessionIds = Array.from(this.sessions.keys());
1321
+ for (const sessionId of sessionIds) {
1322
+ await this.stopSession(sessionId);
1323
+ }
1324
+ }
1325
+ };
1326
+ module2.exports = ScriptBridge;
1327
+ }
1328
+ });
1329
+
1330
+ // ../claude-code-web/src/utils/session-store.js
1331
+ var require_session_store = __commonJS({
1332
+ "../claude-code-web/src/utils/session-store.js"(exports2, module2) {
1333
+ "use strict";
1334
+ var fs = require("fs").promises;
1335
+ var path = require("path");
1336
+ var os = require("os");
1337
+ var SessionStore = class {
1338
+ constructor(options = {}) {
1339
+ this.storageDir = options.storageDir || path.join(os.homedir(), ".claude-code-web");
1340
+ this.sessionsFile = path.join(this.storageDir, "sessions.json");
1341
+ this.initializeStorage();
1342
+ }
1343
+ async initializeStorage() {
1344
+ try {
1345
+ await fs.mkdir(this.storageDir, { recursive: true });
1346
+ } catch (error) {
1347
+ console.error("Failed to create storage directory:", error);
1348
+ }
1349
+ }
1350
+ async saveSessions(sessions) {
1351
+ try {
1352
+ await fs.mkdir(this.storageDir, { recursive: true });
1353
+ const sessionsArray = Array.from(sessions.entries()).map(([id, session]) => ({
1354
+ id,
1355
+ name: session.name || "Unnamed Session",
1356
+ created: session.created || /* @__PURE__ */ new Date(),
1357
+ lastActivity: session.lastActivity || /* @__PURE__ */ new Date(),
1358
+ workingDir: session.workingDir || process.cwd(),
1359
+ active: false,
1360
+ // Always set to false when saving (processes won't persist)
1361
+ outputBuffer: Array.isArray(session.outputBuffer) ? session.outputBuffer.slice(-100) : [],
1362
+ // Keep last 100 lines
1363
+ connections: [],
1364
+ // Clear connections (they won't persist)
1365
+ lastAccessed: session.lastAccessed || Date.now(),
1366
+ // Session-specific usage tracking
1367
+ sessionStartTime: session.sessionStartTime || null,
1368
+ sessionUsage: session.sessionUsage || {
1369
+ requests: 0,
1370
+ inputTokens: 0,
1371
+ outputTokens: 0,
1372
+ cacheTokens: 0,
1373
+ totalCost: 0,
1374
+ models: {}
1375
+ }
1376
+ }));
1377
+ const data = {
1378
+ version: "1.0",
1379
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
1380
+ sessions: sessionsArray
1381
+ };
1382
+ const tempFile = `${this.sessionsFile}.tmp`;
1383
+ await fs.writeFile(tempFile, JSON.stringify(data, null, 2));
1384
+ await fs.mkdir(this.storageDir, { recursive: true });
1385
+ await fs.rename(tempFile, this.sessionsFile);
1386
+ return true;
1387
+ } catch (error) {
1388
+ console.error("Failed to save sessions:", error.message);
1389
+ return false;
1390
+ }
1391
+ }
1392
+ async loadSessions() {
1393
+ try {
1394
+ await fs.access(this.sessionsFile);
1395
+ const data = await fs.readFile(this.sessionsFile, "utf8");
1396
+ if (!data || !data.trim()) {
1397
+ console.log("Sessions file is empty, starting fresh");
1398
+ return /* @__PURE__ */ new Map();
1399
+ }
1400
+ let parsed;
1401
+ try {
1402
+ parsed = JSON.parse(data);
1403
+ } catch (parseError) {
1404
+ console.error("Sessions file is corrupted, starting fresh:", parseError.message);
1405
+ try {
1406
+ await fs.rename(this.sessionsFile, `${this.sessionsFile}.corrupted.${Date.now()}`);
1407
+ } catch (renameError) {
1408
+ }
1409
+ return /* @__PURE__ */ new Map();
1410
+ }
1411
+ if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.sessions)) {
1412
+ console.log("Invalid sessions file format, starting fresh");
1413
+ return /* @__PURE__ */ new Map();
1414
+ }
1415
+ if (parsed.savedAt) {
1416
+ const savedAt = new Date(parsed.savedAt);
1417
+ const now = /* @__PURE__ */ new Date();
1418
+ const daysSinceSave = (now - savedAt) / (1e3 * 60 * 60 * 24);
1419
+ if (daysSinceSave > 7) {
1420
+ console.log("Sessions are too old, starting fresh");
1421
+ return /* @__PURE__ */ new Map();
1422
+ }
1423
+ }
1424
+ const sessions = /* @__PURE__ */ new Map();
1425
+ for (const session of parsed.sessions) {
1426
+ if (!session || !session.id) continue;
1427
+ sessions.set(session.id, {
1428
+ ...session,
1429
+ created: session.created ? new Date(session.created) : /* @__PURE__ */ new Date(),
1430
+ lastActivity: session.lastActivity ? new Date(session.lastActivity) : /* @__PURE__ */ new Date(),
1431
+ active: false,
1432
+ connections: /* @__PURE__ */ new Set(),
1433
+ outputBuffer: session.outputBuffer || [],
1434
+ maxBufferSize: 1e3,
1435
+ // Restore usage data if available
1436
+ usageData: session.usageData || null
1437
+ });
1438
+ }
1439
+ console.log(`Restored ${sessions.size} sessions from disk`);
1440
+ return sessions;
1441
+ } catch (error) {
1442
+ if (error.code !== "ENOENT") {
1443
+ console.error("Failed to load sessions:", error.message);
1444
+ }
1445
+ return /* @__PURE__ */ new Map();
1446
+ }
1447
+ }
1448
+ async clearOldSessions() {
1449
+ try {
1450
+ await fs.unlink(this.sessionsFile);
1451
+ console.log("Cleared old sessions");
1452
+ return true;
1453
+ } catch (error) {
1454
+ if (error.code !== "ENOENT") {
1455
+ console.error("Failed to clear sessions:", error);
1456
+ }
1457
+ return false;
1458
+ }
1459
+ }
1460
+ async getSessionMetadata() {
1461
+ try {
1462
+ await fs.access(this.sessionsFile);
1463
+ const stats = await fs.stat(this.sessionsFile);
1464
+ const data = await fs.readFile(this.sessionsFile, "utf8");
1465
+ const parsed = JSON.parse(data);
1466
+ return {
1467
+ exists: true,
1468
+ savedAt: parsed.savedAt,
1469
+ sessionCount: parsed.sessions ? parsed.sessions.length : 0,
1470
+ fileSize: stats.size,
1471
+ version: parsed.version
1472
+ };
1473
+ } catch (error) {
1474
+ return {
1475
+ exists: false,
1476
+ error: error.message
1477
+ };
1478
+ }
1479
+ }
1480
+ };
1481
+ module2.exports = SessionStore;
1482
+ }
1483
+ });
1484
+
1485
+ // ../claude-code-web/src/usage-reader.js
1486
+ var require_usage_reader = __commonJS({
1487
+ "../claude-code-web/src/usage-reader.js"(exports2, module2) {
1488
+ "use strict";
1489
+ var fs = require("fs").promises;
1490
+ var path = require("path");
1491
+ var readline = require("readline");
1492
+ var { createReadStream } = require("fs");
1493
+ var UsageReader = class {
1494
+ constructor(sessionDurationHours = 5) {
1495
+ this.claudeProjectsPath = path.join(process.env.HOME, ".claude", "projects");
1496
+ this.cache = null;
1497
+ this.cacheTime = null;
1498
+ this.cacheTimeout = 5e3;
1499
+ this.sessionDurationHours = sessionDurationHours;
1500
+ this.overlappingSessions = [];
1501
+ }
1502
+ /**
1503
+ * Normalize model names for consistent categorization
1504
+ */
1505
+ normalizeModelName(model) {
1506
+ if (!model || typeof model !== "string") {
1507
+ return "unknown";
1508
+ }
1509
+ const modelLower = model.toLowerCase();
1510
+ if (modelLower.includes("opus")) {
1511
+ return "opus";
1512
+ } else if (modelLower.includes("sonnet")) {
1513
+ return "sonnet";
1514
+ } else if (modelLower.includes("haiku")) {
1515
+ return "haiku";
1516
+ }
1517
+ return "unknown";
1518
+ }
1519
+ /**
1520
+ * Create unique hash for deduplication based on message_id and request_id
1521
+ */
1522
+ createUniqueHash(entry) {
1523
+ const messageId = entry.message_id || entry.messageId || entry.message && entry.message.id || null;
1524
+ const requestId = entry.request_id || entry.requestId || null;
1525
+ if (messageId && requestId) {
1526
+ return `${messageId}:${requestId}`;
1527
+ }
1528
+ return null;
1529
+ }
1530
+ async getUsageStats(hoursBack = 24) {
1531
+ if (this.cache && this.cacheTime && Date.now() - this.cacheTime < this.cacheTimeout) {
1532
+ return this.cache;
1533
+ }
1534
+ try {
1535
+ const cutoffTime = new Date(Date.now() - hoursBack * 60 * 60 * 1e3);
1536
+ const entries = await this.readAllEntries(cutoffTime);
1537
+ const stats = this.calculateStats(entries, hoursBack);
1538
+ this.cache = stats;
1539
+ this.cacheTime = Date.now();
1540
+ return stats;
1541
+ } catch (error) {
1542
+ console.error("Error reading usage stats:", error);
1543
+ return null;
1544
+ }
1545
+ }
1546
+ async getCurrentSessionStats() {
1547
+ try {
1548
+ const currentSession = await this.getCurrentSession();
1549
+ if (!currentSession) {
1550
+ return null;
1551
+ }
1552
+ const startOfDay = this.getStartOfCurrentDay();
1553
+ const allTodayEntries = await this.readAllEntries(startOfDay);
1554
+ if (allTodayEntries.length === 0) {
1555
+ return null;
1556
+ }
1557
+ const sessionEntries = allTodayEntries.filter((entry) => {
1558
+ const entryTime = new Date(entry.timestamp);
1559
+ return entryTime >= currentSession.startTime && entryTime <= currentSession.endTime;
1560
+ });
1561
+ sessionEntries.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
1562
+ const stats = {
1563
+ requests: 0,
1564
+ inputTokens: 0,
1565
+ outputTokens: 0,
1566
+ cacheCreationTokens: 0,
1567
+ cacheReadTokens: 0,
1568
+ cacheTokens: 0,
1569
+ totalTokens: 0,
1570
+ totalCost: 0,
1571
+ models: {},
1572
+ sessionStartTime: currentSession.startTime.toISOString(),
1573
+ lastUpdate: null,
1574
+ sessionId: currentSession.sessionId,
1575
+ sessionNumber: currentSession.sessionNumber,
1576
+ // Add session number
1577
+ isExpired: /* @__PURE__ */ new Date() > currentSession.endTime,
1578
+ remainingTokens: null
1579
+ };
1580
+ for (const entry of sessionEntries) {
1581
+ stats.requests++;
1582
+ stats.inputTokens += entry.inputTokens;
1583
+ stats.outputTokens += entry.outputTokens;
1584
+ stats.cacheCreationTokens += entry.cacheCreationTokens;
1585
+ stats.cacheReadTokens += entry.cacheReadTokens;
1586
+ stats.totalCost += entry.totalCost;
1587
+ stats.lastUpdate = entry.timestamp;
1588
+ const model = entry.model || "unknown";
1589
+ if (!stats.models[model]) {
1590
+ stats.models[model] = {
1591
+ requests: 0,
1592
+ inputTokens: 0,
1593
+ outputTokens: 0,
1594
+ cost: 0
1595
+ };
1596
+ }
1597
+ stats.models[model].requests++;
1598
+ stats.models[model].inputTokens += entry.inputTokens;
1599
+ stats.models[model].outputTokens += entry.outputTokens;
1600
+ stats.models[model].cost += entry.totalCost;
1601
+ }
1602
+ stats.cacheTokens = stats.cacheCreationTokens + stats.cacheReadTokens;
1603
+ stats.totalTokens = stats.inputTokens + stats.outputTokens;
1604
+ return stats;
1605
+ } catch (error) {
1606
+ console.error("Error reading current session stats:", error);
1607
+ return null;
1608
+ }
1609
+ }
1610
+ async getAllTimeUsageStats() {
1611
+ try {
1612
+ const entries = await this.readAllEntries(/* @__PURE__ */ new Date(0));
1613
+ const stats = {
1614
+ requests: 0,
1615
+ inputTokens: 0,
1616
+ outputTokens: 0,
1617
+ cacheCreationTokens: 0,
1618
+ cacheReadTokens: 0,
1619
+ cacheTokens: 0,
1620
+ totalTokens: 0,
1621
+ totalCost: 0,
1622
+ models: {},
1623
+ firstRequest: null,
1624
+ lastRequest: null
1625
+ };
1626
+ for (const entry of entries) {
1627
+ stats.requests++;
1628
+ stats.inputTokens += entry.inputTokens;
1629
+ stats.outputTokens += entry.outputTokens;
1630
+ stats.cacheCreationTokens += entry.cacheCreationTokens;
1631
+ stats.cacheReadTokens += entry.cacheReadTokens;
1632
+ stats.totalCost += entry.totalCost;
1633
+ if (!stats.firstRequest || new Date(entry.timestamp) < new Date(stats.firstRequest)) {
1634
+ stats.firstRequest = entry.timestamp;
1635
+ }
1636
+ if (!stats.lastRequest || new Date(entry.timestamp) > new Date(stats.lastRequest)) {
1637
+ stats.lastRequest = entry.timestamp;
1638
+ }
1639
+ const model = entry.model || "unknown";
1640
+ if (!stats.models[model]) {
1641
+ stats.models[model] = {
1642
+ requests: 0,
1643
+ inputTokens: 0,
1644
+ outputTokens: 0,
1645
+ cost: 0
1646
+ };
1647
+ }
1648
+ stats.models[model].requests++;
1649
+ stats.models[model].inputTokens += entry.inputTokens;
1650
+ stats.models[model].outputTokens += entry.outputTokens;
1651
+ stats.models[model].cost += entry.totalCost;
1652
+ }
1653
+ stats.cacheTokens = stats.cacheCreationTokens + stats.cacheReadTokens;
1654
+ stats.totalTokens = stats.inputTokens + stats.outputTokens;
1655
+ return stats;
1656
+ } catch (error) {
1657
+ console.error("Error reading all-time usage stats:", error);
1658
+ return null;
1659
+ }
1660
+ }
1661
+ async readAllEntries(cutoffTime) {
1662
+ const entries = [];
1663
+ try {
1664
+ const files = await this.findJsonlFiles();
1665
+ for (const file of files) {
1666
+ const fileEntries = await this.readJsonlFile(file, cutoffTime);
1667
+ entries.push(...fileEntries);
1668
+ }
1669
+ entries.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
1670
+ return entries;
1671
+ } catch (error) {
1672
+ console.error("Error reading entries:", error);
1673
+ return [];
1674
+ }
1675
+ }
1676
+ async readRecentEntries(cutoffTime) {
1677
+ const entries = [];
1678
+ try {
1679
+ const files = await this.findJsonlFiles(true);
1680
+ for (const file of files) {
1681
+ const fileEntries = await this.readJsonlFile(file, cutoffTime);
1682
+ entries.push(...fileEntries);
1683
+ }
1684
+ entries.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
1685
+ return entries;
1686
+ } catch (error) {
1687
+ console.error("Error reading recent entries:", error);
1688
+ return [];
1689
+ }
1690
+ }
1691
+ async getMostRecentSessionFile() {
1692
+ try {
1693
+ const cwd = process.cwd();
1694
+ const projectDirName = cwd.replace(/\//g, "-");
1695
+ let projectPath = path.join(this.claudeProjectsPath, projectDirName);
1696
+ try {
1697
+ await fs.access(projectPath);
1698
+ } catch (err) {
1699
+ console.log(`Project directory not found: ${projectPath}`);
1700
+ return null;
1701
+ }
1702
+ const files = await fs.readdir(projectPath);
1703
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
1704
+ if (jsonlFiles.length === 0) {
1705
+ return null;
1706
+ }
1707
+ let mostRecentFile = null;
1708
+ let mostRecentTime = 0;
1709
+ for (const file of jsonlFiles) {
1710
+ const filePath = path.join(projectPath, file);
1711
+ const stat = await fs.stat(filePath);
1712
+ if (stat.mtime.getTime() > mostRecentTime) {
1713
+ mostRecentTime = stat.mtime.getTime();
1714
+ mostRecentFile = filePath;
1715
+ }
1716
+ }
1717
+ return mostRecentFile;
1718
+ } catch (error) {
1719
+ console.error("Error finding most recent session file:", error);
1720
+ return null;
1721
+ }
1722
+ }
1723
+ async findJsonlFiles(onlyRecent = false) {
1724
+ const files = [];
1725
+ try {
1726
+ const projectDirs = await fs.readdir(this.claudeProjectsPath);
1727
+ for (const projectDir of projectDirs) {
1728
+ const projectPath = path.join(this.claudeProjectsPath, projectDir);
1729
+ const stat = await fs.stat(projectPath);
1730
+ if (stat.isDirectory()) {
1731
+ const projectFiles = await fs.readdir(projectPath);
1732
+ const jsonlFiles = projectFiles.filter((f) => f.endsWith(".jsonl"));
1733
+ for (const jsonlFile of jsonlFiles) {
1734
+ const filePath = path.join(projectPath, jsonlFile);
1735
+ if (onlyRecent) {
1736
+ const fileStat = await fs.stat(filePath);
1737
+ const hoursSinceModified = (Date.now() - fileStat.mtime.getTime()) / (1e3 * 60 * 60);
1738
+ if (hoursSinceModified <= 24) {
1739
+ files.push(filePath);
1740
+ }
1741
+ } else {
1742
+ files.push(filePath);
1743
+ }
1744
+ }
1745
+ }
1746
+ }
1747
+ } catch (error) {
1748
+ console.error("Error finding JSONL files:", error);
1749
+ }
1750
+ return files;
1751
+ }
1752
+ async readJsonlFile(filePath, cutoffTime) {
1753
+ const entries = [];
1754
+ const fileProcessedEntries = /* @__PURE__ */ new Set();
1755
+ return new Promise((resolve) => {
1756
+ const rl = readline.createInterface({
1757
+ input: createReadStream(filePath),
1758
+ crlfDelay: Infinity
1759
+ });
1760
+ rl.on("line", (line) => {
1761
+ try {
1762
+ const entry = JSON.parse(line);
1763
+ if (entry.timestamp && new Date(entry.timestamp) >= cutoffTime) {
1764
+ const uniqueHash = this.createUniqueHash(entry);
1765
+ if (uniqueHash && fileProcessedEntries.has(uniqueHash)) {
1766
+ return;
1767
+ }
1768
+ const usage = entry.usage || entry.message && entry.message.usage;
1769
+ const rawModel = entry.model || entry.message && entry.message.model || "unknown";
1770
+ const model = this.normalizeModelName(rawModel);
1771
+ if ((entry.type === "assistant" || entry.message && entry.message.role === "assistant") && usage) {
1772
+ const inputTokens = usage.input_tokens || 0;
1773
+ const outputTokens = usage.output_tokens || 0;
1774
+ const cacheCreationTokens = usage.cache_creation_input_tokens || 0;
1775
+ const cacheReadTokens = usage.cache_read_input_tokens || 0;
1776
+ let totalCost = 0;
1777
+ if (model === "opus") {
1778
+ totalCost = inputTokens * 15e-6 + outputTokens * 75e-6;
1779
+ totalCost += cacheCreationTokens * 15e-6 + cacheReadTokens * 15e-7;
1780
+ } else if (model === "sonnet") {
1781
+ totalCost = inputTokens * 3e-6 + outputTokens * 15e-6;
1782
+ totalCost += cacheCreationTokens * 3e-6 + cacheReadTokens * 3e-7;
1783
+ } else if (model === "haiku") {
1784
+ totalCost = inputTokens * 25e-8 + outputTokens * 125e-8;
1785
+ totalCost += cacheCreationTokens * 25e-8 + cacheReadTokens * 25e-9;
1786
+ }
1787
+ let finalCost = totalCost;
1788
+ if (usage.total_cost !== void 0) {
1789
+ finalCost = usage.total_cost > 1 ? usage.total_cost / 100 : usage.total_cost;
1790
+ }
1791
+ const processedEntry = {
1792
+ timestamp: entry.timestamp,
1793
+ model,
1794
+ inputTokens,
1795
+ outputTokens,
1796
+ cacheCreationTokens,
1797
+ cacheReadTokens,
1798
+ totalCost: finalCost,
1799
+ sessionId: entry.sessionId,
1800
+ messageId: entry.message_id || entry.messageId || entry.message && entry.message.id || null,
1801
+ requestId: entry.request_id || entry.requestId || null
1802
+ };
1803
+ entries.push(processedEntry);
1804
+ if (uniqueHash) {
1805
+ fileProcessedEntries.add(uniqueHash);
1806
+ }
1807
+ }
1808
+ }
1809
+ } catch (e) {
1810
+ }
1811
+ });
1812
+ rl.on("close", () => {
1813
+ resolve(entries);
1814
+ });
1815
+ rl.on("error", (error) => {
1816
+ console.error("Error reading file:", filePath, error);
1817
+ resolve(entries);
1818
+ });
1819
+ });
1820
+ }
1821
+ calculateStats(entries, hoursBack) {
1822
+ if (!entries || entries.length === 0) {
1823
+ return {
1824
+ requests: 0,
1825
+ totalTokens: 0,
1826
+ inputTokens: 0,
1827
+ outputTokens: 0,
1828
+ cacheTokens: 0,
1829
+ totalCost: 0,
1830
+ periodHours: hoursBack,
1831
+ firstEntry: null,
1832
+ lastEntry: null,
1833
+ models: {},
1834
+ hourlyRate: 0,
1835
+ projectedDaily: 0
1836
+ };
1837
+ }
1838
+ const stats = {
1839
+ requests: entries.length,
1840
+ totalTokens: 0,
1841
+ inputTokens: 0,
1842
+ outputTokens: 0,
1843
+ cacheCreationTokens: 0,
1844
+ cacheReadTokens: 0,
1845
+ cacheTokens: 0,
1846
+ // Combined cache tokens for display
1847
+ totalCost: 0,
1848
+ periodHours: hoursBack,
1849
+ firstEntry: entries[0].timestamp,
1850
+ lastEntry: entries[entries.length - 1].timestamp,
1851
+ models: {},
1852
+ hourlyRate: 0,
1853
+ projectedDaily: 0
1854
+ };
1855
+ for (const entry of entries) {
1856
+ stats.inputTokens += entry.inputTokens;
1857
+ stats.outputTokens += entry.outputTokens;
1858
+ stats.cacheCreationTokens += entry.cacheCreationTokens;
1859
+ stats.cacheReadTokens += entry.cacheReadTokens;
1860
+ stats.totalCost += entry.totalCost;
1861
+ if (!stats.models[entry.model]) {
1862
+ stats.models[entry.model] = {
1863
+ requests: 0,
1864
+ inputTokens: 0,
1865
+ outputTokens: 0,
1866
+ cost: 0
1867
+ };
1868
+ }
1869
+ stats.models[entry.model].requests++;
1870
+ stats.models[entry.model].inputTokens += entry.inputTokens;
1871
+ stats.models[entry.model].outputTokens += entry.outputTokens;
1872
+ stats.models[entry.model].cost += entry.totalCost;
1873
+ }
1874
+ stats.cacheTokens = stats.cacheCreationTokens + stats.cacheReadTokens;
1875
+ stats.totalTokens = stats.inputTokens + stats.outputTokens;
1876
+ if (entries.length > 0) {
1877
+ const actualHours = (new Date(stats.lastEntry) - new Date(stats.firstEntry)) / (1e3 * 60 * 60);
1878
+ if (actualHours > 0) {
1879
+ stats.hourlyRate = stats.requests / actualHours;
1880
+ stats.projectedDaily = stats.hourlyRate * 24;
1881
+ stats.tokensPerHour = stats.totalTokens / actualHours;
1882
+ stats.costPerHour = stats.totalCost / actualHours;
1883
+ }
1884
+ }
1885
+ const estimatedDailyLimit = 100;
1886
+ const estimatedTokenLimit = 1e6;
1887
+ stats.requestPercentage = stats.projectedDaily / estimatedDailyLimit * 100;
1888
+ stats.tokenPercentage = stats.tokensPerHour * 24 / estimatedTokenLimit * 100;
1889
+ return stats;
1890
+ }
1891
+ // Get usage for a specific Claude session ID
1892
+ async getSessionUsageById(sessionId) {
1893
+ try {
1894
+ if (!sessionId) {
1895
+ return null;
1896
+ }
1897
+ const sessionFile = path.join(this.claudeProjectsPath, path.basename(process.cwd()).replace(/[^a-zA-Z0-9-]/g, "-"), `${sessionId}.jsonl`);
1898
+ try {
1899
+ await fs.access(sessionFile);
1900
+ } catch (err) {
1901
+ return null;
1902
+ }
1903
+ const entries = await this.readJsonlFile(sessionFile, /* @__PURE__ */ new Date(0));
1904
+ const sessionStats = {
1905
+ requests: 0,
1906
+ inputTokens: 0,
1907
+ outputTokens: 0,
1908
+ cacheCreationTokens: 0,
1909
+ cacheReadTokens: 0,
1910
+ cacheTokens: 0,
1911
+ totalCost: 0,
1912
+ models: {},
1913
+ sessionId,
1914
+ lastUpdate: null,
1915
+ firstRequestTime: null
1916
+ };
1917
+ for (const entry of entries) {
1918
+ sessionStats.requests++;
1919
+ sessionStats.inputTokens += entry.inputTokens;
1920
+ sessionStats.outputTokens += entry.outputTokens;
1921
+ sessionStats.cacheCreationTokens += entry.cacheCreationTokens;
1922
+ sessionStats.cacheReadTokens += entry.cacheReadTokens;
1923
+ sessionStats.totalCost += entry.totalCost;
1924
+ sessionStats.lastUpdate = entry.timestamp;
1925
+ if (!sessionStats.firstRequestTime) {
1926
+ sessionStats.firstRequestTime = entry.timestamp;
1927
+ }
1928
+ const model = entry.model || "unknown";
1929
+ if (!sessionStats.models[model]) {
1930
+ sessionStats.models[model] = {
1931
+ requests: 0,
1932
+ inputTokens: 0,
1933
+ outputTokens: 0,
1934
+ cost: 0
1935
+ };
1936
+ }
1937
+ sessionStats.models[model].requests++;
1938
+ sessionStats.models[model].inputTokens += entry.inputTokens;
1939
+ sessionStats.models[model].outputTokens += entry.outputTokens;
1940
+ sessionStats.models[model].cost += entry.totalCost;
1941
+ }
1942
+ sessionStats.cacheTokens = sessionStats.cacheCreationTokens + sessionStats.cacheReadTokens;
1943
+ sessionStats.totalTokens = sessionStats.inputTokens + sessionStats.outputTokens;
1944
+ return sessionStats;
1945
+ } catch (error) {
1946
+ console.error("Error getting session usage:", error);
1947
+ return null;
1948
+ }
1949
+ }
1950
+ // Legacy method - keeping for compatibility
1951
+ async getSessionUsage(sessionStartTime) {
1952
+ return null;
1953
+ }
1954
+ // Detect overlapping sessions within rolling windows
1955
+ async detectOverlappingSessions() {
1956
+ try {
1957
+ const now = /* @__PURE__ */ new Date();
1958
+ const lookbackHours = this.sessionDurationHours * 2;
1959
+ const cutoff = new Date(now - lookbackHours * 60 * 60 * 1e3);
1960
+ const entries = await this.readAllEntries(cutoff);
1961
+ if (entries.length === 0) return [];
1962
+ const sessions = [];
1963
+ let currentSession = null;
1964
+ for (const entry of entries) {
1965
+ if (!currentSession) {
1966
+ currentSession = {
1967
+ startTime: entry.timestamp,
1968
+ endTime: new Date(new Date(entry.timestamp).getTime() + this.sessionDurationHours * 60 * 60 * 1e3),
1969
+ entries: [entry],
1970
+ totalTokens: entry.inputTokens + entry.outputTokens,
1971
+ totalCost: entry.totalCost
1972
+ };
1973
+ } else {
1974
+ const timeSinceLastEntry = new Date(entry.timestamp) - new Date(currentSession.entries[currentSession.entries.length - 1].timestamp);
1975
+ const gapHours = timeSinceLastEntry / (1e3 * 60 * 60);
1976
+ if (gapHours < this.sessionDurationHours) {
1977
+ currentSession.entries.push(entry);
1978
+ currentSession.totalTokens += entry.inputTokens + entry.outputTokens;
1979
+ currentSession.totalCost += entry.totalCost;
1980
+ } else {
1981
+ sessions.push(currentSession);
1982
+ currentSession = {
1983
+ startTime: entry.timestamp,
1984
+ endTime: new Date(new Date(entry.timestamp).getTime() + this.sessionDurationHours * 60 * 60 * 1e3),
1985
+ entries: [entry],
1986
+ totalTokens: entry.inputTokens + entry.outputTokens,
1987
+ totalCost: entry.totalCost
1988
+ };
1989
+ }
1990
+ }
1991
+ }
1992
+ if (currentSession) {
1993
+ sessions.push(currentSession);
1994
+ }
1995
+ const overlapping = [];
1996
+ for (let i = 0; i < sessions.length; i++) {
1997
+ for (let j = i + 1; j < sessions.length; j++) {
1998
+ const session1 = sessions[i];
1999
+ const session2 = sessions[j];
2000
+ if (new Date(session1.startTime) < new Date(session2.endTime) && new Date(session2.startTime) < new Date(session1.endTime)) {
2001
+ overlapping.push({
2002
+ session1,
2003
+ session2,
2004
+ overlapStart: new Date(Math.max(new Date(session1.startTime), new Date(session2.startTime))),
2005
+ overlapEnd: new Date(Math.min(new Date(session1.endTime), new Date(session2.endTime)))
2006
+ });
2007
+ }
2008
+ }
2009
+ }
2010
+ this.overlappingSessions = overlapping;
2011
+ return sessions;
2012
+ } catch (error) {
2013
+ console.error("Error detecting overlapping sessions:", error);
2014
+ return [];
2015
+ }
2016
+ }
2017
+ // Generate a session ID from timestamp
2018
+ generateSessionId(timestamp) {
2019
+ return `session_${new Date(timestamp).getTime()}`;
2020
+ }
2021
+ // Calculate burn rate for a given time window
2022
+ async calculateBurnRate(minutes = 60) {
2023
+ try {
2024
+ const cutoff = new Date(Date.now() - minutes * 60 * 1e3);
2025
+ const entries = await this.readRecentEntries(cutoff);
2026
+ if (entries.length < 2) {
2027
+ return { rate: 0, confidence: 0 };
2028
+ }
2029
+ const totalTokens = entries.reduce((sum, e) => sum + e.inputTokens + e.outputTokens, 0);
2030
+ const duration = (new Date(entries[entries.length - 1].timestamp) - new Date(entries[0].timestamp)) / 1e3 / 60;
2031
+ if (duration === 0) {
2032
+ return { rate: 0, confidence: 0 };
2033
+ }
2034
+ const rate = totalTokens / duration;
2035
+ const confidence = Math.min(entries.length / 10, 1);
2036
+ return { rate, confidence, dataPoints: entries.length };
2037
+ } catch (error) {
2038
+ console.error("Error calculating burn rate:", error);
2039
+ return { rate: 0, confidence: 0 };
2040
+ }
2041
+ }
2042
+ // Get recent sessions for display
2043
+ async getRecentSessions(limit = 5) {
2044
+ try {
2045
+ const entries = await this.readAllEntries(new Date(Date.now() - 24 * 60 * 60 * 1e3));
2046
+ const sessions = {};
2047
+ for (const entry of entries) {
2048
+ const sessionId = entry.sessionId || "unknown";
2049
+ if (!sessions[sessionId]) {
2050
+ sessions[sessionId] = {
2051
+ sessionId,
2052
+ startTime: entry.timestamp,
2053
+ endTime: entry.timestamp,
2054
+ requests: 0,
2055
+ totalTokens: 0,
2056
+ cost: 0
2057
+ };
2058
+ }
2059
+ sessions[sessionId].endTime = entry.timestamp;
2060
+ sessions[sessionId].requests++;
2061
+ sessions[sessionId].totalTokens += entry.inputTokens + entry.outputTokens;
2062
+ sessions[sessionId].cost += entry.totalCost;
2063
+ }
2064
+ const sessionArray = Object.values(sessions);
2065
+ sessionArray.sort((a, b) => new Date(b.endTime) - new Date(a.endTime));
2066
+ return sessionArray.slice(0, limit);
2067
+ } catch (error) {
2068
+ console.error("Error getting recent sessions:", error);
2069
+ return [];
2070
+ }
2071
+ }
2072
+ // Helper function to get start of current day (midnight)
2073
+ getStartOfCurrentDay() {
2074
+ const now = /* @__PURE__ */ new Date();
2075
+ const startOfDay = new Date(now);
2076
+ startOfDay.setHours(0, 0, 0, 0);
2077
+ return startOfDay;
2078
+ }
2079
+ // Helper function to find all sessions for the current day
2080
+ async getDailySessionBoundaries() {
2081
+ try {
2082
+ const startOfDay = this.getStartOfCurrentDay();
2083
+ const endOfDay = new Date(startOfDay);
2084
+ endOfDay.setHours(23, 59, 59, 999);
2085
+ const entries = await this.readAllEntries(startOfDay);
2086
+ if (entries.length === 0) {
2087
+ return [];
2088
+ }
2089
+ const todayEntries = entries.filter((entry) => {
2090
+ const entryTime = new Date(entry.timestamp);
2091
+ return entryTime >= startOfDay && entryTime <= endOfDay;
2092
+ });
2093
+ if (todayEntries.length === 0) {
2094
+ return [];
2095
+ }
2096
+ todayEntries.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
2097
+ const sessions = [];
2098
+ let sessionNumber = 1;
2099
+ let currentSessionStart = null;
2100
+ let processedEntries = /* @__PURE__ */ new Set();
2101
+ for (const entry of todayEntries) {
2102
+ if (processedEntries.has(entry.timestamp)) {
2103
+ continue;
2104
+ }
2105
+ const entryTime = new Date(entry.timestamp);
2106
+ if (!currentSessionStart || entryTime >= new Date(currentSessionStart.getTime() + this.sessionDurationHours * 60 * 60 * 1e3)) {
2107
+ const sessionStart = new Date(entryTime);
2108
+ sessionStart.setMinutes(0, 0, 0);
2109
+ const sessionEnd = new Date(sessionStart.getTime() + this.sessionDurationHours * 60 * 60 * 1e3);
2110
+ const midnightEnd = new Date(endOfDay);
2111
+ const actualSessionEnd = sessionEnd > midnightEnd ? midnightEnd : sessionEnd;
2112
+ sessions.push({
2113
+ sessionNumber,
2114
+ startTime: sessionStart,
2115
+ endTime: actualSessionEnd,
2116
+ sessionId: this.generateSessionId(sessionStart.toISOString())
2117
+ });
2118
+ currentSessionStart = sessionStart;
2119
+ sessionNumber++;
2120
+ for (const e of todayEntries) {
2121
+ const eTime = new Date(e.timestamp);
2122
+ if (eTime >= sessionStart && eTime <= actualSessionEnd) {
2123
+ processedEntries.add(e.timestamp);
2124
+ }
2125
+ }
2126
+ }
2127
+ }
2128
+ return sessions;
2129
+ } catch (error) {
2130
+ console.error("Error getting daily session boundaries:", error);
2131
+ return [];
2132
+ }
2133
+ }
2134
+ // Helper function to find which session is currently active
2135
+ async getCurrentSession() {
2136
+ try {
2137
+ const now = /* @__PURE__ */ new Date();
2138
+ const sessions = await this.getDailySessionBoundaries();
2139
+ for (const session of sessions) {
2140
+ if (now >= session.startTime && now <= session.endTime) {
2141
+ return session;
2142
+ }
2143
+ }
2144
+ return null;
2145
+ } catch (error) {
2146
+ console.error("Error getting current session:", error);
2147
+ return null;
2148
+ }
2149
+ }
2150
+ };
2151
+ module2.exports = UsageReader;
2152
+ }
2153
+ });
2154
+
2155
+ // ../claude-code-web/src/usage-analytics.js
2156
+ var require_usage_analytics = __commonJS({
2157
+ "../claude-code-web/src/usage-analytics.js"(exports2, module2) {
2158
+ "use strict";
2159
+ var EventEmitter = require("events");
2160
+ var UsageAnalytics = class extends EventEmitter {
2161
+ constructor(options = {}) {
2162
+ super();
2163
+ this.sessionDurationHours = options.sessionDurationHours || 5;
2164
+ this.confidenceThreshold = options.confidenceThreshold || 0.95;
2165
+ this.burnRateWindow = options.burnRateWindow || 60;
2166
+ this.updateInterval = options.updateInterval || 1e4;
2167
+ this.planLimits = {
2168
+ "pro": {
2169
+ tokens: 19e3,
2170
+ cost: 18,
2171
+ messages: 250,
2172
+ algorithm: "fixed"
2173
+ },
2174
+ "claude-pro": {
2175
+ // Keep for backwards compatibility
2176
+ tokens: 19e3,
2177
+ cost: 18,
2178
+ messages: 250,
2179
+ algorithm: "fixed"
2180
+ },
2181
+ "max5": {
2182
+ tokens: 88e3,
2183
+ cost: 35,
2184
+ messages: 1e3,
2185
+ algorithm: "fixed"
2186
+ },
2187
+ "claude-max5": {
2188
+ // Keep for backwards compatibility
2189
+ tokens: 88e3,
2190
+ cost: 35,
2191
+ messages: 1e3,
2192
+ algorithm: "fixed"
2193
+ },
2194
+ "max20": {
2195
+ tokens: 22e4,
2196
+ cost: 140,
2197
+ messages: 2e3,
2198
+ algorithm: "fixed"
2199
+ },
2200
+ "claude-max20": {
2201
+ // Keep for backwards compatibility
2202
+ tokens: 22e4,
2203
+ cost: 140,
2204
+ messages: 2e3,
2205
+ algorithm: "fixed"
2206
+ },
2207
+ "custom": {
2208
+ tokens: null,
2209
+ // Calculated via P90
2210
+ cost: options.customCostLimit || 76.89,
2211
+ // Default based on typical usage
2212
+ messages: 1019,
2213
+ // Higher message limit for custom plans
2214
+ algorithm: "p90"
2215
+ }
2216
+ };
2217
+ this.currentPlan = options.plan || "custom";
2218
+ this.activeSessions = /* @__PURE__ */ new Map();
2219
+ this.sessionHistory = [];
2220
+ this.rollingWindows = /* @__PURE__ */ new Map();
2221
+ this.recentUsage = [];
2222
+ this.historicalData = [];
2223
+ this.p90Limit = null;
2224
+ this.burnRateHistory = [];
2225
+ this.currentBurnRate = 0;
2226
+ this.velocityTrend = "stable";
2227
+ this.depletionTime = null;
2228
+ this.depletionConfidence = 0;
2229
+ }
2230
+ /**
2231
+ * Process new usage data point
2232
+ */
2233
+ addUsageData(data) {
2234
+ const entry = {
2235
+ timestamp: /* @__PURE__ */ new Date(),
2236
+ tokens: (data.inputTokens || 0) + (data.outputTokens || 0),
2237
+ // Only input + output tokens
2238
+ inputTokens: data.inputTokens || 0,
2239
+ outputTokens: data.outputTokens || 0,
2240
+ cacheCreationTokens: data.cacheCreationTokens || 0,
2241
+ cacheReadTokens: data.cacheReadTokens || 0,
2242
+ cost: data.cost || 0,
2243
+ model: data.model || "unknown",
2244
+ sessionId: data.sessionId
2245
+ };
2246
+ this.recentUsage.push(entry);
2247
+ const cutoff = new Date(Date.now() - this.burnRateWindow * 60 * 1e3);
2248
+ this.recentUsage = this.recentUsage.filter((e) => e.timestamp > cutoff);
2249
+ this.calculateBurnRate();
2250
+ this.updatePredictions();
2251
+ this.emit("usage-update", entry);
2252
+ }
2253
+ /**
2254
+ * Start or update a session
2255
+ */
2256
+ startSession(sessionId, startTime = /* @__PURE__ */ new Date()) {
2257
+ const session = {
2258
+ id: sessionId,
2259
+ startTime,
2260
+ endTime: new Date(startTime.getTime() + this.sessionDurationHours * 60 * 60 * 1e3),
2261
+ tokens: 0,
2262
+ cost: 0,
2263
+ messages: 0,
2264
+ isActive: true,
2265
+ window: "current"
2266
+ };
2267
+ this.activeSessions.set(sessionId, session);
2268
+ this.updateRollingWindows();
2269
+ this.emit("session-started", session);
2270
+ return session;
2271
+ }
2272
+ /**
2273
+ * Update rolling windows for overlapping sessions
2274
+ */
2275
+ updateRollingWindows() {
2276
+ const now = /* @__PURE__ */ new Date();
2277
+ this.rollingWindows.clear();
2278
+ const fiveHoursAgo = new Date(now - this.sessionDurationHours * 60 * 60 * 1e3);
2279
+ for (const [id, session] of this.activeSessions) {
2280
+ if (session.startTime > fiveHoursAgo) {
2281
+ const windowId = `window_${session.startTime.getTime()}`;
2282
+ if (!this.rollingWindows.has(windowId)) {
2283
+ this.rollingWindows.set(windowId, {
2284
+ startTime: session.startTime,
2285
+ endTime: session.endTime,
2286
+ sessions: [],
2287
+ totalTokens: 0,
2288
+ totalCost: 0,
2289
+ remainingTokens: this.getTokenLimit(),
2290
+ burnRate: 0
2291
+ });
2292
+ }
2293
+ const window = this.rollingWindows.get(windowId);
2294
+ window.sessions.push(id);
2295
+ }
2296
+ }
2297
+ this.emit("windows-updated", Array.from(this.rollingWindows.values()));
2298
+ }
2299
+ /**
2300
+ * Calculate burn rate with sophisticated analysis
2301
+ */
2302
+ calculateBurnRate() {
2303
+ if (this.recentUsage.length < 2) {
2304
+ this.currentBurnRate = 0;
2305
+ return;
2306
+ }
2307
+ const sorted = [...this.recentUsage].sort((a, b) => a.timestamp - b.timestamp);
2308
+ const rates = [];
2309
+ const windows = [5, 10, 15, 30, 60];
2310
+ for (const window of windows) {
2311
+ const cutoff = new Date(Date.now() - window * 60 * 1e3);
2312
+ const windowData = sorted.filter((e) => e.timestamp > cutoff);
2313
+ if (windowData.length >= 2) {
2314
+ const duration = (windowData[windowData.length - 1].timestamp - windowData[0].timestamp) / 1e3 / 60;
2315
+ const totalTokens = windowData.reduce((sum, e) => sum + e.inputTokens + e.outputTokens, 0);
2316
+ if (duration > 0) {
2317
+ rates.push({
2318
+ window,
2319
+ rate: totalTokens / duration,
2320
+ weight: Math.min(windowData.length / 10, 1)
2321
+ // Weight by data points
2322
+ });
2323
+ }
2324
+ }
2325
+ }
2326
+ if (rates.length === 0) {
2327
+ this.currentBurnRate = 0;
2328
+ return;
2329
+ }
2330
+ const totalWeight = rates.reduce((sum, r) => sum + r.weight, 0);
2331
+ this.currentBurnRate = rates.reduce((sum, r) => sum + r.rate * r.weight, 0) / totalWeight;
2332
+ this.burnRateHistory.push({
2333
+ timestamp: /* @__PURE__ */ new Date(),
2334
+ rate: this.currentBurnRate
2335
+ });
2336
+ const histCutoff = new Date(Date.now() - 60 * 60 * 1e3);
2337
+ this.burnRateHistory = this.burnRateHistory.filter((e) => e.timestamp > histCutoff);
2338
+ this.analyzeTrend();
2339
+ this.emit("burn-rate-updated", {
2340
+ rate: this.currentBurnRate,
2341
+ trend: this.velocityTrend,
2342
+ confidence: this.calculateConfidence()
2343
+ });
2344
+ }
2345
+ /**
2346
+ * Analyze velocity trend
2347
+ */
2348
+ analyzeTrend() {
2349
+ if (this.burnRateHistory.length < 5) {
2350
+ this.velocityTrend = "stable";
2351
+ return;
2352
+ }
2353
+ const mid = Math.floor(this.burnRateHistory.length / 2);
2354
+ const oldRates = this.burnRateHistory.slice(0, mid);
2355
+ const newRates = this.burnRateHistory.slice(mid);
2356
+ const oldAvg = oldRates.reduce((sum, e) => sum + e.rate, 0) / oldRates.length;
2357
+ const newAvg = newRates.reduce((sum, e) => sum + e.rate, 0) / newRates.length;
2358
+ const change = (newAvg - oldAvg) / oldAvg;
2359
+ if (change > 0.15) {
2360
+ this.velocityTrend = "increasing";
2361
+ } else if (change < -0.15) {
2362
+ this.velocityTrend = "decreasing";
2363
+ } else {
2364
+ this.velocityTrend = "stable";
2365
+ }
2366
+ }
2367
+ /**
2368
+ * Update predictions for token depletion
2369
+ */
2370
+ updatePredictions() {
2371
+ const currentSession = this.getCurrentSession();
2372
+ if (!currentSession || this.currentBurnRate === 0) {
2373
+ this.depletionTime = null;
2374
+ this.depletionConfidence = 0;
2375
+ return;
2376
+ }
2377
+ const limit = this.getTokenLimit();
2378
+ const used = this.getSessionTokens(currentSession.id);
2379
+ const remaining = limit - used;
2380
+ if (remaining <= 0) {
2381
+ this.depletionTime = /* @__PURE__ */ new Date();
2382
+ this.depletionConfidence = 1;
2383
+ return;
2384
+ }
2385
+ const minutesToDepletion = remaining / this.currentBurnRate;
2386
+ this.depletionTime = new Date(Date.now() + minutesToDepletion * 60 * 1e3);
2387
+ this.depletionConfidence = this.calculateConfidence();
2388
+ if (this.velocityTrend === "increasing") {
2389
+ const adjustment = 0.9;
2390
+ const adjustedTime = Date.now() + (this.depletionTime - Date.now()) * adjustment;
2391
+ this.depletionTime = new Date(adjustedTime);
2392
+ } else if (this.velocityTrend === "decreasing") {
2393
+ const adjustment = 1.1;
2394
+ const adjustedTime = Date.now() + (this.depletionTime - Date.now()) * adjustment;
2395
+ this.depletionTime = new Date(adjustedTime);
2396
+ }
2397
+ this.emit("prediction-updated", {
2398
+ depletionTime: this.depletionTime,
2399
+ confidence: this.depletionConfidence,
2400
+ remaining,
2401
+ burnRate: this.currentBurnRate
2402
+ });
2403
+ }
2404
+ /**
2405
+ * Calculate confidence score for predictions
2406
+ */
2407
+ calculateConfidence() {
2408
+ let confidence = 0;
2409
+ let factors = 0;
2410
+ if (this.recentUsage.length > 0) {
2411
+ const dataScore = Math.min(this.recentUsage.length / 20, 1);
2412
+ confidence += dataScore * 0.3;
2413
+ factors++;
2414
+ }
2415
+ if (this.burnRateHistory.length > 3) {
2416
+ const rates = this.burnRateHistory.map((e) => e.rate);
2417
+ const mean = rates.reduce((a, b) => a + b, 0) / rates.length;
2418
+ const variance = rates.reduce((sum, r) => sum + Math.pow(r - mean, 2), 0) / rates.length;
2419
+ const cv = mean > 0 ? Math.sqrt(variance) / mean : 1;
2420
+ const consistencyScore = Math.max(0, 1 - cv);
2421
+ confidence += consistencyScore * 0.4;
2422
+ factors++;
2423
+ }
2424
+ const trendScore = this.velocityTrend === "stable" ? 1 : 0.7;
2425
+ confidence += trendScore * 0.3;
2426
+ factors++;
2427
+ return factors > 0 ? confidence / factors : 0;
2428
+ }
2429
+ /**
2430
+ * Get current active session
2431
+ */
2432
+ getCurrentSession() {
2433
+ const now = /* @__PURE__ */ new Date();
2434
+ for (const [id, session] of this.activeSessions) {
2435
+ if (session.startTime <= now && session.endTime > now) {
2436
+ return session;
2437
+ }
2438
+ }
2439
+ return null;
2440
+ }
2441
+ /**
2442
+ * Get token limit based on plan
2443
+ */
2444
+ getTokenLimit() {
2445
+ const plan = this.planLimits[this.currentPlan];
2446
+ if (plan.algorithm === "fixed") {
2447
+ return plan.tokens;
2448
+ } else if (plan.algorithm === "p90") {
2449
+ return this.p90Limit || 188026;
2450
+ }
2451
+ return 188026;
2452
+ }
2453
+ /**
2454
+ * Calculate P90 limit from historical data
2455
+ */
2456
+ calculateP90Limit(historicalSessions) {
2457
+ if (!historicalSessions || historicalSessions.length < 10) {
2458
+ return null;
2459
+ }
2460
+ const tokenCounts = historicalSessions.map((s) => s.totalTokens).filter((t) => t > 0).sort((a, b) => a - b);
2461
+ if (tokenCounts.length === 0) {
2462
+ return null;
2463
+ }
2464
+ const p90Index = Math.floor(tokenCounts.length * 0.9);
2465
+ this.p90Limit = tokenCounts[p90Index];
2466
+ this.emit("p90-calculated", {
2467
+ limit: this.p90Limit,
2468
+ sampleSize: tokenCounts.length,
2469
+ confidence: Math.min(tokenCounts.length / 100, 1)
2470
+ });
2471
+ return this.p90Limit;
2472
+ }
2473
+ /**
2474
+ * Get tokens used in a session
2475
+ */
2476
+ getSessionTokens(sessionId) {
2477
+ const session = this.activeSessions.get(sessionId);
2478
+ if (!session) return 0;
2479
+ const sessionData = this.recentUsage.filter((e) => e.sessionId === sessionId);
2480
+ return sessionData.reduce((sum, e) => sum + e.tokens, 0);
2481
+ }
2482
+ /**
2483
+ * Get comprehensive analytics data
2484
+ */
2485
+ getAnalytics() {
2486
+ const currentSession = this.getCurrentSession();
2487
+ return {
2488
+ currentSession: currentSession ? {
2489
+ id: currentSession.id,
2490
+ startTime: currentSession.startTime,
2491
+ endTime: currentSession.endTime,
2492
+ tokens: this.getSessionTokens(currentSession.id),
2493
+ remaining: this.getTokenLimit() - this.getSessionTokens(currentSession.id),
2494
+ percentUsed: this.getSessionTokens(currentSession.id) / this.getTokenLimit() * 100
2495
+ } : null,
2496
+ burnRate: {
2497
+ current: this.currentBurnRate,
2498
+ trend: this.velocityTrend,
2499
+ history: this.burnRateHistory.slice(-10)
2500
+ // Last 10 data points
2501
+ },
2502
+ predictions: {
2503
+ depletionTime: this.depletionTime,
2504
+ confidence: this.depletionConfidence,
2505
+ minutesRemaining: this.depletionTime ? Math.max(0, (this.depletionTime - Date.now()) / 1e3 / 60) : null
2506
+ },
2507
+ plan: {
2508
+ type: this.currentPlan,
2509
+ limits: this.planLimits[this.currentPlan],
2510
+ p90Limit: this.p90Limit
2511
+ },
2512
+ windows: Array.from(this.rollingWindows.values()),
2513
+ activeSessions: Array.from(this.activeSessions.values()).map((s) => ({
2514
+ id: s.id,
2515
+ startTime: s.startTime,
2516
+ endTime: s.endTime,
2517
+ isActive: s.isActive,
2518
+ tokens: this.getSessionTokens(s.id)
2519
+ }))
2520
+ };
2521
+ }
2522
+ /**
2523
+ * Set user's plan type
2524
+ */
2525
+ setPlan(planType) {
2526
+ if (this.planLimits[planType]) {
2527
+ this.currentPlan = planType;
2528
+ this.updatePredictions();
2529
+ this.emit("plan-changed", planType);
2530
+ }
2531
+ }
2532
+ /**
2533
+ * Clean up old data
2534
+ */
2535
+ cleanup() {
2536
+ const now = /* @__PURE__ */ new Date();
2537
+ for (const [id, session] of this.activeSessions) {
2538
+ if (session.endTime < now) {
2539
+ this.sessionHistory.push(session);
2540
+ this.activeSessions.delete(id);
2541
+ }
2542
+ }
2543
+ const cutoff = new Date(now - 24 * 60 * 60 * 1e3);
2544
+ this.sessionHistory = this.sessionHistory.filter((s) => s.endTime > cutoff);
2545
+ }
2546
+ };
2547
+ module2.exports = UsageAnalytics;
2548
+ }
2549
+ });
2550
+
2551
+ // ../claude-code-web/src/index.js
2552
+ var require_src = __commonJS({
2553
+ "../claude-code-web/src/index.js"(exports2, module2) {
2554
+ "use strict";
2555
+ var path = require("path");
2556
+ var WebSocket = require("ws");
2557
+ var { v4: uuidv4 } = (init_esm_node(), __toCommonJS(esm_node_exports));
2558
+ var ClaudeBridge = require_claude_bridge();
2559
+ var CodexBridge = require_codex_bridge();
2560
+ var AgentBridge = require_agent_bridge();
2561
+ var ScriptBridge = require_script_bridge();
2562
+ var SessionStore = require_session_store();
2563
+ var UsageReader = require_usage_reader();
2564
+ var UsageAnalytics = require_usage_analytics();
2565
+ var fs = require("fs");
2566
+ function stripAnsi(str) {
2567
+ return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "").replace(/\x1b[()][AB012]/g, "").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "").replace(/\r\n?/g, "\n").trim();
2568
+ }
2569
+ var TerminalHandler = class {
2570
+ constructor(options = {}) {
2571
+ this.baseFolder = options.workingDir || process.cwd();
2572
+ this.selectedWorkingDir = null;
2573
+ this.sessionDurationHours = parseFloat(process.env.CLAUDE_SESSION_HOURS || options.sessionHours || 5);
2574
+ this.claudeSessions = /* @__PURE__ */ new Map();
2575
+ this.webSocketConnections = /* @__PURE__ */ new Map();
2576
+ this.claudeBridge = new ClaudeBridge();
2577
+ this.codexBridge = new CodexBridge();
2578
+ this.agentBridge = new AgentBridge();
2579
+ this.scriptBridge = new ScriptBridge();
2580
+ this.sessionStore = new SessionStore({ storageDir: options.sessionStorageDir });
2581
+ this.usageReader = new UsageReader(this.sessionDurationHours);
2582
+ this.usageAnalytics = new UsageAnalytics({
2583
+ sessionDurationHours: this.sessionDurationHours,
2584
+ plan: options.plan || process.env.CLAUDE_PLAN || "max20",
2585
+ customCostLimit: parseFloat(process.env.CLAUDE_COST_LIMIT || options.customCostLimit || 50)
2586
+ });
2587
+ this.autoSaveInterval = null;
2588
+ this.isShuttingDown = false;
2589
+ this.aliases = {
2590
+ claude: options.claudeAlias || process.env.CLAUDE_ALIAS || "Claude",
2591
+ codex: options.codexAlias || process.env.CODEX_ALIAS || "Codex",
2592
+ agent: options.agentAlias || process.env.AGENT_ALIAS || "Cursor"
2593
+ };
2594
+ this.loadPersistedSessions();
2595
+ this.setupAutoSave();
2596
+ }
2597
+ // ── Persistence ──────────────────────────────────────────────────────
2598
+ async loadPersistedSessions() {
2599
+ try {
2600
+ const sessions = await this.sessionStore.loadSessions();
2601
+ let restored = 0;
2602
+ for (const [id, session] of sessions) {
2603
+ if (!this.claudeSessions.has(id)) {
2604
+ this.claudeSessions.set(id, session);
2605
+ restored++;
2606
+ }
2607
+ }
2608
+ if (restored > 0) {
2609
+ console.log(`Loaded ${restored} persisted sessions`);
2610
+ }
2611
+ } catch (error) {
2612
+ console.error("Failed to load persisted sessions:", error);
2613
+ }
2614
+ }
2615
+ setupAutoSave() {
2616
+ this.autoSaveInterval = setInterval(() => {
2617
+ this.saveSessionsToDisk();
2618
+ }, 3e4);
2619
+ process.on("beforeExit", () => this.saveSessionsToDisk());
2620
+ }
2621
+ async saveSessionsToDisk(sessions = this.claudeSessions) {
2622
+ if (sessions.size > 0) {
2623
+ await this.sessionStore.saveSessions(sessions);
2624
+ }
2625
+ }
2626
+ async handleShutdown() {
2627
+ if (this.isShuttingDown) {
2628
+ return;
2629
+ }
2630
+ this.isShuttingDown = true;
2631
+ console.log("\nGracefully shutting down terminal handler...");
2632
+ await this.saveSessionsToDisk();
2633
+ if (this.autoSaveInterval) {
2634
+ clearInterval(this.autoSaveInterval);
2635
+ }
2636
+ }
2637
+ // ── Path validation ──────────────────────────────────────────────────
2638
+ isPathWithinBase(targetPath) {
2639
+ try {
2640
+ const resolvedTarget = path.resolve(targetPath);
2641
+ const resolvedBase = path.resolve(this.baseFolder);
2642
+ return resolvedTarget.startsWith(resolvedBase);
2643
+ } catch (error) {
2644
+ return false;
2645
+ }
2646
+ }
2647
+ validatePath(targetPath) {
2648
+ if (!targetPath) {
2649
+ return { valid: false, error: "Path is required" };
2650
+ }
2651
+ const resolvedPath = path.resolve(targetPath);
2652
+ if (!this.isPathWithinBase(resolvedPath)) {
2653
+ return {
2654
+ valid: false,
2655
+ error: "Access denied: Path is outside the allowed directory"
2656
+ };
2657
+ }
2658
+ return { valid: true, path: resolvedPath };
2659
+ }
2660
+ // ── HTTP request handler ─────────────────────────────────────────────
2661
+ // Replaces setupExpress() — handles API routes for /terminal/api/*
2662
+ // Strips the /terminal prefix before matching routes.
2663
+ // Returns true if handled, false if not.
2664
+ handleRequest(req, res) {
2665
+ const url = new URL(req.url || "/", `http://localhost`);
2666
+ const apiPath = url.pathname.replace(/^\/terminal/, "");
2667
+ if (req.method === "GET" && apiPath === "/api/health") {
2668
+ res.writeHead(200, { "Content-Type": "application/json" });
2669
+ res.end(JSON.stringify({
2670
+ status: "ok",
2671
+ claudeSessions: this.claudeSessions.size,
2672
+ activeConnections: this.webSocketConnections.size
2673
+ }));
2674
+ return true;
2675
+ }
2676
+ if (req.method === "GET" && apiPath === "/api/sessions/persistence") {
2677
+ (async () => {
2678
+ const metadata = await this.sessionStore.getSessionMetadata();
2679
+ res.writeHead(200, { "Content-Type": "application/json" });
2680
+ res.end(JSON.stringify({
2681
+ ...metadata,
2682
+ currentSessions: this.claudeSessions.size,
2683
+ autoSaveEnabled: true,
2684
+ autoSaveInterval: 3e4
2685
+ }));
2686
+ })();
2687
+ return true;
2688
+ }
2689
+ if (req.method === "GET" && apiPath === "/api/sessions/list") {
2690
+ const sessionList = Array.from(this.claudeSessions.entries()).map(([id, session]) => ({
2691
+ id,
2692
+ name: session.name,
2693
+ created: session.created,
2694
+ active: session.active,
2695
+ workingDir: session.workingDir,
2696
+ connectedClients: session.connections.size,
2697
+ lastActivity: session.lastActivity
2698
+ }));
2699
+ res.writeHead(200, { "Content-Type": "application/json" });
2700
+ res.end(JSON.stringify({ sessions: sessionList }));
2701
+ return true;
2702
+ }
2703
+ if (req.method === "POST" && apiPath === "/api/sessions/create") {
2704
+ let body = "";
2705
+ req.on("data", (chunk) => body += chunk);
2706
+ req.on("end", () => {
2707
+ try {
2708
+ const { name, workingDir } = JSON.parse(body);
2709
+ const sessionId = uuidv4();
2710
+ let validWorkingDir = this.baseFolder;
2711
+ if (workingDir) {
2712
+ const validation = this.validatePath(workingDir);
2713
+ if (!validation.valid) {
2714
+ res.writeHead(403, { "Content-Type": "application/json" });
2715
+ res.end(JSON.stringify({ error: validation.error }));
2716
+ return;
2717
+ }
2718
+ validWorkingDir = validation.path;
2719
+ } else if (this.selectedWorkingDir) {
2720
+ validWorkingDir = this.selectedWorkingDir;
2721
+ }
2722
+ const session = {
2723
+ id: sessionId,
2724
+ name: name || `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
2725
+ created: /* @__PURE__ */ new Date(),
2726
+ lastActivity: /* @__PURE__ */ new Date(),
2727
+ active: false,
2728
+ agent: null,
2729
+ workingDir: validWorkingDir,
2730
+ connections: /* @__PURE__ */ new Set(),
2731
+ outputBuffer: [],
2732
+ maxBufferSize: 1e3
2733
+ };
2734
+ this.claudeSessions.set(sessionId, session);
2735
+ this.saveSessionsToDisk();
2736
+ res.writeHead(200, { "Content-Type": "application/json" });
2737
+ res.end(JSON.stringify({
2738
+ success: true,
2739
+ sessionId,
2740
+ session: { id: sessionId, name: session.name, workingDir: session.workingDir }
2741
+ }));
2742
+ } catch (e) {
2743
+ res.writeHead(400, { "Content-Type": "application/json" });
2744
+ res.end(JSON.stringify({ error: "Invalid request" }));
2745
+ }
2746
+ });
2747
+ return true;
2748
+ }
2749
+ const sessionMatch = apiPath.match(/^\/api\/sessions\/([^/]+)$/);
2750
+ if (sessionMatch && req.method === "GET") {
2751
+ const session = this.claudeSessions.get(sessionMatch[1]);
2752
+ if (!session) {
2753
+ res.writeHead(404, { "Content-Type": "application/json" });
2754
+ res.end(JSON.stringify({ error: "Session not found" }));
2755
+ } else {
2756
+ res.writeHead(200, { "Content-Type": "application/json" });
2757
+ res.end(JSON.stringify({
2758
+ id: session.id,
2759
+ name: session.name,
2760
+ created: session.created,
2761
+ active: session.active,
2762
+ workingDir: session.workingDir,
2763
+ connectedClients: session.connections.size,
2764
+ lastActivity: session.lastActivity
2765
+ }));
2766
+ }
2767
+ return true;
2768
+ }
2769
+ if (sessionMatch && req.method === "DELETE") {
2770
+ const sessionId = sessionMatch[1];
2771
+ const session = this.claudeSessions.get(sessionId);
2772
+ if (!session) {
2773
+ res.writeHead(404, { "Content-Type": "application/json" });
2774
+ res.end(JSON.stringify({ error: "Session not found" }));
2775
+ } else {
2776
+ const doDelete = async () => {
2777
+ if (session.active) {
2778
+ try {
2779
+ if (session.agent === "script") await this.scriptBridge.stopSession(sessionId);
2780
+ else if (session.agent === "codex") await this.codexBridge.stopSession(sessionId);
2781
+ else if (session.agent === "agent") await this.agentBridge.stopSession(sessionId);
2782
+ else await this.claudeBridge.stopSession(sessionId);
2783
+ } catch (err) {
2784
+ console.error(`Failed to stop agent in session ${sessionId}:`, err);
2785
+ }
2786
+ }
2787
+ session.connections.forEach((wsId) => {
2788
+ const wsInfo = this.webSocketConnections.get(wsId);
2789
+ if (wsInfo && wsInfo.ws.readyState === WebSocket.OPEN) {
2790
+ wsInfo.ws.send(JSON.stringify({ type: "session_deleted", message: "Session has been deleted" }));
2791
+ wsInfo.ws.close();
2792
+ }
2793
+ });
2794
+ this.claudeSessions.delete(sessionId);
2795
+ this.saveSessionsToDisk();
2796
+ if (this.onSessionDeleted) this.onSessionDeleted(sessionId);
2797
+ res.writeHead(200, { "Content-Type": "application/json" });
2798
+ res.end(JSON.stringify({ success: true }));
2799
+ };
2800
+ doDelete();
2801
+ }
2802
+ return true;
2803
+ }
2804
+ if (req.method === "GET" && apiPath === "/api/config") {
2805
+ res.writeHead(200, { "Content-Type": "application/json" });
2806
+ res.end(JSON.stringify({
2807
+ folderMode: true,
2808
+ selectedWorkingDir: this.selectedWorkingDir,
2809
+ baseFolder: this.baseFolder,
2810
+ aliases: this.aliases
2811
+ }));
2812
+ return true;
2813
+ }
2814
+ if (req.method === "GET" && apiPath === "/api/folders") {
2815
+ const requestedPath = url.searchParams.get("path") || this.baseFolder;
2816
+ const validation = this.validatePath(requestedPath);
2817
+ if (!validation.valid) {
2818
+ res.writeHead(403, { "Content-Type": "application/json" });
2819
+ res.end(JSON.stringify({ error: validation.error }));
2820
+ return true;
2821
+ }
2822
+ try {
2823
+ const items = fs.readdirSync(validation.path, { withFileTypes: true });
2824
+ const showHidden = url.searchParams.get("showHidden") === "true";
2825
+ const folders = items.filter((item) => item.isDirectory()).filter((item) => !item.name.startsWith(".") || showHidden).map((item) => ({
2826
+ name: item.name,
2827
+ path: path.join(validation.path, item.name),
2828
+ isDirectory: true
2829
+ })).sort((a, b) => a.name.localeCompare(b.name));
2830
+ const parentDir = path.dirname(validation.path);
2831
+ const canGoUp = this.isPathWithinBase(parentDir) && parentDir !== validation.path;
2832
+ res.writeHead(200, { "Content-Type": "application/json" });
2833
+ res.end(JSON.stringify({
2834
+ currentPath: validation.path,
2835
+ parentPath: canGoUp ? parentDir : null,
2836
+ folders,
2837
+ home: this.baseFolder,
2838
+ baseFolder: this.baseFolder
2839
+ }));
2840
+ } catch (error) {
2841
+ res.writeHead(403, { "Content-Type": "application/json" });
2842
+ res.end(JSON.stringify({ error: "Cannot access directory" }));
2843
+ }
2844
+ return true;
2845
+ }
2846
+ if (req.method === "POST" && apiPath === "/api/set-working-dir") {
2847
+ let body = "";
2848
+ req.on("data", (chunk) => body += chunk);
2849
+ req.on("end", () => {
2850
+ try {
2851
+ const { path: selectedPath } = JSON.parse(body);
2852
+ const validation = this.validatePath(selectedPath);
2853
+ if (!validation.valid) {
2854
+ res.writeHead(403, { "Content-Type": "application/json" });
2855
+ res.end(JSON.stringify({ error: validation.error }));
2856
+ return;
2857
+ }
2858
+ if (!fs.existsSync(validation.path) || !fs.statSync(validation.path).isDirectory()) {
2859
+ res.writeHead(400, { "Content-Type": "application/json" });
2860
+ res.end(JSON.stringify({ error: "Not a valid directory" }));
2861
+ return;
2862
+ }
2863
+ this.selectedWorkingDir = validation.path;
2864
+ res.writeHead(200, { "Content-Type": "application/json" });
2865
+ res.end(JSON.stringify({ success: true, workingDir: this.selectedWorkingDir }));
2866
+ } catch (e) {
2867
+ res.writeHead(400, { "Content-Type": "application/json" });
2868
+ res.end(JSON.stringify({ error: "Invalid request" }));
2869
+ }
2870
+ });
2871
+ return true;
2872
+ }
2873
+ if (req.method === "POST" && apiPath === "/api/folders/select") {
2874
+ let body = "";
2875
+ req.on("data", (chunk) => body += chunk);
2876
+ req.on("end", () => {
2877
+ try {
2878
+ const { path: selectedPath } = JSON.parse(body);
2879
+ const validation = this.validatePath(selectedPath);
2880
+ if (!validation.valid) {
2881
+ res.writeHead(403, { "Content-Type": "application/json" });
2882
+ res.end(JSON.stringify({ error: validation.error }));
2883
+ return;
2884
+ }
2885
+ if (!fs.existsSync(validation.path) || !fs.statSync(validation.path).isDirectory()) {
2886
+ res.writeHead(400, { "Content-Type": "application/json" });
2887
+ res.end(JSON.stringify({ error: "Invalid directory path" }));
2888
+ return;
2889
+ }
2890
+ this.selectedWorkingDir = validation.path;
2891
+ res.writeHead(200, { "Content-Type": "application/json" });
2892
+ res.end(JSON.stringify({ success: true, workingDir: this.selectedWorkingDir }));
2893
+ } catch (e) {
2894
+ res.writeHead(400, { "Content-Type": "application/json" });
2895
+ res.end(JSON.stringify({ error: "Invalid request" }));
2896
+ }
2897
+ });
2898
+ return true;
2899
+ }
2900
+ if (req.method === "POST" && apiPath === "/api/create-folder") {
2901
+ let body = "";
2902
+ req.on("data", (chunk) => body += chunk);
2903
+ req.on("end", () => {
2904
+ try {
2905
+ const { parentPath, folderName } = JSON.parse(body);
2906
+ if (!folderName || !folderName.trim() || folderName.includes("/") || folderName.includes("\\")) {
2907
+ res.writeHead(400, { "Content-Type": "application/json" });
2908
+ res.end(JSON.stringify({ message: "Invalid folder name" }));
2909
+ return;
2910
+ }
2911
+ const basePath = parentPath || this.baseFolder;
2912
+ const fullPath = path.join(basePath, folderName);
2913
+ const parentValidation = this.validatePath(basePath);
2914
+ const fullValidation = this.validatePath(fullPath);
2915
+ if (!parentValidation.valid || !fullValidation.valid) {
2916
+ res.writeHead(403, { "Content-Type": "application/json" });
2917
+ res.end(JSON.stringify({ message: "Cannot create folder outside the allowed area" }));
2918
+ return;
2919
+ }
2920
+ if (fs.existsSync(fullValidation.path)) {
2921
+ res.writeHead(409, { "Content-Type": "application/json" });
2922
+ res.end(JSON.stringify({ message: "Folder already exists" }));
2923
+ return;
2924
+ }
2925
+ fs.mkdirSync(fullValidation.path, { recursive: true });
2926
+ res.writeHead(200, { "Content-Type": "application/json" });
2927
+ res.end(JSON.stringify({ success: true, path: fullValidation.path }));
2928
+ } catch (e) {
2929
+ res.writeHead(500, { "Content-Type": "application/json" });
2930
+ res.end(JSON.stringify({ message: "Failed to create folder" }));
2931
+ }
2932
+ });
2933
+ return true;
2934
+ }
2935
+ if (req.method === "POST" && apiPath === "/api/close-session") {
2936
+ this.selectedWorkingDir = null;
2937
+ res.writeHead(200, { "Content-Type": "application/json" });
2938
+ res.end(JSON.stringify({ success: true, message: "Working directory cleared" }));
2939
+ return true;
2940
+ }
2941
+ return false;
2942
+ }
2943
+ // ── Static file serving ──────────────────────────────────────────────
2944
+ // Serves files from the public/ directory for /terminal/* paths.
2945
+ serveStatic(req, res) {
2946
+ const url = new URL(req.url || "/", `http://localhost`);
2947
+ let filePath = url.pathname.replace(/^\/terminal\/?/, "");
2948
+ if (!filePath || filePath === "") filePath = "index.html";
2949
+ const fullPath = path.join(__dirname, "public", filePath);
2950
+ try {
2951
+ if (!fs.existsSync(fullPath)) return false;
2952
+ const ext = path.extname(fullPath);
2953
+ const mimeTypes = {
2954
+ ".html": "text/html",
2955
+ ".js": "application/javascript",
2956
+ ".css": "text/css",
2957
+ ".json": "application/json",
2958
+ ".png": "image/png",
2959
+ ".svg": "image/svg+xml",
2960
+ ".ico": "image/x-icon"
2961
+ };
2962
+ const mime = mimeTypes[ext] || "application/octet-stream";
2963
+ const content = fs.readFileSync(fullPath);
2964
+ res.writeHead(200, { "Content-Type": mime });
2965
+ res.end(content);
2966
+ return true;
2967
+ } catch {
2968
+ return false;
2969
+ }
2970
+ }
2971
+ // ── WebSocket handling ───────────────────────────────────────────────
2972
+ handleWebSocketConnection(ws, req) {
2973
+ const wsId = uuidv4();
2974
+ const url = new URL(req.url, `ws://localhost`);
2975
+ const claudeSessionId = url.searchParams.get("sessionId");
2976
+ const wsInfo = {
2977
+ id: wsId,
2978
+ ws,
2979
+ claudeSessionId: null,
2980
+ created: /* @__PURE__ */ new Date()
2981
+ };
2982
+ this.webSocketConnections.set(wsId, wsInfo);
2983
+ ws.on("message", async (message) => {
2984
+ try {
2985
+ const data = JSON.parse(message);
2986
+ await this.handleMessage(wsId, data);
2987
+ } catch (error) {
2988
+ this.sendToWebSocket(ws, {
2989
+ type: "error",
2990
+ message: "Failed to process message"
2991
+ });
2992
+ }
2993
+ });
2994
+ ws.on("close", () => {
2995
+ this.cleanupWebSocketConnection(wsId);
2996
+ });
2997
+ ws.on("error", (error) => {
2998
+ this.cleanupWebSocketConnection(wsId);
2999
+ });
3000
+ this.sendToWebSocket(ws, {
3001
+ type: "connected",
3002
+ connectionId: wsId
3003
+ });
3004
+ if (claudeSessionId && this.claudeSessions.has(claudeSessionId)) {
3005
+ this.joinClaudeSession(wsId, claudeSessionId);
3006
+ }
3007
+ }
3008
+ async handleMessage(wsId, data) {
3009
+ const wsInfo = this.webSocketConnections.get(wsId);
3010
+ if (!wsInfo) return;
3011
+ switch (data.type) {
3012
+ case "create_session":
3013
+ await this.createAndJoinSession(wsId, data.name, data.workingDir);
3014
+ break;
3015
+ case "join_session":
3016
+ await this.joinClaudeSession(wsId, data.sessionId);
3017
+ break;
3018
+ case "leave_session":
3019
+ await this.leaveClaudeSession(wsId);
3020
+ break;
3021
+ case "start_claude":
3022
+ await this.startClaude(wsId, data.options || {});
3023
+ break;
3024
+ case "start_codex":
3025
+ await this.startCodex(wsId, data.options || {});
3026
+ break;
3027
+ case "start_agent":
3028
+ await this.startAgent(wsId, data.options || {});
3029
+ break;
3030
+ case "input":
3031
+ if (wsInfo.claudeSessionId) {
3032
+ const session = this.claudeSessions.get(wsInfo.claudeSessionId);
3033
+ if (session && session.connections.has(wsId)) {
3034
+ if (session.active && session.agent) {
3035
+ try {
3036
+ if (session.agent === "script") {
3037
+ await this.scriptBridge.sendInput(wsInfo.claudeSessionId, data.data);
3038
+ } else if (session.agent === "codex") {
3039
+ await this.codexBridge.sendInput(wsInfo.claudeSessionId, data.data);
3040
+ } else if (session.agent === "agent") {
3041
+ await this.agentBridge.sendInput(wsInfo.claudeSessionId, data.data);
3042
+ } else {
3043
+ await this.claudeBridge.sendInput(wsInfo.claudeSessionId, data.data);
3044
+ }
3045
+ } catch (error) {
3046
+ this.sendToWebSocket(wsInfo.ws, {
3047
+ type: "error",
3048
+ message: "Agent is not running in this session. Please start an agent first."
3049
+ });
3050
+ }
3051
+ } else {
3052
+ this.sendToWebSocket(wsInfo.ws, {
3053
+ type: "info",
3054
+ message: "No agent is running. Choose an option to start."
3055
+ });
3056
+ }
3057
+ }
3058
+ }
3059
+ break;
3060
+ case "resize":
3061
+ if (wsInfo.claudeSessionId) {
3062
+ const session = this.claudeSessions.get(wsInfo.claudeSessionId);
3063
+ if (session && session.connections.has(wsId)) {
3064
+ if (session.active && session.agent) {
3065
+ try {
3066
+ if (session.agent === "script") {
3067
+ await this.scriptBridge.resize(wsInfo.claudeSessionId, data.cols, data.rows);
3068
+ } else if (session.agent === "codex") {
3069
+ await this.codexBridge.resize(wsInfo.claudeSessionId, data.cols, data.rows);
3070
+ } else if (session.agent === "agent") {
3071
+ await this.agentBridge.resize(wsInfo.claudeSessionId, data.cols, data.rows);
3072
+ } else {
3073
+ await this.claudeBridge.resize(wsInfo.claudeSessionId, data.cols, data.rows);
3074
+ }
3075
+ } catch (error) {
3076
+ }
3077
+ }
3078
+ }
3079
+ }
3080
+ break;
3081
+ case "stop":
3082
+ if (wsInfo.claudeSessionId) {
3083
+ const session = this.claudeSessions.get(wsInfo.claudeSessionId);
3084
+ if (session?.agent === "codex") {
3085
+ await this.stopCodex(wsInfo.claudeSessionId);
3086
+ } else if (session?.agent === "agent") {
3087
+ await this.stopAgent(wsInfo.claudeSessionId);
3088
+ } else {
3089
+ await this.stopClaude(wsInfo.claudeSessionId);
3090
+ }
3091
+ }
3092
+ break;
3093
+ case "ping":
3094
+ this.sendToWebSocket(wsInfo.ws, { type: "pong" });
3095
+ break;
3096
+ case "get_usage":
3097
+ this.handleGetUsage(wsInfo);
3098
+ break;
3099
+ default:
3100
+ break;
3101
+ }
3102
+ }
3103
+ // ── Session management ───────────────────────────────────────────────
3104
+ async createAndJoinSession(wsId, name, workingDir) {
3105
+ const wsInfo = this.webSocketConnections.get(wsId);
3106
+ if (!wsInfo) return;
3107
+ let validWorkingDir = this.baseFolder;
3108
+ if (workingDir) {
3109
+ const validation = this.validatePath(workingDir);
3110
+ if (!validation.valid) {
3111
+ this.sendToWebSocket(wsInfo.ws, {
3112
+ type: "error",
3113
+ message: "Cannot create session with working directory outside the allowed area"
3114
+ });
3115
+ return;
3116
+ }
3117
+ validWorkingDir = validation.path;
3118
+ } else if (this.selectedWorkingDir) {
3119
+ validWorkingDir = this.selectedWorkingDir;
3120
+ }
3121
+ const sessionId = uuidv4();
3122
+ const session = {
3123
+ id: sessionId,
3124
+ name: name || `Session ${(/* @__PURE__ */ new Date()).toLocaleString()}`,
3125
+ created: /* @__PURE__ */ new Date(),
3126
+ lastActivity: /* @__PURE__ */ new Date(),
3127
+ active: false,
3128
+ workingDir: validWorkingDir,
3129
+ connections: /* @__PURE__ */ new Set([wsId]),
3130
+ outputBuffer: [],
3131
+ sessionStartTime: null,
3132
+ sessionUsage: {
3133
+ requests: 0,
3134
+ inputTokens: 0,
3135
+ outputTokens: 0,
3136
+ cacheTokens: 0,
3137
+ totalCost: 0,
3138
+ models: {}
3139
+ },
3140
+ maxBufferSize: 1e3
3141
+ };
3142
+ this.claudeSessions.set(sessionId, session);
3143
+ wsInfo.claudeSessionId = sessionId;
3144
+ this.saveSessionsToDisk();
3145
+ this.sendToWebSocket(wsInfo.ws, {
3146
+ type: "session_created",
3147
+ sessionId,
3148
+ sessionName: session.name,
3149
+ workingDir: session.workingDir
3150
+ });
3151
+ }
3152
+ async joinClaudeSession(wsId, claudeSessionId) {
3153
+ const wsInfo = this.webSocketConnections.get(wsId);
3154
+ if (!wsInfo) return;
3155
+ const session = this.claudeSessions.get(claudeSessionId);
3156
+ if (!session) {
3157
+ this.sendToWebSocket(wsInfo.ws, {
3158
+ type: "error",
3159
+ message: "Session not found"
3160
+ });
3161
+ return;
3162
+ }
3163
+ if (wsInfo.claudeSessionId) {
3164
+ await this.leaveClaudeSession(wsId);
3165
+ }
3166
+ wsInfo.claudeSessionId = claudeSessionId;
3167
+ session.connections.add(wsId);
3168
+ session.lastActivity = /* @__PURE__ */ new Date();
3169
+ session.lastAccessed = Date.now();
3170
+ this.sendToWebSocket(wsInfo.ws, {
3171
+ type: "session_joined",
3172
+ sessionId: claudeSessionId,
3173
+ sessionName: session.name,
3174
+ workingDir: session.workingDir,
3175
+ active: session.active,
3176
+ outputBuffer: session.outputBuffer.slice(-200)
3177
+ });
3178
+ }
3179
+ async leaveClaudeSession(wsId) {
3180
+ const wsInfo = this.webSocketConnections.get(wsId);
3181
+ if (!wsInfo || !wsInfo.claudeSessionId) return;
3182
+ const session = this.claudeSessions.get(wsInfo.claudeSessionId);
3183
+ if (session) {
3184
+ session.connections.delete(wsId);
3185
+ session.lastActivity = /* @__PURE__ */ new Date();
3186
+ }
3187
+ wsInfo.claudeSessionId = null;
3188
+ this.sendToWebSocket(wsInfo.ws, {
3189
+ type: "session_left"
3190
+ });
3191
+ }
3192
+ // ── Agent start/stop ─────────────────────────────────────────────────
3193
+ async startClaude(wsId, options) {
3194
+ const wsInfo = this.webSocketConnections.get(wsId);
3195
+ if (!wsInfo || !wsInfo.claudeSessionId) {
3196
+ this.sendToWebSocket(wsInfo.ws, {
3197
+ type: "error",
3198
+ message: "No session joined"
3199
+ });
3200
+ return;
3201
+ }
3202
+ const session = this.claudeSessions.get(wsInfo.claudeSessionId);
3203
+ if (!session) return;
3204
+ if (session.active) {
3205
+ this.sendToWebSocket(wsInfo.ws, {
3206
+ type: "error",
3207
+ message: "An agent is already running in this session"
3208
+ });
3209
+ return;
3210
+ }
3211
+ const sessionId = wsInfo.claudeSessionId;
3212
+ try {
3213
+ await this.claudeBridge.startSession(sessionId, {
3214
+ workingDir: session.workingDir,
3215
+ onOutput: (data) => {
3216
+ const currentSession = this.claudeSessions.get(sessionId);
3217
+ if (!currentSession) return;
3218
+ currentSession.lastOutputAt = Date.now();
3219
+ currentSession.outputBuffer.push(data);
3220
+ if (currentSession.outputBuffer.length > currentSession.maxBufferSize) {
3221
+ currentSession.outputBuffer.shift();
3222
+ }
3223
+ this.broadcastToSession(sessionId, {
3224
+ type: "output",
3225
+ data
3226
+ });
3227
+ },
3228
+ onExit: (code, signal) => {
3229
+ const currentSession = this.claudeSessions.get(sessionId);
3230
+ if (currentSession) {
3231
+ currentSession.active = false;
3232
+ }
3233
+ this.broadcastToSession(sessionId, {
3234
+ type: "exit",
3235
+ code,
3236
+ signal
3237
+ });
3238
+ },
3239
+ onError: (error) => {
3240
+ const currentSession = this.claudeSessions.get(sessionId);
3241
+ if (currentSession) {
3242
+ currentSession.active = false;
3243
+ }
3244
+ this.broadcastToSession(sessionId, {
3245
+ type: "error",
3246
+ message: error.message
3247
+ });
3248
+ },
3249
+ ...options
3250
+ });
3251
+ session.active = true;
3252
+ session.agent = "claude";
3253
+ session.lastActivity = /* @__PURE__ */ new Date();
3254
+ if (!session.sessionStartTime) {
3255
+ session.sessionStartTime = /* @__PURE__ */ new Date();
3256
+ }
3257
+ this.broadcastToSession(sessionId, {
3258
+ type: "claude_started",
3259
+ sessionId
3260
+ });
3261
+ } catch (error) {
3262
+ this.sendToWebSocket(wsInfo.ws, {
3263
+ type: "error",
3264
+ message: `Failed to start Claude Code: ${error.message}`
3265
+ });
3266
+ }
3267
+ }
3268
+ async stopClaude(claudeSessionId) {
3269
+ const session = this.claudeSessions.get(claudeSessionId);
3270
+ if (!session || !session.active) return;
3271
+ await this.claudeBridge.stopSession(claudeSessionId);
3272
+ session.active = false;
3273
+ session.agent = null;
3274
+ session.lastActivity = /* @__PURE__ */ new Date();
3275
+ this.broadcastToSession(claudeSessionId, {
3276
+ type: "claude_stopped"
3277
+ });
3278
+ }
3279
+ async startCodex(wsId, options) {
3280
+ const wsInfo = this.webSocketConnections.get(wsId);
3281
+ if (!wsInfo || !wsInfo.claudeSessionId) {
3282
+ this.sendToWebSocket(wsInfo.ws, {
3283
+ type: "error",
3284
+ message: "No session joined"
3285
+ });
3286
+ return;
3287
+ }
3288
+ const session = this.claudeSessions.get(wsInfo.claudeSessionId);
3289
+ if (!session) return;
3290
+ if (session.active) {
3291
+ this.sendToWebSocket(wsInfo.ws, {
3292
+ type: "error",
3293
+ message: "An agent is already running in this session"
3294
+ });
3295
+ return;
3296
+ }
3297
+ const sessionId = wsInfo.claudeSessionId;
3298
+ try {
3299
+ await this.codexBridge.startSession(sessionId, {
3300
+ workingDir: session.workingDir,
3301
+ onOutput: (data) => {
3302
+ const currentSession = this.claudeSessions.get(sessionId);
3303
+ if (!currentSession) return;
3304
+ currentSession.lastOutputAt = Date.now();
3305
+ currentSession.outputBuffer.push(data);
3306
+ if (currentSession.outputBuffer.length > currentSession.maxBufferSize) {
3307
+ currentSession.outputBuffer.shift();
3308
+ }
3309
+ this.broadcastToSession(sessionId, { type: "output", data });
3310
+ },
3311
+ onExit: (code, signal) => {
3312
+ const currentSession = this.claudeSessions.get(sessionId);
3313
+ if (currentSession) {
3314
+ currentSession.active = false;
3315
+ currentSession.agent = null;
3316
+ }
3317
+ this.broadcastToSession(sessionId, { type: "exit", code, signal });
3318
+ },
3319
+ onError: (error) => {
3320
+ const currentSession = this.claudeSessions.get(sessionId);
3321
+ if (currentSession) {
3322
+ currentSession.active = false;
3323
+ currentSession.agent = null;
3324
+ }
3325
+ this.broadcastToSession(sessionId, { type: "error", message: error.message });
3326
+ },
3327
+ ...options
3328
+ });
3329
+ session.active = true;
3330
+ session.agent = "codex";
3331
+ session.lastActivity = /* @__PURE__ */ new Date();
3332
+ if (!session.sessionStartTime) {
3333
+ session.sessionStartTime = /* @__PURE__ */ new Date();
3334
+ }
3335
+ this.broadcastToSession(sessionId, {
3336
+ type: "codex_started",
3337
+ sessionId
3338
+ });
3339
+ } catch (error) {
3340
+ this.sendToWebSocket(wsInfo.ws, {
3341
+ type: "error",
3342
+ message: `Failed to start Codex Code: ${error.message}`
3343
+ });
3344
+ }
3345
+ }
3346
+ async stopCodex(sessionId) {
3347
+ const session = this.claudeSessions.get(sessionId);
3348
+ if (!session || !session.active) return;
3349
+ await this.codexBridge.stopSession(sessionId);
3350
+ session.active = false;
3351
+ session.agent = null;
3352
+ session.lastActivity = /* @__PURE__ */ new Date();
3353
+ this.broadcastToSession(sessionId, { type: "codex_stopped" });
3354
+ }
3355
+ async startAgent(wsId, options) {
3356
+ const wsInfo = this.webSocketConnections.get(wsId);
3357
+ if (!wsInfo || !wsInfo.claudeSessionId) {
3358
+ this.sendToWebSocket(wsInfo.ws, {
3359
+ type: "error",
3360
+ message: "No session joined"
3361
+ });
3362
+ return;
3363
+ }
3364
+ const session = this.claudeSessions.get(wsInfo.claudeSessionId);
3365
+ if (!session) return;
3366
+ if (session.active) {
3367
+ this.sendToWebSocket(wsInfo.ws, {
3368
+ type: "error",
3369
+ message: "An agent is already running in this session"
3370
+ });
3371
+ return;
3372
+ }
3373
+ const sessionId = wsInfo.claudeSessionId;
3374
+ try {
3375
+ await this.agentBridge.startSession(sessionId, {
3376
+ workingDir: session.workingDir,
3377
+ onOutput: (data) => {
3378
+ const currentSession = this.claudeSessions.get(sessionId);
3379
+ if (!currentSession) return;
3380
+ currentSession.lastOutputAt = Date.now();
3381
+ currentSession.outputBuffer.push(data);
3382
+ if (currentSession.outputBuffer.length > currentSession.maxBufferSize) {
3383
+ currentSession.outputBuffer.shift();
3384
+ }
3385
+ this.broadcastToSession(sessionId, { type: "output", data });
3386
+ },
3387
+ onExit: (code, signal) => {
3388
+ const currentSession = this.claudeSessions.get(sessionId);
3389
+ if (currentSession) {
3390
+ currentSession.active = false;
3391
+ currentSession.agent = null;
3392
+ }
3393
+ this.broadcastToSession(sessionId, { type: "exit", code, signal });
3394
+ },
3395
+ onError: (error) => {
3396
+ const currentSession = this.claudeSessions.get(sessionId);
3397
+ if (currentSession) {
3398
+ currentSession.active = false;
3399
+ currentSession.agent = null;
3400
+ }
3401
+ this.broadcastToSession(sessionId, { type: "error", message: error.message });
3402
+ },
3403
+ ...options
3404
+ });
3405
+ session.active = true;
3406
+ session.agent = "agent";
3407
+ session.lastActivity = /* @__PURE__ */ new Date();
3408
+ if (!session.sessionStartTime) {
3409
+ session.sessionStartTime = /* @__PURE__ */ new Date();
3410
+ }
3411
+ this.broadcastToSession(sessionId, {
3412
+ type: "agent_started",
3413
+ sessionId
3414
+ });
3415
+ } catch (error) {
3416
+ this.sendToWebSocket(wsInfo.ws, {
3417
+ type: "error",
3418
+ message: `Failed to start Agent: ${error.message}`
3419
+ });
3420
+ }
3421
+ }
3422
+ async stopAgent(sessionId) {
3423
+ const session = this.claudeSessions.get(sessionId);
3424
+ if (!session || !session.active) return;
3425
+ await this.agentBridge.stopSession(sessionId);
3426
+ session.active = false;
3427
+ session.agent = null;
3428
+ session.lastActivity = /* @__PURE__ */ new Date();
3429
+ this.broadcastToSession(sessionId, { type: "agent_stopped" });
3430
+ }
3431
+ // ── WebSocket helpers ────────────────────────────────────────────────
3432
+ sendToWebSocket(ws, data) {
3433
+ if (ws && ws.readyState === WebSocket.OPEN) {
3434
+ ws.send(JSON.stringify(data));
3435
+ }
3436
+ }
3437
+ broadcastToSession(claudeSessionId, data) {
3438
+ const session = this.claudeSessions.get(claudeSessionId);
3439
+ if (!session) return;
3440
+ session.connections.forEach((wsId) => {
3441
+ const wsInfo = this.webSocketConnections.get(wsId);
3442
+ if (wsInfo && wsInfo.claudeSessionId === claudeSessionId && wsInfo.ws.readyState === WebSocket.OPEN) {
3443
+ this.sendToWebSocket(wsInfo.ws, data);
3444
+ }
3445
+ });
3446
+ }
3447
+ cleanupWebSocketConnection(wsId) {
3448
+ const wsInfo = this.webSocketConnections.get(wsId);
3449
+ if (!wsInfo) return;
3450
+ if (wsInfo.claudeSessionId) {
3451
+ const session = this.claudeSessions.get(wsInfo.claudeSessionId);
3452
+ if (session) {
3453
+ session.connections.delete(wsId);
3454
+ session.lastActivity = /* @__PURE__ */ new Date();
3455
+ }
3456
+ }
3457
+ this.webSocketConnections.delete(wsId);
3458
+ }
3459
+ // ── Usage ────────────────────────────────────────────────────────────
3460
+ async handleGetUsage(wsInfo) {
3461
+ try {
3462
+ const currentSessionStats = await this.usageReader.getCurrentSessionStats();
3463
+ const burnRateData = await this.usageReader.calculateBurnRate(60);
3464
+ const overlappingSessions = await this.usageReader.detectOverlappingSessions();
3465
+ const dailyStats = await this.usageReader.getUsageStats(24);
3466
+ if (currentSessionStats && currentSessionStats.sessionStartTime) {
3467
+ this.usageAnalytics.startSession(
3468
+ currentSessionStats.sessionId,
3469
+ new Date(currentSessionStats.sessionStartTime)
3470
+ );
3471
+ if (currentSessionStats.totalTokens > 0) {
3472
+ this.usageAnalytics.addUsageData({
3473
+ tokens: currentSessionStats.totalTokens,
3474
+ inputTokens: currentSessionStats.inputTokens,
3475
+ outputTokens: currentSessionStats.outputTokens,
3476
+ cacheCreationTokens: currentSessionStats.cacheCreationTokens,
3477
+ cacheReadTokens: currentSessionStats.cacheReadTokens,
3478
+ cost: currentSessionStats.totalCost,
3479
+ model: Object.keys(currentSessionStats.models)[0] || "unknown",
3480
+ sessionId: currentSessionStats.sessionId
3481
+ });
3482
+ }
3483
+ }
3484
+ const analytics = this.usageAnalytics.getAnalytics();
3485
+ let sessionTimer = null;
3486
+ if (currentSessionStats && currentSessionStats.sessionStartTime) {
3487
+ const startTime = new Date(currentSessionStats.sessionStartTime);
3488
+ const now = /* @__PURE__ */ new Date();
3489
+ const elapsedMs = now - startTime;
3490
+ const sessionDurationMs = this.sessionDurationHours * 60 * 60 * 1e3;
3491
+ const remainingMs = Math.max(0, sessionDurationMs - elapsedMs);
3492
+ const hours = Math.floor(elapsedMs / (1e3 * 60 * 60));
3493
+ const minutes = Math.floor(elapsedMs % (1e3 * 60 * 60) / (1e3 * 60));
3494
+ const seconds = Math.floor(elapsedMs % (1e3 * 60) / 1e3);
3495
+ const remainingHours = Math.floor(remainingMs / (1e3 * 60 * 60));
3496
+ const remainingMinutes = Math.floor(remainingMs % (1e3 * 60 * 60) / (1e3 * 60));
3497
+ sessionTimer = {
3498
+ startTime: currentSessionStats.sessionStartTime,
3499
+ elapsed: elapsedMs,
3500
+ remaining: remainingMs,
3501
+ formatted: `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`,
3502
+ remainingFormatted: `${String(remainingHours).padStart(2, "0")}:${String(remainingMinutes).padStart(2, "0")}`,
3503
+ hours,
3504
+ minutes,
3505
+ seconds,
3506
+ remainingMs,
3507
+ sessionDurationHours: this.sessionDurationHours,
3508
+ sessionNumber: currentSessionStats.sessionNumber || 1,
3509
+ isExpired: remainingMs === 0,
3510
+ burnRate: burnRateData.rate,
3511
+ burnRateConfidence: burnRateData.confidence,
3512
+ depletionTime: analytics.predictions.depletionTime,
3513
+ depletionConfidence: analytics.predictions.confidence
3514
+ };
3515
+ }
3516
+ this.sendToWebSocket(wsInfo.ws, {
3517
+ type: "usage_update",
3518
+ sessionStats: currentSessionStats || {
3519
+ requests: 0,
3520
+ totalTokens: 0,
3521
+ totalCost: 0,
3522
+ message: "No active Claude session"
3523
+ },
3524
+ dailyStats,
3525
+ sessionTimer,
3526
+ analytics,
3527
+ burnRate: burnRateData,
3528
+ overlappingSessions: overlappingSessions.length,
3529
+ plan: this.usageAnalytics.currentPlan,
3530
+ limits: this.usageAnalytics.planLimits[this.usageAnalytics.currentPlan]
3531
+ });
3532
+ } catch (error) {
3533
+ console.error("Error getting usage stats:", error);
3534
+ this.sendToWebSocket(wsInfo.ws, {
3535
+ type: "error",
3536
+ message: "Failed to retrieve usage statistics"
3537
+ });
3538
+ }
3539
+ }
3540
+ // ── Direct session access (for server-side adapter) ─────────────────
3541
+ // existingId is optional: when present, reuse the given UUID instead of
3542
+ // minting a fresh one. Used by the radar agent to resume sessions across
3543
+ // process restarts — the wsSessionId must match the JSONL filename so the
3544
+ // transcript reader, textbox, and interrupt button all stay wired to the
3545
+ // same identity. If an entry with the id already exists (loaded from
3546
+ // sessionStorageDir), return it as-is rather than clobbering.
3547
+ createSessionDirect(name, workingDir, existingId) {
3548
+ const sessionId = existingId || uuidv4();
3549
+ if (existingId && this.claudeSessions.has(sessionId)) {
3550
+ const existing = this.claudeSessions.get(sessionId);
3551
+ existing.active = false;
3552
+ return sessionId;
3553
+ }
3554
+ const session = {
3555
+ id: sessionId,
3556
+ name: name || `Agent ${(/* @__PURE__ */ new Date()).toLocaleTimeString()}`,
3557
+ created: /* @__PURE__ */ new Date(),
3558
+ lastActivity: /* @__PURE__ */ new Date(),
3559
+ active: false,
3560
+ workingDir: workingDir || this.baseFolder,
3561
+ connections: /* @__PURE__ */ new Set(),
3562
+ outputBuffer: [],
3563
+ sessionStartTime: null,
3564
+ sessionUsage: {
3565
+ requests: 0,
3566
+ inputTokens: 0,
3567
+ outputTokens: 0,
3568
+ cacheTokens: 0,
3569
+ totalCost: 0,
3570
+ models: {}
3571
+ },
3572
+ maxBufferSize: 1e3
3573
+ };
3574
+ this.claudeSessions.set(sessionId, session);
3575
+ this.saveSessionsToDisk();
3576
+ return sessionId;
3577
+ }
3578
+ async startClaudeInSession(sessionId, options = {}) {
3579
+ const session = this.claudeSessions.get(sessionId);
3580
+ if (!session) throw new Error(`Session ${sessionId} not found`);
3581
+ if (session.active) throw new Error(`Agent already running in session ${sessionId}`);
3582
+ await this.claudeBridge.startSession(sessionId, {
3583
+ workingDir: session.workingDir,
3584
+ onOutput: (data) => {
3585
+ const s = this.claudeSessions.get(sessionId);
3586
+ if (!s) return;
3587
+ s.lastOutputAt = Date.now();
3588
+ s.outputBuffer.push(data);
3589
+ if (s.outputBuffer.length > s.maxBufferSize) s.outputBuffer.shift();
3590
+ this.broadcastToSession(sessionId, { type: "output", data });
3591
+ },
3592
+ onExit: (code, signal) => {
3593
+ const s = this.claudeSessions.get(sessionId);
3594
+ if (s) s.active = false;
3595
+ this.broadcastToSession(sessionId, { type: "exit", code, signal });
3596
+ },
3597
+ onError: (error) => {
3598
+ const s = this.claudeSessions.get(sessionId);
3599
+ if (s) s.active = false;
3600
+ this.broadcastToSession(sessionId, { type: "error", message: error.message });
3601
+ },
3602
+ ...options
3603
+ });
3604
+ session.active = true;
3605
+ session.agent = "claude";
3606
+ session.lastActivity = /* @__PURE__ */ new Date();
3607
+ if (!session.sessionStartTime) session.sessionStartTime = /* @__PURE__ */ new Date();
3608
+ this.broadcastToSession(sessionId, { type: "claude_started", sessionId });
3609
+ }
3610
+ async stopAgentInSession(sessionId) {
3611
+ const session = this.claudeSessions.get(sessionId);
3612
+ if (!session || !session.active) return;
3613
+ if (session.agent === "script") await this.scriptBridge.stopSession(sessionId);
3614
+ else if (session.agent === "codex") await this.codexBridge.stopSession(sessionId);
3615
+ else if (session.agent === "agent") await this.agentBridge.stopSession(sessionId);
3616
+ else await this.claudeBridge.stopSession(sessionId);
3617
+ session.active = false;
3618
+ session.agent = null;
3619
+ }
3620
+ async startScriptInSession(sessionId, options = {}) {
3621
+ const session = this.claudeSessions.get(sessionId);
3622
+ if (!session) throw new Error(`Session ${sessionId} not found`);
3623
+ if (session.active) throw new Error(`Agent already running in session ${sessionId}`);
3624
+ const { command, args = [], env = {} } = options;
3625
+ if (!command) throw new Error("startScriptInSession requires a command");
3626
+ return new Promise((resolve, reject) => {
3627
+ this.scriptBridge.startSession(sessionId, {
3628
+ command,
3629
+ args,
3630
+ workingDir: session.workingDir || this.baseFolder,
3631
+ env,
3632
+ cols: 80,
3633
+ rows: 24,
3634
+ onOutput: (data) => {
3635
+ session.lastActivity = /* @__PURE__ */ new Date();
3636
+ this.broadcastToSession(sessionId, {
3637
+ type: "output",
3638
+ data,
3639
+ sessionId
3640
+ });
3641
+ },
3642
+ onExit: (exitCode, signal) => {
3643
+ session.active = false;
3644
+ session.agent = null;
3645
+ session.lastActivity = /* @__PURE__ */ new Date();
3646
+ this.broadcastToSession(sessionId, { type: "script_stopped", sessionId });
3647
+ if (exitCode === 0) {
3648
+ resolve({ code: exitCode, signal });
3649
+ } else {
3650
+ reject(new Error(`Script exited with code ${exitCode}`));
3651
+ }
3652
+ },
3653
+ onError: (error) => {
3654
+ session.active = false;
3655
+ session.agent = null;
3656
+ reject(error);
3657
+ }
3658
+ });
3659
+ session.active = true;
3660
+ session.agent = "script";
3661
+ session.lastActivity = /* @__PURE__ */ new Date();
3662
+ if (!session.sessionStartTime) session.sessionStartTime = /* @__PURE__ */ new Date();
3663
+ this.broadcastToSession(sessionId, { type: "script_started", sessionId });
3664
+ });
3665
+ }
3666
+ isSessionActive(sessionId) {
3667
+ const session = this.claudeSessions.get(sessionId);
3668
+ return session?.active ?? false;
3669
+ }
3670
+ /**
3671
+ * Returns session IDs that are active but haven't produced output
3672
+ * for longer than thresholdMs. Used as a heuristic for "terminal
3673
+ * may need user input" (e.g. permission prompt).
3674
+ */
3675
+ getIdleSessions(thresholdMs = 15e3) {
3676
+ const now = Date.now();
3677
+ const idle = [];
3678
+ for (const [sessionId, session] of this.claudeSessions) {
3679
+ if (session.active && session.lastOutputAt && now - session.lastOutputAt > thresholdMs) {
3680
+ idle.push(sessionId);
3681
+ }
3682
+ }
3683
+ return idle;
3684
+ }
3685
+ getSessionPreviews() {
3686
+ const previews = {};
3687
+ for (const [sessionId, session] of this.claudeSessions) {
3688
+ if (session.outputBuffer.length === 0) continue;
3689
+ const raw = session.outputBuffer.slice(-5).join("");
3690
+ const text = stripAnsi(raw).slice(-100);
3691
+ if (text.length === 0) continue;
3692
+ previews[sessionId] = { text, active: !!session.active };
3693
+ }
3694
+ return previews;
3695
+ }
3696
+ // ── Shutdown ─────────────────────────────────────────────────────────
3697
+ shutdown() {
3698
+ this.isShuttingDown = true;
3699
+ const sessionsSnapshot = new Map(this.claudeSessions);
3700
+ this.saveSessionsToDisk(sessionsSnapshot).catch(
3701
+ (err) => console.error("shutdown save failed:", err)
3702
+ );
3703
+ if (this.autoSaveInterval) clearInterval(this.autoSaveInterval);
3704
+ for (const [sessionId, session] of this.claudeSessions.entries()) {
3705
+ if (session.active) {
3706
+ if (session.agent === "script") this.scriptBridge.stopSession(sessionId);
3707
+ else if (session.agent === "codex") this.codexBridge.stopSession(sessionId);
3708
+ else if (session.agent === "agent") this.agentBridge.stopSession(sessionId);
3709
+ else this.claudeBridge.stopSession(sessionId);
3710
+ }
3711
+ }
3712
+ this.claudeSessions.clear();
3713
+ this.webSocketConnections.clear();
3714
+ }
3715
+ };
3716
+ function createTerminalHandler2(options) {
3717
+ return new TerminalHandler(options);
3718
+ }
3719
+ function createWebSocketHandler2(httpServer, handler2, wsPath = "/terminal/ws") {
3720
+ const wss2 = new WebSocket.Server({
3721
+ server: httpServer,
3722
+ path: wsPath
3723
+ });
3724
+ wss2.on("connection", (ws, req) => {
3725
+ handler2.handleWebSocketConnection(ws, req);
3726
+ });
3727
+ return wss2;
3728
+ }
3729
+ function detectClaudeCli2() {
3730
+ const ClaudeBridgeTemp = require_claude_bridge();
3731
+ const bridge = new ClaudeBridgeTemp();
3732
+ try {
3733
+ require("child_process").execFileSync("which", [bridge.claudeCommand], { stdio: "ignore" });
3734
+ return true;
3735
+ } catch {
3736
+ return false;
3737
+ }
3738
+ }
3739
+ module2.exports = { createTerminalHandler: createTerminalHandler2, createWebSocketHandler: createWebSocketHandler2, detectClaudeCli: detectClaudeCli2, TerminalHandler };
3740
+ }
3741
+ });
3742
+
3743
+ // src/server/launch-bot-entry.ts
3744
+ var import_node_http = __toESM(require("node:http"));
3745
+
3746
+ // src/server/terminal-bridge.ts
3747
+ var { createTerminalHandler, createWebSocketHandler, detectClaudeCli } = require_src();
3748
+ var handler = null;
3749
+ var wss = null;
3750
+ function isTerminalAvailable() {
3751
+ return handler !== null;
3752
+ }
3753
+ function initTerminalBridge(httpServer, workingDir, sessionStorageDir) {
3754
+ try {
3755
+ const cliAvailable = detectClaudeCli();
3756
+ if (!cliAvailable) {
3757
+ console.log("Terminal bridge: Claude CLI not found, terminal features disabled");
3758
+ return false;
3759
+ }
3760
+ handler = createTerminalHandler({ workingDir, sessionStorageDir });
3761
+ wss = createWebSocketHandler(httpServer, handler);
3762
+ console.log("Terminal bridge initialized");
3763
+ return true;
3764
+ } catch (err) {
3765
+ console.error("Failed to initialize terminal bridge:", err);
3766
+ return false;
3767
+ }
3768
+ }
3769
+ function handleTerminalRequest(req, res) {
3770
+ if (!handler) return false;
3771
+ const url = new URL(req.url || "/", "http://localhost");
3772
+ if (url.pathname.startsWith("/terminal/api/")) {
3773
+ return handler.handleRequest(req, res);
3774
+ }
3775
+ if (url.pathname.startsWith("/terminal")) {
3776
+ return handler.serveStatic(req, res);
3777
+ }
3778
+ return false;
3779
+ }
3780
+ function shutdownTerminalBridge() {
3781
+ if (handler) {
3782
+ handler.shutdown();
3783
+ handler = null;
3784
+ }
3785
+ if (wss) {
3786
+ wss.close();
3787
+ wss = null;
3788
+ }
3789
+ }
3790
+
3791
+ // src/server/launch-bot-entry.ts
3792
+ var DEFAULT_PORT = 52849;
3793
+ var TILES = [
3794
+ {
3795
+ id: "claude-code",
3796
+ title: "Claude Code",
3797
+ subtitle: "Interactive Claude session in this project",
3798
+ glyph: "\u2736",
3799
+ href: "/app/claude-code"
3800
+ },
3801
+ {
3802
+ id: "openrouter",
3803
+ title: "OpenRouter",
3804
+ subtitle: "Bring-your-own model \u2014 under construction",
3805
+ glyph: "\u25F4"
3806
+ }
3807
+ ];
3808
+ function logStderr(msg) {
3809
+ process.stderr.write(`[launch-bot] ${msg}
3810
+ `);
3811
+ }
3812
+ function printUsage() {
3813
+ process.stdout.write(
3814
+ [
3815
+ "Usage: launch-bot <command> [options]",
3816
+ "",
3817
+ "Commands:",
3818
+ " serve [--port=<p>] start the AI workbench shell (foreground).",
3819
+ " Port also resolves from $PORT; default 52849.",
3820
+ "",
3821
+ "Environment:",
3822
+ " LAUNCHKIT_BOT_WORKDIR working dir for provider sessions (default: cwd)",
3823
+ " LAUNCHKIT_BOT_SESSIONS session-store dir (default: <workdir>/.launchpod/bot-sessions)",
3824
+ ""
3825
+ ].join("\n")
3826
+ );
3827
+ }
3828
+ function escapeHtml(s) {
3829
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3830
+ }
3831
+ var BASE_STYLE = `
3832
+ :root { color-scheme: dark; }
3833
+ * { box-sizing: border-box; }
3834
+ html, body { height: 100%; }
3835
+ body {
3836
+ margin: 0;
3837
+ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
3838
+ color: #e2e8f0;
3839
+ }
3840
+ `;
3841
+ function renderTile(t) {
3842
+ const inner = `
3843
+ <div class="glyph">${escapeHtml(t.glyph)}</div>
3844
+ <div class="meta">
3845
+ <div class="title">${escapeHtml(t.title)}</div>
3846
+ <div class="subtitle">${escapeHtml(t.subtitle)}</div>
3847
+ </div>
3848
+ ${t.href ? '<div class="badge open">Open &rarr;</div>' : '<div class="badge soon">Soon</div>'}`;
3849
+ return t.href ? `<a class="tile active" href="${escapeHtml(t.href)}">${inner}</a>` : `<div class="tile placeholder" aria-disabled="true">${inner}</div>`;
3850
+ }
3851
+ function renderHome() {
3852
+ const tiles = TILES.map(renderTile).join("\n");
3853
+ return `<!doctype html>
3854
+ <html lang="en">
3855
+ <head>
3856
+ <meta charset="utf-8" />
3857
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
3858
+ <title>launch-bot \xB7 AI workbench</title>
3859
+ <style>
3860
+ ${BASE_STYLE}
3861
+ body { background: radial-gradient(1200px 600px at 50% -10%, #0f1b2d 0%, #060a12 60%) fixed, #060a12; }
3862
+ .wrap { max-width: 760px; margin: 0 auto; padding: 64px 24px; }
3863
+ header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 32px; }
3864
+ h1 { font-size: 20px; font-weight: 600; margin: 0 0 6px; letter-spacing: -0.01em; }
3865
+ .sub { font-size: 13px; color: #7c8aa0; margin: 0; }
3866
+ .controls { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
3867
+ .count { font-size: 12px; color: #94a3b8; white-space: nowrap; padding: 5px 10px; border-radius: 999px; background: rgba(148,163,184,0.1); }
3868
+ .clear {
3869
+ font: inherit; font-size: 12px; cursor: pointer; color: #fda4af;
3870
+ padding: 5px 11px; border-radius: 8px;
3871
+ border: 1px solid rgba(244,63,94,0.3); background: rgba(244,63,94,0.08);
3872
+ transition: border-color .15s ease, opacity .15s ease;
3873
+ }
3874
+ .clear:hover:not(:disabled) { border-color: rgba(244,63,94,0.55); }
3875
+ .clear:disabled { opacity: .4; cursor: default; }
3876
+ .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
3877
+ .tile {
3878
+ display: flex; align-items: center; gap: 14px;
3879
+ padding: 16px; border-radius: 12px; text-decoration: none;
3880
+ border: 1px solid rgba(148,163,184,0.18);
3881
+ background: linear-gradient(160deg, rgba(30,41,59,0.55), rgba(8,12,20,0.7));
3882
+ transition: border-color .15s ease, transform .15s ease;
3883
+ }
3884
+ .tile.active:hover { border-color: rgba(34,211,238,0.45); transform: translateY(-1px); }
3885
+ .tile.placeholder { opacity: .55; }
3886
+ .glyph {
3887
+ flex: 0 0 auto; width: 40px; height: 40px; border-radius: 10px;
3888
+ display: grid; place-items: center; font-size: 20px;
3889
+ background: rgba(34,211,238,0.08); border: 1px solid rgba(34,211,238,0.18); color: #67e8f9;
3890
+ }
3891
+ .placeholder .glyph { background: rgba(148,163,184,0.08); border-color: rgba(148,163,184,0.18); color: #94a3b8; }
3892
+ .meta { flex: 1 1 auto; min-width: 0; }
3893
+ .title { font-size: 14px; font-weight: 600; color: #f1f5f9; }
3894
+ .subtitle { font-size: 12px; color: #7c8aa0; margin-top: 2px; }
3895
+ .badge { flex: 0 0 auto; font-size: 11px; padding: 3px 8px; border-radius: 999px; }
3896
+ .badge.open { color: #67e8f9; background: rgba(34,211,238,0.1); }
3897
+ .badge.soon { color: #94a3b8; background: rgba(148,163,184,0.1); }
3898
+ footer { margin-top: 28px; font-size: 11px; color: #475569; }
3899
+ </style>
3900
+ </head>
3901
+ <body>
3902
+ <div class="wrap">
3903
+ <header>
3904
+ <div>
3905
+ <h1>AI workbench</h1>
3906
+ <p class="sub">Pick a provider to start a session against this project.</p>
3907
+ </div>
3908
+ <div class="controls">
3909
+ <span class="count" id="count">\u2026</span>
3910
+ <button class="clear" id="clearBtn" disabled>Clear sessions</button>
3911
+ </div>
3912
+ </header>
3913
+ <div class="grid">
3914
+ ${tiles}
3915
+ </div>
3916
+ <footer>launch-bot \xB7 providers reach project services via MCP</footer>
3917
+ </div>
3918
+ <script>
3919
+ var SESSIONS_URL = '/terminal/api/sessions/list';
3920
+ function sessionUrl(id) { return '/terminal/api/sessions/' + encodeURIComponent(id); }
3921
+
3922
+ function setCount(n) {
3923
+ document.getElementById('count').textContent = n + (n === 1 ? ' session' : ' sessions');
3924
+ document.getElementById('clearBtn').disabled = n === 0;
3925
+ }
3926
+
3927
+ function loadSessions() {
3928
+ return fetch(SESSIONS_URL)
3929
+ .then(function (r) { return r.ok ? r.json() : { sessions: [] }; })
3930
+ .then(function (d) { var s = (d && d.sessions) || []; setCount(s.length); return s; })
3931
+ .catch(function () { setCount(0); return []; });
3932
+ }
3933
+
3934
+ function clearAll() {
3935
+ var btn = document.getElementById('clearBtn');
3936
+ loadSessions().then(function (sessions) {
3937
+ if (!sessions.length) { return; }
3938
+ if (!window.confirm('Stop and remove all ' + sessions.length + ' running session(s)?')) { return; }
3939
+ btn.disabled = true;
3940
+ btn.textContent = 'Clearing\u2026';
3941
+ Promise.all(sessions.map(function (s) {
3942
+ return fetch(sessionUrl(s.id), { method: 'DELETE' }).catch(function () {});
3943
+ })).then(function () {
3944
+ btn.textContent = 'Clear sessions';
3945
+ loadSessions();
3946
+ });
3947
+ });
3948
+ }
3949
+
3950
+ document.getElementById('clearBtn').addEventListener('click', clearAll);
3951
+ loadSessions();
3952
+ // Refresh when returning to this tab (e.g. back from the terminal).
3953
+ window.addEventListener('pageshow', loadSessions);
3954
+ </script>
3955
+ </body>
3956
+ </html>`;
3957
+ }
3958
+ function renderClaudeApp() {
3959
+ return `<!doctype html>
3960
+ <html lang="en">
3961
+ <head>
3962
+ <meta charset="utf-8" />
3963
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
3964
+ <title>Claude Code \xB7 launch-bot</title>
3965
+ <style>
3966
+ ${BASE_STYLE}
3967
+ body { display: flex; flex-direction: column; background: #060a12; }
3968
+ .bar {
3969
+ flex: 0 0 auto; display: flex; align-items: center; gap: 12px;
3970
+ height: 40px; padding: 0 14px;
3971
+ border-bottom: 1px solid rgba(148,163,184,0.16); background: #0b1220;
3972
+ }
3973
+ .back {
3974
+ display: inline-flex; align-items: center; gap: 6px;
3975
+ font-size: 12px; text-decoration: none; color: #cbd5e1;
3976
+ padding: 4px 10px; border-radius: 8px; border: 1px solid rgba(148,163,184,0.2);
3977
+ transition: border-color .15s ease, color .15s ease;
3978
+ }
3979
+ .back:hover { border-color: rgba(34,211,238,0.45); color: #67e8f9; }
3980
+ .name { font-size: 13px; font-weight: 600; color: #f1f5f9; }
3981
+ .dim { font-size: 12px; color: #64748b; }
3982
+ iframe { flex: 1 1 auto; width: 100%; border: 0; display: block; }
3983
+ </style>
3984
+ </head>
3985
+ <body>
3986
+ <div class="bar">
3987
+ <a class="back" href="/">&larr; Workbench</a>
3988
+ <span class="name">Claude Code</span>
3989
+ <span class="dim">\xB7</span>
3990
+ <span class="dim">interactive session</span>
3991
+ </div>
3992
+ <iframe src="/terminal/" title="Claude Code terminal" allow="clipboard-read; clipboard-write"></iframe>
3993
+ </body>
3994
+ </html>`;
3995
+ }
3996
+ function serveHtml(res, html) {
3997
+ res.statusCode = 200;
3998
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
3999
+ res.end(html);
4000
+ }
4001
+ function parseServeArgs(argv) {
4002
+ let port;
4003
+ for (let i = 0; i < argv.length; i++) {
4004
+ if (argv[i].startsWith("--port=")) {
4005
+ port = Number.parseInt(argv[i].slice("--port=".length), 10);
4006
+ } else if (argv[i] === "--port" && argv[i + 1]) {
4007
+ port = Number.parseInt(argv[++i], 10);
4008
+ }
4009
+ }
4010
+ if (!port && process.env.PORT) {
4011
+ port = Number.parseInt(process.env.PORT, 10);
4012
+ }
4013
+ if (!port || !Number.isFinite(port)) {
4014
+ port = DEFAULT_PORT;
4015
+ }
4016
+ const workingDir = process.env.LAUNCHKIT_BOT_WORKDIR || process.cwd();
4017
+ const sessionStorageDir = process.env.LAUNCHKIT_BOT_SESSIONS || `${workingDir.replace(/\/+$/, "")}/.launchpod/bot-sessions`;
4018
+ return { port, workingDir, sessionStorageDir };
4019
+ }
4020
+ async function runServe(argv) {
4021
+ const { port, workingDir, sessionStorageDir } = parseServeArgs(argv);
4022
+ const server = import_node_http.default.createServer((req, res) => {
4023
+ const path = (req.url ?? "/").split("?")[0];
4024
+ if (path === "/" || path === "/bot" || path === "/bot/") {
4025
+ serveHtml(res, renderHome());
4026
+ return;
4027
+ }
4028
+ if (path === "/app/claude-code" || path === "/app/claude-code/") {
4029
+ serveHtml(res, renderClaudeApp());
4030
+ return;
4031
+ }
4032
+ if (handleTerminalRequest(req, res)) return;
4033
+ res.statusCode = 404;
4034
+ res.setHeader("Content-Type", "application/json");
4035
+ res.end(JSON.stringify({ error: "not_found", path }));
4036
+ });
4037
+ const ok = initTerminalBridge(server, workingDir, sessionStorageDir);
4038
+ if (!ok || !isTerminalAvailable()) {
4039
+ throw new Error(
4040
+ "terminal bridge failed to initialize \u2014 is the `claude` CLI installed and on PATH?"
4041
+ );
4042
+ }
4043
+ await new Promise((resolve, reject) => {
4044
+ server.once("error", reject);
4045
+ server.listen(port, "127.0.0.1", () => resolve());
4046
+ });
4047
+ logStderr(`workbench on http://127.0.0.1:${port}/ (workdir: ${workingDir})`);
4048
+ const shutdown = (signal) => {
4049
+ logStderr(`received ${signal}, shutting down`);
4050
+ shutdownTerminalBridge();
4051
+ server.close(() => process.exit(0));
4052
+ setTimeout(() => process.exit(0), 3e3).unref();
4053
+ };
4054
+ process.on("SIGINT", shutdown);
4055
+ process.on("SIGTERM", shutdown);
4056
+ process.on("SIGHUP", shutdown);
4057
+ }
4058
+ async function main() {
4059
+ const argv = process.argv.slice(2);
4060
+ const subcommand = argv[0];
4061
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
4062
+ printUsage();
4063
+ return;
4064
+ }
4065
+ switch (subcommand) {
4066
+ case "serve":
4067
+ await runServe(argv.slice(1));
4068
+ return;
4069
+ default:
4070
+ logStderr(`unknown command "${subcommand}"`);
4071
+ printUsage();
4072
+ process.exitCode = 1;
4073
+ }
4074
+ }
4075
+ main().catch((err) => {
4076
+ logStderr(err instanceof Error ? err.message : String(err));
4077
+ process.exit(1);
4078
+ });