daedalus-cli 3.74.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 (70) hide show
  1. package/CHANGELOG.md +14 -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/formatting.d.ts +1 -1
  9. package/dist/formatting.d.ts.map +1 -1
  10. package/dist/formatting.js +6 -2
  11. package/dist/formatting.js.map +1 -1
  12. package/dist/marathon/engine.js +1 -1
  13. package/dist/marathon/engine.js.map +1 -1
  14. package/dist/marathon/evaluator.d.ts +1 -0
  15. package/dist/marathon/evaluator.d.ts.map +1 -1
  16. package/dist/marathon/evaluator.js +33 -5
  17. package/dist/marathon/evaluator.js.map +1 -1
  18. package/dist/marathon/planner.d.ts.map +1 -1
  19. package/dist/marathon/planner.js +2 -1
  20. package/dist/marathon/planner.js.map +1 -1
  21. package/dist/repl.js +1 -1
  22. package/dist/repl.js.map +1 -1
  23. package/dist/tools/builtin/files.d.ts.map +1 -1
  24. package/dist/tools/builtin/files.js +11 -1
  25. package/dist/tools/builtin/files.js.map +1 -1
  26. package/dist/webui/install.test.d.ts +2 -0
  27. package/dist/webui/install.test.d.ts.map +1 -0
  28. package/dist/webui/install.test.js +43 -0
  29. package/dist/webui/install.test.js.map +1 -0
  30. package/dist/webui/public/apple-touch-icon.png +0 -0
  31. package/dist/webui/public/icon-192.png +0 -0
  32. package/dist/webui/public/icon-512.png +0 -0
  33. package/dist/webui/public/index.html +116 -32
  34. package/dist/webui/public/logo.png +0 -0
  35. package/dist/webui/public/manifest.json +25 -0
  36. package/dist/webui/public/script.js +404 -9
  37. package/dist/webui/public/styles.css +773 -3
  38. package/dist/webui/public/sw.js +81 -0
  39. package/dist/webui/public/types.d.ts +9 -0
  40. package/dist/webui/public/types.d.ts.map +1 -0
  41. package/dist/webui/public/types.js +2 -0
  42. package/dist/webui/public/types.js.map +1 -0
  43. package/dist/webui/public/types.ts +8 -0
  44. package/dist/webui/qr.d.ts +23 -0
  45. package/dist/webui/qr.d.ts.map +1 -0
  46. package/dist/webui/qr.js +60 -0
  47. package/dist/webui/qr.js.map +1 -0
  48. package/dist/webui/qr.test.d.ts +2 -0
  49. package/dist/webui/qr.test.d.ts.map +1 -0
  50. package/dist/webui/qr.test.js +34 -0
  51. package/dist/webui/qr.test.js.map +1 -0
  52. package/dist/webui/server.d.ts +3 -2
  53. package/dist/webui/server.d.ts.map +1 -1
  54. package/dist/webui/server.js +99 -35
  55. package/dist/webui/server.js.map +1 -1
  56. package/dist/webui/server.test.js +33 -1
  57. package/dist/webui/server.test.js.map +1 -1
  58. package/dist/webui/styles.test.d.ts +2 -0
  59. package/dist/webui/styles.test.d.ts.map +1 -0
  60. package/dist/webui/styles.test.js +19 -0
  61. package/dist/webui/styles.test.js.map +1 -0
  62. package/dist/webui/ws.d.ts +17 -0
  63. package/dist/webui/ws.d.ts.map +1 -0
  64. package/dist/webui/ws.js +57 -0
  65. package/dist/webui/ws.js.map +1 -0
  66. package/dist/webui/ws.test.d.ts +2 -0
  67. package/dist/webui/ws.test.d.ts.map +1 -0
  68. package/dist/webui/ws.test.js +66 -0
  69. package/dist/webui/ws.test.js.map +1 -0
  70. 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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"}
@@ -4,10 +4,13 @@ import fs from 'node:fs';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
+ import { loadProfile } from '../profile.js';
8
+ import { generateQrCode, getWebPairingUrl } from './qr.js';
9
+ import { startWebSocketServer, broadcastMilestone, closeWebSocketServer } from './ws.js';
7
10
  const __filename = fileURLToPath(import.meta.url);
8
11
  const __dirname = path.dirname(__filename);
9
12
  const PORT = 3888;
10
- const HOST = '127.0.0.1';
13
+ const HOST = '0.0.0.0';
11
14
  let telemetryIntervalMs = 1000;
