daedalus-cli 3.75.0 → 3.76.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +1 -1
  3. package/dist/agents/roles.d.ts.map +1 -1
  4. package/dist/agents/roles.js +4 -1
  5. package/dist/agents/roles.js.map +1 -1
  6. package/dist/commands/webui.js +5 -5
  7. package/dist/commands/webui.js.map +1 -1
  8. package/dist/marathon/engine.js +1 -1
  9. package/dist/marathon/engine.js.map +1 -1
  10. package/dist/marathon/evaluator.d.ts +1 -0
  11. package/dist/marathon/evaluator.d.ts.map +1 -1
  12. package/dist/marathon/evaluator.js +33 -5
  13. package/dist/marathon/evaluator.js.map +1 -1
  14. package/dist/marathon/planner.d.ts.map +1 -1
  15. package/dist/marathon/planner.js +2 -1
  16. package/dist/marathon/planner.js.map +1 -1
  17. package/dist/tools/builtin/files.d.ts.map +1 -1
  18. package/dist/tools/builtin/files.js +11 -1
  19. package/dist/tools/builtin/files.js.map +1 -1
  20. package/dist/webui/install.test.d.ts +2 -0
  21. package/dist/webui/install.test.d.ts.map +1 -0
  22. package/dist/webui/install.test.js +43 -0
  23. package/dist/webui/install.test.js.map +1 -0
  24. package/dist/webui/public/apple-touch-icon.png +0 -0
  25. package/dist/webui/public/icon-192.png +0 -0
  26. package/dist/webui/public/icon-512.png +0 -0
  27. package/dist/webui/public/index.html +116 -32
  28. package/dist/webui/public/logo.png +0 -0
  29. package/dist/webui/public/manifest.json +25 -0
  30. package/dist/webui/public/script.js +387 -8
  31. package/dist/webui/public/styles.css +773 -3
  32. package/dist/webui/public/sw.js +81 -0
  33. package/dist/webui/public/types.d.ts +9 -0
  34. package/dist/webui/public/types.d.ts.map +1 -0
  35. package/dist/webui/public/types.js +2 -0
  36. package/dist/webui/public/types.js.map +1 -0
  37. package/dist/webui/public/types.ts +8 -0
  38. package/dist/webui/qr.d.ts +23 -0
  39. package/dist/webui/qr.d.ts.map +1 -0
  40. package/dist/webui/qr.js +60 -0
  41. package/dist/webui/qr.js.map +1 -0
  42. package/dist/webui/qr.test.d.ts +2 -0
  43. package/dist/webui/qr.test.d.ts.map +1 -0
  44. package/dist/webui/qr.test.js +34 -0
  45. package/dist/webui/qr.test.js.map +1 -0
  46. package/dist/webui/server.d.ts +3 -2
  47. package/dist/webui/server.d.ts.map +1 -1
  48. package/dist/webui/server.js +93 -36
  49. package/dist/webui/server.js.map +1 -1
  50. package/dist/webui/server.test.js +33 -1
  51. package/dist/webui/server.test.js.map +1 -1
  52. package/dist/webui/styles.test.d.ts +2 -0
  53. package/dist/webui/styles.test.d.ts.map +1 -0
  54. package/dist/webui/styles.test.js +19 -0
  55. package/dist/webui/styles.test.js.map +1 -0
  56. package/dist/webui/ws.d.ts +17 -0
  57. package/dist/webui/ws.d.ts.map +1 -0
  58. package/dist/webui/ws.js +57 -0
  59. package/dist/webui/ws.js.map +1 -0
  60. package/dist/webui/ws.test.d.ts +2 -0
  61. package/dist/webui/ws.test.d.ts.map +1 -0
  62. package/dist/webui/ws.test.js +66 -0
  63. package/dist/webui/ws.test.js.map +1 -0
  64. package/package.json +4 -1
