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.
- package/CHANGELOG.md +14 -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/formatting.d.ts +1 -1
- package/dist/formatting.d.ts.map +1 -1
- package/dist/formatting.js +6 -2
- package/dist/formatting.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/repl.js +1 -1
- package/dist/repl.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 +404 -9
- 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 +99 -35
- 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
|
@@ -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 = '
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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' &&
|
|
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' && (
|
|
265
|
-
const
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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' &&
|
|
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 === '
|
|
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
|