12
15
  const activeClientRecords = new Set();
13
16
  let prevCpus = os.cpus();
@@ -92,12 +95,27 @@ export function registerModelProvider(provider) {
92
95
  activeModelProvider = provider;
93
96
  }
94
97
  function resolvePublicAsset(filename) {
95
- const primary = path.join(__dirname, 'public', filename);
96
- if (fs.existsSync(primary))
97
- return primary;
98
- const fallback = path.join(__dirname, '..', '..', 'src', 'webui', 'public', filename);
99
- if (fs.existsSync(fallback))
100
- 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
+ }
101
119
  return null;
102
120
  }
103
121
  let activeChatHandler = null;
@@ -246,11 +264,15 @@ function handleChatRequest(req, res) {
246
264
  }
247
265
  export function handleRequest(req, res) {
248
266
  try {
249
- 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') {
250
272
  handleChatRequest(req, res);
251
273
  return;
252
274
  }
253
- if (req.method === 'GET' && req.url === '/') {
275
+ if (req.method === 'GET' && pathname === '/') {
254
276
  const htmlPath = resolvePublicAsset('index.html');
255
277
  if (htmlPath) {
256
278
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
@@ -261,23 +283,33 @@ export function handleRequest(req, res) {
261
283
  res.end('OK');
262
284
  return;
263
285
  }
264
- 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')) {
265
- const filename = req.url.slice(1);
266
- const assetPath = resolvePublicAsset(filename);
267
- if (assetPath) {
268
- const mimeTypes = {
269
- '.css': 'text/css; charset=utf-8',
270
- '.js': 'application/javascript; charset=utf-8',
271
- '.svg': 'image/svg+xml; charset=utf-8',
272
- '.ico': 'image/x-icon',
273
- };
274
- const ext = path.extname(assetPath);
275
- res.writeHead(200, { 'Content-Type': mimeTypes[ext] || 'application/octet-stream' });
276
- res.end(fs.readFileSync(assetPath));
277
- 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
+ }
278
310
  }
279
311
  }
280
- if (req.method === 'GET' && req.url === '/telemetry') {
312
+ if (req.method === 'GET' && pathname === '/telemetry') {
281
313
  res.writeHead(200, {
282
314
  'Content-Type': 'text/event-stream',
283
315
  'Cache-Control': 'no-cache',
@@ -300,7 +332,7 @@ export function handleRequest(req, res) {
300
332
  });
301
333
  return;
302
334
  }
303
- if (req.method === 'GET' && req.url === '/api/files') {
335
+ if (req.method === 'GET' && pathname === '/api/files') {
304
336
  const cwd = process.cwd();
305
337
  const gitignored = parseGitignore(cwd);
306
338
  const tree = getProjectTree(cwd, cwd, 0, gitignored);
@@ -308,19 +340,19 @@ export function handleRequest(req, res) {
308
340
  res.end(JSON.stringify({ cwd: path.basename(cwd), tree }));
309
341
  return;
310
342
  }
311
- if (req.method === 'GET' && req.url === '/api/history') {
343
+ if (req.method === 'GET' && pathname === '/api/history') {
312
344
  const history = activeHistoryProvider ? activeHistoryProvider() : [];
313
345
  res.writeHead(200, { 'Content-Type': 'application/json' });
314
346
  res.end(JSON.stringify({ history }));
315
347
  return;
316
348
  }
317
- if (req.method === 'GET' && req.url === '/api/context') {
349
+ if (req.method === 'GET' && pathname === '/api/context') {
318
350
  const files = activeContextFilesProvider ? activeContextFilesProvider.getFiles() : [];
319
351
  res.writeHead(200, { 'Content-Type': 'application/json' });
320
352
  res.end(JSON.stringify({ files }));
321
353
  return;
322
354
  }
323
- if (req.method === 'DELETE' && req.url === '/api/context') {
355
+ if (req.method === 'DELETE' && pathname === '/api/context') {
324
356
  parseJsonBody(req)
325
357
  .then(data => {
326
358
  const removed = activeContextFilesProvider && data.file ? activeContextFilesProvider.removeFile(data.file) : false;
@@ -333,13 +365,13 @@ export function handleRequest(req, res) {
333
365
  });
334
366
  return;
335
367
  }
336
- if (req.method === 'GET' && req.url === '/api/sessions') {
368
+ if (req.method === 'GET' && pathname === '/api/sessions') {
337
369
  const sessions = activeSessionProvider ? activeSessionProvider.listSessions() : [];
338
370
  res.writeHead(200, { 'Content-Type': 'application/json' });
339
371
  res.end(JSON.stringify({ sessions }));
340
372
  return;
341
373
  }
342
- if (req.method === 'POST' && req.url === '/api/sessions/resume') {
374
+ if (req.method === 'POST' && pathname === '/api/sessions/resume') {
343
375
  parseJsonBody(req)
344
376
  .then(async (data) => {
345
377
  if (!activeSessionProvider || !data.sessionId) {
@@ -357,7 +389,7 @@ export function handleRequest(req, res) {
357
389
  });
358
390
  return;
359
391
  }
360
- if (req.method === 'POST' && req.url === '/api/sessions/new') {
392
+ if (req.method === 'POST' && pathname === '/api/sessions/new') {
361
393
  (async () => {
362
394
  if (!activeSessionProvider) {
363
395
  res.writeHead(500, { 'Content-Type': 'application/json' });
@@ -373,7 +405,7 @@ export function handleRequest(req, res) {
373
405
  });
374
406
  return;
375
407
  }
376
- if (req.method === 'DELETE' && req.url === '/api/sessions') {
408
+ if (req.method === 'DELETE' && pathname === '/api/sessions') {
377
409
  parseJsonBody(req)
378
410
  .then(async (data) => {
379
411
  if (!activeSessionProvider || !data.sessionId) {
@@ -391,14 +423,20 @@ export function handleRequest(req, res) {
391
423
  });
392
424
  return;
393
425
  }
394
- if (req.method === 'GET' && req.url === '/api/models') {
426
+ if (req.method === 'GET' && pathname === '/api/models') {
395
427
  const activeModel = activeModelProvider ? activeModelProvider.getActiveModel() : 'auto';
396
428
  const availableModels = activeModelProvider ? activeModelProvider.getAvailableModels() : [];
397
429
  res.writeHead(200, { 'Content-Type': 'application/json' });
398
430
  res.end(JSON.stringify({ activeModel, availableModels }));
399
431
  return;
400
432
  }
401
- if (req.method === 'POST' && req.url === '/api/models/switch') {
433
+ if (req.method === 'GET' && pathname === '/api/profile') {
434
+ const profile = loadProfile();
435
+ res.writeHead(200, { 'Content-Type': 'application/json' });
436
+ res.end(JSON.stringify({ name: profile.name || '', bio: profile.bio || '', style: profile.style || '' }));
437
+ return;
438
+ }
439
+ if (req.method === 'POST' && pathname === '/api/models/switch') {
402
440
  parseJsonBody(req)
403
441
  .then(async (data) => {
404
442
  if (!activeModelProvider || !data.model) {
@@ -416,6 +454,29 @@ export function handleRequest(req, res) {
416
454
  });
417
455
  return;
418
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
+ }
419
480
  res.writeHead(404, { 'Content-Type': 'text/plain' });
420
481
  res.end('Not Found');
421
482
  }
@@ -474,6 +535,7 @@ export function startServer(port = PORT, host = HOST) {
474
535
  server.once('error', errorHandler);
475
536
  server.listen(port, host, () => {
476
537
  server.removeListener('error', errorHandler);
538
+ startWebSocketServer(server);
477
539
  console.info(`[webui] Server listening on http://${host}:${port}`);
478
540
  resolve(server);
479
541
  });
@@ -481,6 +543,7 @@ export function startServer(port = PORT, host = HOST) {
481
543
  }
482
544
  export function stopServer() {
483
545
  return new Promise((resolve, reject) => {
546
+ closeWebSocketServer();
484
547
  for (const record of activeClientRecords) {
485
548
  try {
486
549
  clearInterval(record.intervalId);
@@ -505,6 +568,7 @@ export function stopServer() {
505
568
  }
506
569
  // Auto-cleanup on CLI exit
507
570
  process.on('exit', () => {
571
+ closeWebSocketServer();
508
572
  if (server && server.listening) {
509
573
  try {
510
574
  server.close();
@@ -512,5 +576,5 @@ process.on('exit', () => {
512
576
  catch { /* best-effort */ }
513
577
  }
514
578
  });
515
- export { server, PORT, HOST };
579
+ export { server, PORT, HOST, startWebSocketServer, broadcastMilestone, closeWebSocketServer };
516
580
  //# sourceMappingURL=server.js.map