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.
- package/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/dist/agents/roles.d.ts.map +1 -1
- package/dist/agents/roles.js +4 -1
- package/dist/agents/roles.js.map +1 -1
- package/dist/commands/webui.js +5 -5
- package/dist/commands/webui.js.map +1 -1
- package/dist/marathon/engine.js +1 -1
- package/dist/marathon/engine.js.map +1 -1
- package/dist/marathon/evaluator.d.ts +1 -0
- package/dist/marathon/evaluator.d.ts.map +1 -1
- package/dist/marathon/evaluator.js +33 -5
- package/dist/marathon/evaluator.js.map +1 -1
- package/dist/marathon/planner.d.ts.map +1 -1
- package/dist/marathon/planner.js +2 -1
- package/dist/marathon/planner.js.map +1 -1
- package/dist/tools/builtin/files.d.ts.map +1 -1
- package/dist/tools/builtin/files.js +11 -1
- package/dist/tools/builtin/files.js.map +1 -1
- package/dist/webui/install.test.d.ts +2 -0
- package/dist/webui/install.test.d.ts.map +1 -0
- package/dist/webui/install.test.js +43 -0
- package/dist/webui/install.test.js.map +1 -0
- package/dist/webui/public/apple-touch-icon.png +0 -0
- package/dist/webui/public/icon-192.png +0 -0
- package/dist/webui/public/icon-512.png +0 -0
- package/dist/webui/public/index.html +116 -32
- package/dist/webui/public/logo.png +0 -0
- package/dist/webui/public/manifest.json +25 -0
- package/dist/webui/public/script.js +387 -8
- package/dist/webui/public/styles.css +773 -3
- package/dist/webui/public/sw.js +81 -0
- package/dist/webui/public/types.d.ts +9 -0
- package/dist/webui/public/types.d.ts.map +1 -0
- package/dist/webui/public/types.js +2 -0
- package/dist/webui/public/types.js.map +1 -0
- package/dist/webui/public/types.ts +8 -0
- package/dist/webui/qr.d.ts +23 -0
- package/dist/webui/qr.d.ts.map +1 -0
- package/dist/webui/qr.js +60 -0
- package/dist/webui/qr.js.map +1 -0
- package/dist/webui/qr.test.d.ts +2 -0
- package/dist/webui/qr.test.d.ts.map +1 -0
- package/dist/webui/qr.test.js +34 -0
- package/dist/webui/qr.test.js.map +1 -0
- package/dist/webui/server.d.ts +3 -2
- package/dist/webui/server.d.ts.map +1 -1
- package/dist/webui/server.js +93 -36
- package/dist/webui/server.js.map +1 -1
- package/dist/webui/server.test.js +33 -1
- package/dist/webui/server.test.js.map +1 -1
- package/dist/webui/styles.test.d.ts +2 -0
- package/dist/webui/styles.test.d.ts.map +1 -0
- package/dist/webui/styles.test.js +19 -0
- package/dist/webui/styles.test.js.map +1 -0
- package/dist/webui/ws.d.ts +17 -0
- package/dist/webui/ws.d.ts.map +1 -0
- package/dist/webui/ws.js +57 -0
- package/dist/webui/ws.js.map +1 -0
- package/dist/webui/ws.test.d.ts +2 -0
- package/dist/webui/ws.test.d.ts.map +1 -0
- package/dist/webui/ws.test.js +66 -0
- package/dist/webui/ws.test.js.map +1 -0
- 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 @@
|
|
|
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"}
|
package/dist/webui/qr.js
ADDED
|
@@ -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 @@
|
|
|
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"}
|
package/dist/webui/server.d.ts
CHANGED
|
@@ -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 = "
|
|
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":"AAAA,OAAO,EAAgB,eAAe,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAOlF,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAY,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/webui/server.ts"],"names":[],"mappings":"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"}
|
package/dist/webui/server.js
CHANGED
|
@@ -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 = '
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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' &&
|
|
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' && (
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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
|