@@ -0,0 +1,81 @@
1
+ const CACHE_NAME = 'daedalus-core-v1';
2
+
3
+ const CORE_ASSETS = [
4
+ '/index.html',
5
+ '/styles.css',
6
+ '/script.js',
7
+ '/manifest.json',
8
+ ];
9
+
10
+ async function handleInstall(event) {
11
+ try {
12
+ await event.waitUntil(
13
+ caches.open(CACHE_NAME).then((cache) => {
14
+ return cache.addAll(CORE_ASSETS);
15
+ })
16
+ );
17
+ self.skipWaiting();
18
+ } catch (error) {
19
+ console.error('[ServiceWorker] Install failed:', error);
20
+ }
21
+ }
22
+
23
+ async function handleActivate(event) {
24
+ try {
25
+ await event.waitUntil(
26
+ caches.keys().then((keys) => {
27
+ return Promise.all(
28
+ keys
29
+ .filter((key) => key !== CACHE_NAME)
30
+ .map((key) => caches.delete(key))
31
+ );
32
+ })
33
+ );
34
+ self.clients.claim();
35
+ } catch (error) {
36
+ console.error('[ServiceWorker] Activate failed:', error);
37
+ }
38
+ }
39
+
40
+ async function handleFetch(event) {
41
+ try {
42
+ const cachedResponse = await caches.match(event.request);
43
+ if (cachedResponse) {
44
+ return cachedResponse;
45
+ }
46
+
47
+ const networkResponse = await fetch(event.request);
48
+ if (networkResponse && networkResponse.status === 200) {
49
+ const responseClone = networkResponse.clone();
50
+ await caches.open(CACHE_NAME).then((cache) => {
51
+ return cache.put(event.request, responseClone);
52
+ });
53
+ }
54
+
55
+ return networkResponse;
56
+ } catch (error) {
57
+ console.error('[ServiceWorker] Fetch failed:', error);
58
+
59
+ // For navigation requests, return a fallback offline page
60
+ if (event.request.mode === 'navigate') {
61
+ const cachedIndex = await caches.match('/index.html');
62
+ if (cachedIndex) {
63
+ return cachedIndex;
64
+ }
65
+ return new Response(
66
+ '<!DOCTYPE html><html><head><title>Offline</title></head><body><h1>Offline</h1><p>The application is currently unavailable. Please check your connection.</p></body></html>',
67
+ {
68
+ status: 503,
69
+ statusText: 'Service Unavailable',
70
+ headers: { 'Content-Type': 'text/html' }
71
+ }
72
+ );
73
+ }
74
+
75
+ return new Response('Offline', { status: 503, statusText: 'Service Unavailable' });
76
+ }
77
+ }
78
+
79
+ self.addEventListener('install', handleInstall);
80
+ self.addEventListener('activate', handleActivate);
81
+ self.addEventListener('fetch', handleFetch);
@@ -0,0 +1,9 @@
1
+ export interface TouchTargetConfig {
2
+ /** CSS selector for the interactive element (e.g., '.btn-primary', '#nav-link') */
3
+ selector: string;
4
+ /** Minimum width and height in pixels. Default is 48. */
5
+ minSizePx?: number;
6
+ /** Optional touch‑action CSS value. Defaults to 'manipulation'. */
7
+ touchAction?: string;
8
+ }
9
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/webui/public/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/webui/public/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ export interface TouchTargetConfig {
2
+ /** CSS selector for the interactive element (e.g., '.btn-primary', '#nav-link') */
3
+ selector: string;
4
+ /** Minimum width and height in pixels. Default is 48. */
5
+ minSizePx?: number;
6
+ /** Optional touch‑action CSS value. Defaults to 'manipulation'. */
7
+ touchAction?: string;
8
+ }
@@ -0,0 +1,23 @@
1
+ import QRCode from 'qrcode';
2
+ /**
3
+ * Detect primary local network IPv4 address for mobile pairing over Wi-Fi / LAN.
4
+ */
5
+ export declare function getLocalIpAddress(): string;
6
+ /**
7
+ * Generate a QR code PNG buffer encoding the given WebSocket or Web URL.
8
+ * @param url - The URL to encode (e.g., http://192.168.1.10:3888)
9
+ * @param options - Optional qrcode options for size and error correction
10
+ * @returns Promise resolving to a PNG buffer
11
+ */
12
+ export declare function generateQrCode(url: string, options?: QRCode.QRCodeToBufferOptions): Promise<Buffer>;
13
+ /**
14
+ * Return the local WebSocket URL for the given host and port.
15
+ * Falls back to process.env.WS_URL or ws://<host>:<port>.
16
+ */
17
+ export declare function getWebSocketUrl(host?: string, port?: number): string;
18
+ /**
19
+ * Return the Web pairing URL for mobile camera scanning.
20
+ * Resolves to the local LAN IP (e.g., http://192.168.x.x:3888) or process.env.WEBUI_URL.
21
+ */
22
+ export declare function getWebPairingUrl(host?: string, port?: number): string;
23
+ //# sourceMappingURL=qr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr.d.ts","sourceRoot":"","sources":["../../src/webui/qr.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAG5B;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAU1C;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,CAAC,qBAAqB,GACrC,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,SAAc,EAAE,IAAI,SAAO,GAAG,MAAM,CAMvE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,SAAO,GAAG,MAAM,CASnE"}
@@ -0,0 +1,60 @@
1
+ import QRCode from 'qrcode';
2
+ import os from 'node:os';
3
+ /**
4
+ * Detect primary local network IPv4 address for mobile pairing over Wi-Fi / LAN.
5
+ */
6
+ export function getLocalIpAddress() {
7
+ const interfaces = os.networkInterfaces();
8
+ for (const name of Object.keys(interfaces)) {
9
+ for (const net of interfaces[name] || []) {
10
+ if (net.family === 'IPv4' && !net.internal) {
11
+ return net.address;
12
+ }
13
+ }
14
+ }
15
+ return '127.0.0.1';
16
+ }
17
+ /**
18
+ * Generate a QR code PNG buffer encoding the given WebSocket or Web URL.
19
+ * @param url - The URL to encode (e.g., http://192.168.1.10:3888)
20
+ * @param options - Optional qrcode options for size and error correction
21
+ * @returns Promise resolving to a PNG buffer
22
+ */
23
+ export async function generateQrCode(url, options) {
24
+ return QRCode.toBuffer(url, {
25
+ type: 'png',
26
+ margin: 2,
27
+ scale: 8,
28
+ color: {
29
+ dark: '#f5c358', // glowing mythic gold pixels
30
+ light: '#0b0f19', // deep obsidian dark void background
31
+ },
32
+ ...options,
33
+ });
34
+ }
35
+ /**
36
+ * Return the local WebSocket URL for the given host and port.
37
+ * Falls back to process.env.WS_URL or ws://<host>:<port>.
38
+ */
39
+ export function getWebSocketUrl(host = '127.0.0.1', port = 3888) {
40
+ const envUrl = process.env.WS_URL;
41
+ if (envUrl && (envUrl.startsWith('ws://') || envUrl.startsWith('wss://'))) {
42
+ return envUrl;
43
+ }
44
+ return `ws://${host}:${port}`;
45
+ }
46
+ /**
47
+ * Return the Web pairing URL for mobile camera scanning.
48
+ * Resolves to the local LAN IP (e.g., http://192.168.x.x:3888) or process.env.WEBUI_URL.
49
+ */
50
+ export function getWebPairingUrl(host, port = 3888) {
51
+ const envUrl = process.env.WEBUI_URL;
52
+ if (envUrl && (envUrl.startsWith('http://') || envUrl.startsWith('https://'))) {
53
+ return envUrl;
54
+ }
55
+ const effectiveHost = (!host || host === '0.0.0.0' || host === '127.0.0.1' || host === 'localhost')
56
+ ? getLocalIpAddress()
57
+ : host;
58
+ return `http://${effectiveHost}:${port}`;
59
+ }
60
+ //# sourceMappingURL=qr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr.js","sourceRoot":"","sources":["../../src/webui/qr.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,UAAU,GAAG,EAAE,CAAC,iBAAiB,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3C,KAAK,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACzC,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;gBAC3C,OAAO,GAAG,CAAC,OAAO,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAW,EACX,OAAsC;IAEtC,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE;QAC1B,IAAI,EAAE,KAAK;QACX,MAAM,EAAE,CAAC;QACT,KAAK,EAAE,CAAC;QACR,KAAK,EAAE;YACL,IAAI,EAAE,SAAS,EAAE,6BAA6B;YAC9C,KAAK,EAAE,SAAS,EAAE,qCAAqC;SACxD;QACD,GAAG,OAAO;KACX,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAI,GAAG,WAAW,EAAE,IAAI,GAAG,IAAI;IAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;IAClC,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC1E,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,QAAQ,IAAI,IAAI,IAAI,EAAE,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAa,EAAE,IAAI,GAAG,IAAI;IACzD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC;IACrC,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;QAC9E,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,WAAW,IAAI,IAAI,KAAK,WAAW,CAAC;QACjG,CAAC,CAAC,iBAAiB,EAAE;QACrB,CAAC,CAAC,IAAI,CAAC;IACT,OAAO,UAAU,aAAa,IAAI,IAAI,EAAE,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=qr.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr.test.d.ts","sourceRoot":"","sources":["../../src/webui/qr.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateQrCode, getWebSocketUrl } from './qr.js';
3
+ describe('QR Code Generation & WebSocket Pairing (M-5 / M-6)', () => {
4
+ it('generates a valid PNG buffer encoding a WebSocket URL', async () => {
5
+ const url = 'ws://192.168.1.100:3888';
6
+ const buffer = await generateQrCode(url);
7
+ expect(buffer).toBeInstanceOf(Buffer);
8
+ expect(buffer.length).toBeGreaterThan(50);
9
+ // Check PNG header (0x89 0x50 0x4E 0x47)
10
+ expect(buffer[0]).toBe(0x89);
11
+ expect(buffer[1]).toBe(0x50);
12
+ expect(buffer[2]).toBe(0x4E);
13
+ expect(buffer[3]).toBe(0x47);
14
+ });
15
+ it('constructs default WebSocket URL for host and port', () => {
16
+ const wsUrl = getWebSocketUrl('127.0.0.1', 3888);
17
+ expect(wsUrl).toBe('ws://127.0.0.1:3888');
18
+ });
19
+ it('respects WS_URL environment override when set', () => {
20
+ const prevEnv = process.env.WS_URL;
21
+ process.env.WS_URL = 'wss://my-custom-tailscale.ts.net';
22
+ try {
23
+ const wsUrl = getWebSocketUrl('127.0.0.1', 3888);
24
+ expect(wsUrl).toBe('wss://my-custom-tailscale.ts.net');
25
+ }
26
+ finally {
27
+ if (prevEnv)
28
+ process.env.WS_URL = prevEnv;
29
+ else
30
+ delete process.env.WS_URL;
31
+ }
32
+ });
33
+ });
34
+ //# sourceMappingURL=qr.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr.test.js","sourceRoot":"","sources":["../../src/webui/qr.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1D,QAAQ,CAAC,oDAAoD,EAAE,GAAG,EAAE;IAClE,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,MAAM,GAAG,GAAG,yBAAyB,CAAC;QACtC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;QAC1C,yCAAyC;QACzC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,KAAK,GAAG,eAAe,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QACjD,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,MAAM,GAAG,kCAAkC,CAAC;QACxD,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,eAAe,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACjD,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QACzD,CAAC;gBAAS,CAAC;YACT,IAAI,OAAO;gBAAE,OAAO,CAAC,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC;;gBACrC,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;QACjC,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,7 +1,8 @@
1
1
  import { IncomingMessage, ServerResponse, Server } from 'node:http';
2
2
  import type { WebuiChatMessageEvent, WebuiChatRequest } from './types.js';
3
+ import { startWebSocketServer, broadcastMilestone, closeWebSocketServer } from './ws.js';
3
4
  declare const PORT = 3888;
4
- declare const HOST = "127.0.0.1";
5
+ declare const HOST = "0.0.0.0";
5
6
  export declare function getTelemetryRate(): number;
6
7
  export declare function setTelemetryRate(ms: number): number;
7
8
  export type HistoryProvider = () => Array<{
@@ -46,5 +47,5 @@ export declare function killProcessOnPort(port: number): void;
46
47
  declare let server: Server;
47
48
  export declare function startServer(port?: number, host?: string): Promise<Server>;
48
49
  export declare function stopServer(): Promise<void>;
49
- export { server, PORT, HOST };
50
+ export { server, PORT, HOST, startWebSocketServer, broadcastMilestone, closeWebSocketServer };
50
51
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/webui/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/webui/server.ts"],"names":[],"mappings":"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"}
@@ -5,10 +5,12 @@ import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { loadProfile } from '../profile.js';
8
+ import { generateQrCode, getWebPairingUrl } from './qr.js';
9
+ import { startWebSocketServer, broadcastMilestone, closeWebSocketServer } from './ws.js';
8
10
  const __filename = fileURLToPath(import.meta.url);
9
11
  const __dirname = path.dirname(__filename);
10
12
  const PORT = 3888;
11
- const HOST = '127.0.0.1';
13
+ const HOST = '0.0.0.0';
12
14
  let telemetryIntervalMs = 1000;
13
15
  const activeClientRecords = new Set();
14
16
  let prevCpus = os.cpus();
@@ -93,12 +95,27 @@ export function registerModelProvider(provider) {
93
95
  activeModelProvider = provider;
94
96
  }
95
97
  function resolvePublicAsset(filename) {
96
- const primary = path.join(__dirname, 'public', filename);
97
- if (fs.existsSync(primary))
98
- return primary;
99
- const fallback = path.join(__dirname, '..', '..', 'src', 'webui', 'public', filename);
100
- if (fs.existsSync(fallback))
101
- return fallback;
98
+ const safeFilename = path.normalize(filename).replace(/^(\.\.[\/\\])+/, '');
99
+ const primary = path.join(__dirname, 'public', safeFilename);
100
+ if (fs.existsSync(primary)) {
101
+ try {
102
+ if (fs.statSync(primary).isFile())
103
+ return primary;
104
+ }
105
+ catch {
106
+ return primary;
107
+ }
108
+ }
109
+ const fallback = path.join(__dirname, '..', '..', 'src', 'webui', 'public', safeFilename);
110
+ if (fs.existsSync(fallback)) {
111
+ try {
112
+ if (fs.statSync(fallback).isFile())
113
+ return fallback;
114
+ }
115
+ catch {
116
+ return fallback;
117
+ }
118
+ }
102
119
  return null;
103
120
  }
104
121
  let activeChatHandler = null;
@@ -247,11 +264,15 @@ function handleChatRequest(req, res) {
247
264
  }
248
265
  export function handleRequest(req, res) {
249
266
  try {
250
- if (req.method === 'POST' && req.url === '/api/chat') {
267
+ const rawUrl = req.url || '/';
268
+ const hostHeader = req.headers?.host || `${HOST}:${PORT}`;
269
+ const parsedUrl = new URL(rawUrl, `http://${hostHeader}`);
270
+ const pathname = parsedUrl.pathname;
271
+ if (req.method === 'POST' && pathname === '/api/chat') {
251
272
  handleChatRequest(req, res);
252
273
  return;
253
274
  }
254
- if (req.method === 'GET' && req.url === '/') {
275
+ if (req.method === 'GET' && pathname === '/') {
255
276
  const htmlPath = resolvePublicAsset('index.html');
256
277
  if (htmlPath) {
257
278
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
@@ -262,23 +283,33 @@ export function handleRequest(req, res) {
262
283
  res.end('OK');
263
284
  return;
264
285
  }
265
- if (req.method === 'GET' && (req.url === '/styles.css' || req.url === '/script.js' || req.url === '/marked.min.js' || req.url === '/favicon.svg' || req.url === '/favicon.ico')) {
266
- const filename = req.url.slice(1);
267
- const assetPath = resolvePublicAsset(filename);
268
- if (assetPath) {
269
- const mimeTypes = {
270
- '.css': 'text/css; charset=utf-8',
271
- '.js': 'application/javascript; charset=utf-8',
272
- '.svg': 'image/svg+xml; charset=utf-8',
273
- '.ico': 'image/x-icon',
274
- };
275
- const ext = path.extname(assetPath);
276
- res.writeHead(200, { 'Content-Type': mimeTypes[ext] || 'application/octet-stream' });
277
- res.end(fs.readFileSync(assetPath));
278
- return;
286
+ if (req.method === 'GET' && !pathname.startsWith('/api') && pathname !== '/telemetry' && pathname !== '/qr' && pathname !== '/') {
287
+ const urlPath = pathname.replace(/^\/+/, '');
288
+ const ext = path.extname(urlPath).toLowerCase();
289
+ const mimeTypes = {
290
+ '.html': 'text/html; charset=utf-8',
291
+ '.css': 'text/css; charset=utf-8',
292
+ '.js': 'application/javascript; charset=utf-8',
293
+ '.svg': 'image/svg+xml; charset=utf-8',
294
+ '.ico': 'image/x-icon',
295
+ '.json': 'application/manifest+json; charset=utf-8',
296
+ '.webmanifest': 'application/manifest+json; charset=utf-8',
297
+ '.png': 'image/png',
298
+ '.jpg': 'image/jpeg',
299
+ '.jpeg': 'image/jpeg',
300
+ '.woff': 'font/woff',
301
+ '.woff2': 'font/woff2',
302
+ };
303
+ if (ext && mimeTypes[ext]) {
304
+ const assetPath = resolvePublicAsset(urlPath);
305
+ if (assetPath) {
306
+ res.writeHead(200, { 'Content-Type': mimeTypes[ext] || 'application/octet-stream' });
307
+ res.end(fs.readFileSync(assetPath));
308
+ return;
309
+ }
279
310
  }
280
311
  }
281
- if (req.method === 'GET' && req.url === '/telemetry') {
312
+ if (req.method === 'GET' && pathname === '/telemetry') {
282
313
  res.writeHead(200, {
283
314
  'Content-Type': 'text/event-stream',
284
315
  'Cache-Control': 'no-cache',
@@ -301,7 +332,7 @@ export function handleRequest(req, res) {
301
332
  });
302
333
  return;
303
334
  }
304
- if (req.method === 'GET' && req.url === '/api/files') {
335
+ if (req.method === 'GET' && pathname === '/api/files') {
305
336
  const cwd = process.cwd();
306
337
  const gitignored = parseGitignore(cwd);
307
338
  const tree = getProjectTree(cwd, cwd, 0, gitignored);
@@ -309,19 +340,19 @@ export function handleRequest(req, res) {
309
340
  res.end(JSON.stringify({ cwd: path.basename(cwd), tree }));
310
341
  return;
311
342
  }
312
- if (req.method === 'GET' && req.url === '/api/history') {
343
+ if (req.method === 'GET' && pathname === '/api/history') {
313
344
  const history = activeHistoryProvider ? activeHistoryProvider() : [];
314
345
  res.writeHead(200, { 'Content-Type': 'application/json' });
315
346
  res.end(JSON.stringify({ history }));
316
347
  return;
317
348
  }
318
- if (req.method === 'GET' && req.url === '/api/context') {
349
+ if (req.method === 'GET' && pathname === '/api/context') {
319
350
  const files = activeContextFilesProvider ? activeContextFilesProvider.getFiles() : [];
320
351
  res.writeHead(200, { 'Content-Type': 'application/json' });
321
352
  res.end(JSON.stringify({ files }));
322
353
  return;
323
354
  }
324
- if (req.method === 'DELETE' && req.url === '/api/context') {
355
+ if (req.method === 'DELETE' && pathname === '/api/context') {
325
356
  parseJsonBody(req)
326
357
  .then(data => {
327
358
  const removed = activeContextFilesProvider && data.file ? activeContextFilesProvider.removeFile(data.file) : false;
@@ -334,13 +365,13 @@ export function handleRequest(req, res) {
334
365
  });
335
366
  return;
336
367
  }
337
- if (req.method === 'GET' && req.url === '/api/sessions') {
368
+ if (req.method === 'GET' && pathname === '/api/sessions') {
338
369
  const sessions = activeSessionProvider ? activeSessionProvider.listSessions() : [];
339
370
  res.writeHead(200, { 'Content-Type': 'application/json' });
340
371
  res.end(JSON.stringify({ sessions }));
341
372
  return;
342
373
  }
343
- if (req.method === 'POST' && req.url === '/api/sessions/resume') {
374
+ if (req.method === 'POST' && pathname === '/api/sessions/resume') {
344
375
  parseJsonBody(req)
345
376
  .then(async (data) => {
346
377
  if (!activeSessionProvider || !data.sessionId) {
@@ -358,7 +389,7 @@ export function handleRequest(req, res) {
358
389
  });
359
390
  return;
360
391
  }
361
- if (req.method === 'POST' && req.url === '/api/sessions/new') {
392
+ if (req.method === 'POST' && pathname === '/api/sessions/new') {
362
393
  (async () => {
363
394
  if (!activeSessionProvider) {
364
395
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -374,7 +405,7 @@ export function handleRequest(req, res) {
374
405
  });
375
406
  return;
376
407
  }
377
- if (req.method === 'DELETE' && req.url === '/api/sessions') {
408
+ if (req.method === 'DELETE' && pathname === '/api/sessions') {
378
409
  parseJsonBody(req)
379
410
  .then(async (data) => {
380
411
  if (!activeSessionProvider || !data.sessionId) {
@@ -392,20 +423,20 @@ export function handleRequest(req, res) {
392
423
  });
393
424
  return;
394
425
  }
395
- if (req.method === 'GET' && req.url === '/api/models') {
426
+ if (req.method === 'GET' && pathname === '/api/models') {
396
427
  const activeModel = activeModelProvider ? activeModelProvider.getActiveModel() : 'auto';
397
428
  const availableModels = activeModelProvider ? activeModelProvider.getAvailableModels() : [];
398
429
  res.writeHead(200, { 'Content-Type': 'application/json' });
399
430
  res.end(JSON.stringify({ activeModel, availableModels }));
400
431
  return;
401
432
  }
402
- if (req.method === 'GET' && req.url === '/api/profile') {
433
+ if (req.method === 'GET' && pathname === '/api/profile') {
403
434
  const profile = loadProfile();
404
435
  res.writeHead(200, { 'Content-Type': 'application/json' });
405
436
  res.end(JSON.stringify({ name: profile.name || '', bio: profile.bio || '', style: profile.style || '' }));
406
437
  return;
407
438
  }
408
- if (req.method === 'POST' && req.url === '/api/models/switch') {
439
+ if (req.method === 'POST' && pathname === '/api/models/switch') {
409
440
  parseJsonBody(req)
410
441
  .then(async (data) => {
411
442
  if (!activeModelProvider || !data.model) {
@@ -423,6 +454,29 @@ export function handleRequest(req, res) {
423
454
  });
424
455
  return;
425
456
  }
457
+ if (req.method === 'GET' && pathname === '/api/pairing-url') {
458
+ const url = getWebPairingUrl(HOST, PORT);
459
+ res.writeHead(200, { 'Content-Type': 'application/json' });
460
+ res.end(JSON.stringify({ url }));
461
+ return;
462
+ }
463
+ if (req.method === 'GET' && pathname === '/api/qr') {
464
+ const customUrl = parsedUrl.searchParams.get('url');
465
+ const targetUrl = customUrl || getWebPairingUrl(HOST, PORT);
466
+ generateQrCode(targetUrl)
467
+ .then(buf => {
468
+ res.writeHead(200, {
469
+ 'Content-Type': 'image/png',
470
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
471
+ });
472
+ res.end(buf);
473
+ })
474
+ .catch(err => {
475
+ res.writeHead(500, { 'Content-Type': 'application/json' });
476
+ res.end(JSON.stringify({ error: err.message || 'QR generation failed' }));
477
+ });
478
+ return;
479
+ }
426
480
  res.writeHead(404, { 'Content-Type': 'text/plain' });
427
481
  res.end('Not Found');
428
482
  }
@@ -481,6 +535,7 @@ export function startServer(port = PORT, host = HOST) {
481
535
  server.once('error', errorHandler);
482
536
  server.listen(port, host, () => {
483
537
  server.removeListener('error', errorHandler);
538
+ startWebSocketServer(server);
484
539
  console.info(`[webui] Server listening on http://${host}:${port}`);
485
540
  resolve(server);
486
541
  });
@@ -488,6 +543,7 @@ export function startServer(port = PORT, host = HOST) {
488
543
  }
489
544
  export function stopServer() {
490
545
  return new Promise((resolve, reject) => {
546
+ closeWebSocketServer();
491
547
  for (const record of activeClientRecords) {
492
548
  try {
493
549
  clearInterval(record.intervalId);
@@ -512,6 +568,7 @@ export function stopServer() {
512
568
  }
513
569
  // Auto-cleanup on CLI exit
514
570
  process.on('exit', () => {
571
+ closeWebSocketServer();
515
572
  if (server && server.listening) {
516
573
  try {
517
574
  server.close();
@@ -519,5 +576,5 @@ process.on('exit', () => {
519
576
  catch { /* best-effort */ }
520
577
  }
521
578
  });
522
- export { server, PORT, HOST };
579
+ export { server, PORT, HOST, startWebSocketServer, broadcastMilestone, closeWebSocketServer };
523
580
  //# sourceMappingURL=server.js.map