fluxy-bot 0.8.9 → 0.9.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 (106) hide show
  1. package/README.md +53 -84
  2. package/bin/cli.js +1717 -6
  3. package/components.json +18 -18
  4. package/package.json +98 -109
  5. package/postcss.config.js +3 -3
  6. package/scripts/install.ps1 +1 -1
  7. package/scripts/install.sh +1 -1
  8. package/scripts/postinstall.js +47 -58
  9. package/shared/ai.ts +118 -202
  10. package/shared/config.ts +37 -39
  11. package/shared/logger.ts +6 -14
  12. package/shared/paths.ts +10 -16
  13. package/shared/relay.ts +89 -98
  14. package/supervisor/backend.ts +77 -89
  15. package/supervisor/chat/ARCHITECTURE.md +13 -14
  16. package/supervisor/chat/OnboardWizard.tsx +2194 -3419
  17. package/supervisor/chat/fluxy-main.tsx +495 -633
  18. package/supervisor/chat/fluxy.html +24 -36
  19. package/supervisor/chat/onboard-main.tsx +8 -8
  20. package/supervisor/chat/onboard.html +10 -13
  21. package/supervisor/chat/src/components/Chat/AudioBubble.tsx +94 -108
  22. package/supervisor/chat/src/components/Chat/ChatView.tsx +14 -36
  23. package/supervisor/chat/src/components/Chat/ImageLightbox.tsx +71 -89
  24. package/supervisor/chat/src/components/Chat/InputBar.tsx +498 -728
  25. package/supervisor/chat/src/components/Chat/MessageBubble.tsx +154 -246
  26. package/supervisor/chat/src/components/Chat/MessageList.tsx +114 -138
  27. package/supervisor/chat/src/components/Chat/TypingIndicator.tsx +38 -50
  28. package/supervisor/chat/src/components/LoginScreen.tsx +245 -296
  29. package/supervisor/chat/src/hooks/useChat.ts +210 -259
  30. package/supervisor/chat/src/hooks/useFluxyChat.ts +232 -322
  31. package/supervisor/chat/src/hooks/useSpeechRecognition.ts +126 -148
  32. package/supervisor/chat/src/lib/auth.ts +20 -23
  33. package/supervisor/chat/src/lib/ws-client.ts +113 -125
  34. package/supervisor/chat/src/styles/globals.css +76 -85
  35. package/supervisor/file-saver.ts +39 -52
  36. package/supervisor/fluxy-agent.ts +213 -292
  37. package/supervisor/index.ts +895 -1263
  38. package/supervisor/scheduler.ts +237 -299
  39. package/supervisor/tunnel.ts +113 -167
  40. package/supervisor/vite-dev.ts +51 -61
  41. package/supervisor/widget.js +135 -147
  42. package/supervisor/worker.ts +45 -49
  43. package/tsconfig.json +18 -24
  44. package/vite.config.ts +48 -55
  45. package/vite.fluxy.config.ts +34 -34
  46. package/worker/claude-auth.ts +202 -265
  47. package/worker/codex-auth.ts +144 -181
  48. package/worker/db.ts +63 -191
  49. package/worker/index.ts +601 -842
  50. package/workspace/MYSELF.md +1 -1
  51. package/workspace/PULSE.json +6 -6
  52. package/workspace/backend/index.ts +16 -19
  53. package/workspace/client/index.html +47 -60
  54. package/workspace/client/public/manifest.json +21 -21
  55. package/workspace/client/public/sw.js +29 -31
  56. package/workspace/client/src/App.tsx +91 -136
  57. package/workspace/client/src/components/Dashboard/DashboardPage.tsx +68 -72
  58. package/workspace/client/src/components/ErrorBoundary.tsx +11 -11
  59. package/workspace/client/src/components/Layout/DashboardLayout.tsx +35 -35
  60. package/workspace/client/src/components/Layout/Footer.tsx +12 -12
  61. package/workspace/client/src/components/Layout/MobileNav.tsx +23 -23
  62. package/workspace/client/src/components/Layout/Sidebar.tsx +81 -81
  63. package/workspace/client/src/components/ui/avatar.tsx +85 -85
  64. package/workspace/client/src/components/ui/badge.tsx +40 -41
  65. package/workspace/client/src/components/ui/button.tsx +56 -57
  66. package/workspace/client/src/components/ui/card.tsx +75 -75
  67. package/workspace/client/src/components/ui/dialog.tsx +114 -117
  68. package/workspace/client/src/components/ui/dropdown-menu.tsx +191 -199
  69. package/workspace/client/src/components/ui/input.tsx +17 -17
  70. package/workspace/client/src/components/ui/scroll-area.tsx +47 -47
  71. package/workspace/client/src/components/ui/select.tsx +145 -151
  72. package/workspace/client/src/components/ui/separator.tsx +21 -21
  73. package/workspace/client/src/components/ui/sheet.tsx +102 -102
  74. package/workspace/client/src/components/ui/skeleton.tsx +10 -10
  75. package/workspace/client/src/components/ui/switch.tsx +26 -26
  76. package/workspace/client/src/components/ui/tabs.tsx +69 -69
  77. package/workspace/client/src/components/ui/textarea.tsx +14 -14
  78. package/workspace/client/src/components/ui/tooltip.tsx +37 -37
  79. package/workspace/client/src/lib/utils.ts +1 -1
  80. package/workspace/client/src/main.tsx +3 -3
  81. package/workspace/client/src/styles/globals.css +76 -85
  82. package/workspace/skills/code-reviewer/.claude-plugin/plugin.json +3 -3
  83. package/workspace/skills/code-reviewer/skills/code-reviewer/SKILL.md +0 -6
  84. package/workspace/skills/daily-standup/.claude-plugin/plugin.json +3 -3
  85. package/workspace/skills/daily-standup/skills/daily-standup/SKILL.md +0 -7
  86. package/workspace/skills/workspace-helper/.claude-plugin/plugin.json +3 -3
  87. package/workspace/skills/workspace-helper/skills/workspace-helper/SKILL.md +0 -2
  88. package/bin/cli.backup.js +0 -2138
  89. package/cli/commands/daemon.ts +0 -42
  90. package/cli/commands/init.ts +0 -32
  91. package/cli/commands/start.ts +0 -113
  92. package/cli/commands/tunnel.temp.ts +0 -206
  93. package/cli/commands/tunnel.ts +0 -227
  94. package/cli/commands/update.ts +0 -163
  95. package/cli/core/base-adapter.ts +0 -156
  96. package/cli/core/cloudflared.ts +0 -113
  97. package/cli/core/config.ts +0 -73
  98. package/cli/core/os-detector.ts +0 -43
  99. package/cli/core/server.ts +0 -109
  100. package/cli/core/types.ts +0 -15
  101. package/cli/index.ts +0 -72
  102. package/cli/platforms/darwin.ts +0 -146
  103. package/cli/platforms/index.ts +0 -21
  104. package/cli/platforms/linux.ts +0 -156
  105. package/cli/platforms/win32.ts +0 -34
  106. package/cli/utils/ui.ts +0 -37
@@ -10,201 +10,147 @@ let proc: ChildProcess | null = null;
10
10
  const MIN_CF_SIZE = 10 * 1024 * 1024; // 10 MB — valid cloudflared is ~30-50 MB
11
11
 
12
12
  function findBinary(): string | null {
13
- // Check system-wide install
14
- const which =
15
- process.platform === 'win32'
16
- ? 'where cloudflared'
17
- : 'which cloudflared';
18
- try {
19
- execSync(which, { stdio: 'ignore' });
20
- return 'cloudflared';
21
- } catch {}
22
-
23
- // Check local install (validate by file size, never execute — avoids Windows popup)
24
- if (!fs.existsSync(paths.cloudflared)) return null;
25
- const size = fs.statSync(paths.cloudflared).size;
26
- if (size < MIN_CF_SIZE) {
27
- fs.unlinkSync(paths.cloudflared);
28
- return null;
29
- }
30
- return paths.cloudflared;
13
+ // Check system-wide install
14
+ const which = process.platform === 'win32' ? 'where cloudflared' : 'which cloudflared';
15
+ try { execSync(which, { stdio: 'ignore' }); return 'cloudflared'; } catch {}
16
+
17
+ // Check local install (validate by file size, never execute — avoids Windows popup)
18
+ if (!fs.existsSync(paths.cloudflared)) return null;
19
+ const size = fs.statSync(paths.cloudflared).size;
20
+ if (size < MIN_CF_SIZE) {
21
+ fs.unlinkSync(paths.cloudflared);
22
+ return null;
23
+ }
24
+ return paths.cloudflared;
31
25
  }
32
26
 
33
27
  export async function installCloudflared(): Promise<string> {
34
- const existing = findBinary();
35
- if (existing) return existing;
36
-
37
- const dir = path.dirname(paths.cloudflared);
38
- fs.mkdirSync(dir, { recursive: true });
39
-
40
- const p = os.platform();
41
- const a =
42
- p === 'win32'
43
- ? (process.env.PROCESSOR_ARCHITECTURE || os.arch()).toLowerCase()
44
- : os.arch();
45
- const base =
46
- 'https://github.com/cloudflare/cloudflared/releases/latest/download';
47
-
48
- let url: string;
49
- if (p === 'win32')
50
- url = `${base}/cloudflared-windows-${a.includes('arm') ? 'arm64' : 'amd64'}.exe`;
51
- else if (p === 'darwin')
52
- url = `${base}/cloudflared-darwin-${a === 'arm64' ? 'arm64' : 'amd64'}.tgz`;
53
- else if (a === 'arm64' || a === 'aarch64')
54
- url = `${base}/cloudflared-linux-arm64`;
55
- else if (a.startsWith('arm')) url = `${base}/cloudflared-linux-arm`;
56
- else url = `${base}/cloudflared-linux-amd64`;
57
-
58
- log.info('Installing cloudflared...');
59
- if (p === 'win32') {
60
- execSync(`curl.exe -fsSL -o "${paths.cloudflared}" "${url}"`, {
61
- stdio: 'ignore'
62
- });
63
- } else if (url.endsWith('.tgz')) {
64
- execSync(`curl -fsSL "${url}" | tar xz -C "${dir}"`, {
65
- stdio: 'ignore'
66
- });
67
- } else {
68
- execSync(`curl -fsSL -o "${paths.cloudflared}" "${url}"`, {
69
- stdio: 'ignore'
70
- });
71
- }
72
- if (p !== 'win32') fs.chmodSync(paths.cloudflared, 0o755);
73
- log.ok('cloudflared installed');
74
- return paths.cloudflared;
28
+ const existing = findBinary();
29
+ if (existing) return existing;
30
+
31
+ const dir = path.dirname(paths.cloudflared);
32
+ fs.mkdirSync(dir, { recursive: true });
33
+
34
+ const p = os.platform();
35
+ const a = p === 'win32'
36
+ ? (process.env.PROCESSOR_ARCHITECTURE || os.arch()).toLowerCase()
37
+ : os.arch();
38
+ const base = 'https://github.com/cloudflare/cloudflared/releases/latest/download';
39
+
40
+ let url: string;
41
+ if (p === 'win32') url = `${base}/cloudflared-windows-${a.includes('arm') ? 'arm64' : 'amd64'}.exe`;
42
+ else if (p === 'darwin') url = `${base}/cloudflared-darwin-${a === 'arm64' ? 'arm64' : 'amd64'}.tgz`;
43
+ else if (a === 'arm64' || a === 'aarch64') url = `${base}/cloudflared-linux-arm64`;
44
+ else if (a.startsWith('arm')) url = `${base}/cloudflared-linux-arm`;
45
+ else url = `${base}/cloudflared-linux-amd64`;
46
+
47
+ log.info('Installing cloudflared...');
48
+ if (p === 'win32') {
49
+ execSync(`curl.exe -fsSL -o "${paths.cloudflared}" "${url}"`, { stdio: 'ignore' });
50
+ } else if (url.endsWith('.tgz')) {
51
+ execSync(`curl -fsSL "${url}" | tar xz -C "${dir}"`, { stdio: 'ignore' });
52
+ } else {
53
+ execSync(`curl -fsSL -o "${paths.cloudflared}" "${url}"`, { stdio: 'ignore' });
54
+ }
55
+ if (p !== 'win32') fs.chmodSync(paths.cloudflared, 0o755);
56
+ log.ok('cloudflared installed');
57
+ return paths.cloudflared;
75
58
  }
76
59
 
77
60
  export function startTunnel(port: number): Promise<string> {
78
- return new Promise(async (resolve, reject) => {
79
- const bin = await installCloudflared();
80
- const timeout = setTimeout(
81
- () => reject(new Error('Tunnel timeout')),
82
- 30_000
83
- );
84
-
85
- proc = spawn(
86
- bin,
87
- ['tunnel', '--url', `http://localhost:${port}`, '--no-autoupdate'],
88
- {
89
- stdio: ['ignore', 'pipe', 'pipe'],
90
- windowsHide: true
91
- }
92
- );
93
-
94
- let buf = '';
95
- const onData = (d: Buffer) => {
96
- buf += d.toString();
97
- const m = buf.match(/https:\/\/[^\s]+\.trycloudflare\.com/);
98
- if (m) {
99
- clearTimeout(timeout);
100
- resolve(m[0]);
101
- }
102
- };
103
-
104
- proc.stdout?.on('data', onData);
105
- proc.stderr?.on('data', onData);
106
- proc.on('error', e => {
107
- clearTimeout(timeout);
108
- reject(e);
109
- });
110
- proc.on('exit', code => {
111
- if (code !== 0) {
112
- clearTimeout(timeout);
113
- reject(new Error(`cloudflared exited with code ${code}`));
114
- }
115
- });
61
+ return new Promise(async (resolve, reject) => {
62
+ const bin = await installCloudflared();
63
+ const timeout = setTimeout(() => reject(new Error('Tunnel timeout')), 30_000);
64
+
65
+ proc = spawn(bin, ['tunnel', '--url', `http://localhost:${port}`, '--no-autoupdate'], {
66
+ stdio: ['ignore', 'pipe', 'pipe'],
67
+ windowsHide: true,
116
68
  });
69
+
70
+ let buf = '';
71
+ const onData = (d: Buffer) => {
72
+ buf += d.toString();
73
+ const m = buf.match(/https:\/\/[^\s]+\.trycloudflare\.com/);
74
+ if (m) { clearTimeout(timeout); resolve(m[0]); }
75
+ };
76
+
77
+ proc.stdout?.on('data', onData);
78
+ proc.stderr?.on('data', onData);
79
+ proc.on('error', (e) => { clearTimeout(timeout); reject(e); });
80
+ proc.on('exit', (code) => { if (code !== 0) { clearTimeout(timeout); reject(new Error(`cloudflared exited with code ${code}`)); } });
81
+ });
117
82
  }
118
83
 
119
- export async function startNamedTunnel(
120
- configPath: string,
121
- name: string
122
- ): Promise<void> {
123
- const bin = await installCloudflared();
84
+ export async function startNamedTunnel(configPath: string, name: string): Promise<void> {
85
+ const bin = await installCloudflared();
124
86
 
125
- proc = spawn(bin, ['tunnel', '--config', configPath, 'run', name], {
126
- stdio: ['ignore', 'pipe', 'pipe'],
127
- windowsHide: true
128
- });
87
+ proc = spawn(bin, ['tunnel', '--config', configPath, 'run', name], {
88
+ stdio: ['ignore', 'pipe', 'pipe'],
89
+ windowsHide: true,
90
+ });
129
91
 
130
- proc.stdout?.on('data', (d: Buffer) =>
131
- log.info(`[named-tunnel] ${d.toString().trim()}`)
132
- );
133
- proc.stderr?.on('data', (d: Buffer) =>
134
- log.info(`[named-tunnel] ${d.toString().trim()}`)
135
- );
136
- proc.on('error', e => log.error(`Named tunnel error: ${e.message}`));
137
- proc.on('exit', code => {
138
- if (code !== 0) log.warn(`Named tunnel exited with code ${code}`);
139
- });
92
+ proc.stdout?.on('data', (d: Buffer) => log.info(`[named-tunnel] ${d.toString().trim()}`));
93
+ proc.stderr?.on('data', (d: Buffer) => log.info(`[named-tunnel] ${d.toString().trim()}`));
94
+ proc.on('error', (e) => log.error(`Named tunnel error: ${e.message}`));
95
+ proc.on('exit', (code) => { if (code !== 0) log.warn(`Named tunnel exited with code ${code}`); });
140
96
  }
141
97
 
142
98
  export async function startTunnelForMode(
143
- mode: 'off' | 'quick' | 'named',
144
- port: number,
145
- configPath?: string,
146
- name?: string
99
+ mode: 'off' | 'quick' | 'named',
100
+ port: number,
101
+ configPath?: string,
102
+ name?: string,
147
103
  ): Promise<string | null> {
148
- if (mode === 'off') return null;
149
- if (mode === 'named') {
150
- if (!configPath || !name)
151
- throw new Error('Named tunnel requires configPath and name');
152
- await startNamedTunnel(configPath, name);
153
- return null; // URL is known from config.tunnel.domain
154
- }
155
- // mode === 'quick'
156
- return startTunnel(port);
104
+ if (mode === 'off') return null;
105
+ if (mode === 'named') {
106
+ if (!configPath || !name) throw new Error('Named tunnel requires configPath and name');
107
+ await startNamedTunnel(configPath, name);
108
+ return null; // URL is known from config.tunnel.domain
109
+ }
110
+ // mode === 'quick'
111
+ return startTunnel(port);
157
112
  }
158
113
 
159
114
  export function stopTunnel(): void {
160
- proc?.kill();
161
- proc = null;
115
+ proc?.kill();
116
+ proc = null;
162
117
  }
163
118
 
164
119
  /** Check if the cloudflared child process is still running */
165
120
  export function isTunnelProcessAlive(): boolean {
166
- return proc !== null && proc.exitCode === null && proc.signalCode === null;
121
+ return proc !== null && proc.exitCode === null && proc.signalCode === null;
167
122
  }
168
123
 
169
- export async function isTunnelAlive(
170
- url: string,
171
- localPort?: number
172
- ): Promise<boolean> {
173
- // 1. Process check — if cloudflared exited, tunnel is definitely dead
174
- if (!isTunnelProcessAlive()) return false;
175
-
176
- // 2. Local reachability check confirm the local server is still responding.
177
- // We probe localhost directly instead of the tunnel URL because on macOS
178
- // the server can't reach itself through the Cloudflare tunnel (DNS/firewall).
179
- // If localhost responds and cloudflared is running, the tunnel is alive.
180
- if (localPort) {
181
- try {
182
- const res = await fetch(
183
- `http://127.0.0.1:${localPort}/api/health?_cb=${Date.now()}`,
184
- {
185
- signal: AbortSignal.timeout(3000)
186
- }
187
- );
188
- if (res.ok) return true;
189
- } catch {}
190
- // Local server not responding — but cloudflared is alive, so tunnel is still up.
191
- // The worker may be restarting. Don't kill the tunnel for a local issue.
192
- return true;
193
- }
194
-
195
- // No local port provided — process alive is enough
124
+ export async function isTunnelAlive(url: string, localPort?: number): Promise<boolean> {
125
+ // 1. Process check — if cloudflared exited, tunnel is definitely dead
126
+ if (!isTunnelProcessAlive()) return false;
127
+
128
+ // 2. Local reachability check — confirm the local server is still responding.
129
+ // We probe localhost directly instead of the tunnel URL because on macOS
130
+ // the server can't reach itself through the Cloudflare tunnel (DNS/firewall).
131
+ // If localhost responds and cloudflared is running, the tunnel is alive.
132
+ if (localPort) {
133
+ try {
134
+ const res = await fetch(`http://127.0.0.1:${localPort}/api/health?_cb=${Date.now()}`, {
135
+ signal: AbortSignal.timeout(3000),
136
+ });
137
+ if (res.ok) return true;
138
+ } catch {}
139
+ // Local server not responding — but cloudflared is alive, so tunnel is still up.
140
+ // The worker may be restarting. Don't kill the tunnel for a local issue.
196
141
  return true;
142
+ }
143
+
144
+ // No local port provided — process alive is enough
145
+ return true;
197
146
  }
198
147
 
199
148
  export async function restartTunnel(port: number): Promise<string> {
200
- stopTunnel();
201
- return startTunnel(port);
149
+ stopTunnel();
150
+ return startTunnel(port);
202
151
  }
203
152
 
204
- export async function restartNamedTunnel(
205
- configPath: string,
206
- name: string
207
- ): Promise<void> {
208
- stopTunnel();
209
- await startNamedTunnel(configPath, name);
153
+ export async function restartNamedTunnel(configPath: string, name: string): Promise<void> {
154
+ stopTunnel();
155
+ await startNamedTunnel(configPath, name);
210
156
  }
@@ -6,77 +6,67 @@ import { log } from '../shared/logger.js';
6
6
 
7
7
  let dashboardVite: ViteDevServer | null = null;
8
8
 
9
- export async function startViteDevServers(
10
- supervisorPort: number,
11
- hmrServer: http.Server
12
- ): Promise<{ dashboard: number }> {
13
- const ports = {
14
- dashboard: supervisorPort + 2
15
- };
9
+ export async function startViteDevServers(supervisorPort: number, hmrServer: http.Server): Promise<{ dashboard: number }> {
10
+ const ports = {
11
+ dashboard: supervisorPort + 2,
12
+ };
16
13
 
17
- console.log(
18
- `[vite-dev] Starting dashboard Vite dev server on :${ports.dashboard}`
19
- );
20
- console.log(`[vite-dev] HMR attached to supervisor server`);
21
- console.log(`[vite-dev] PKG_DIR = ${PKG_DIR}`);
14
+ console.log(`[vite-dev] Starting dashboard Vite dev server on :${ports.dashboard}`);
15
+ console.log(`[vite-dev] HMR attached to supervisor server`);
16
+ console.log(`[vite-dev] PKG_DIR = ${PKG_DIR}`);
22
17
 
23
- try {
24
- dashboardVite = await createViteServer({
25
- configFile: path.join(PKG_DIR, 'vite.config.ts'),
26
- server: {
27
- port: ports.dashboard,
28
- host: '127.0.0.1',
29
- strictPort: true,
30
- allowedHosts: true,
31
- // HMR WebSocket is attached directly to the supervisor's HTTP server.
32
- // No clientPort — the browser connects on the same origin the page is served from,
33
- // so it works both locally (localhost:3000) and through the relay (riven.fluxy.bot:443).
34
- hmr: { server: hmrServer }
35
- },
36
- logLevel: 'info'
37
- });
38
- await dashboardVite.listen();
39
- console.log(`[vite-dev] ✓ Dashboard Vite ready on :${ports.dashboard}`);
40
- } catch (err) {
41
- console.error('[vite-dev] ✗ Dashboard Vite failed:', err);
42
- throw err;
43
- }
18
+ try {
19
+ dashboardVite = await createViteServer({
20
+ configFile: path.join(PKG_DIR, 'vite.config.ts'),
21
+ server: {
22
+ port: ports.dashboard,
23
+ host: '127.0.0.1',
24
+ strictPort: true,
25
+ allowedHosts: true,
26
+ // HMR WebSocket is attached directly to the supervisor's HTTP server.
27
+ // No clientPort — the browser connects on the same origin the page is served from,
28
+ // so it works both locally (localhost:3000) and through the relay (riven.fluxy.bot:443).
29
+ hmr: { server: hmrServer },
30
+ },
31
+ logLevel: 'info',
32
+ });
33
+ await dashboardVite.listen();
34
+ console.log(`[vite-dev] ✓ Dashboard Vite ready on :${ports.dashboard}`);
35
+ } catch (err) {
36
+ console.error('[vite-dev] ✗ Dashboard Vite failed:', err);
37
+ throw err;
38
+ }
44
39
 
45
- log.ok(`Vite HMR active — dashboard :${ports.dashboard}`);
40
+ log.ok(`Vite HMR active — dashboard :${ports.dashboard}`);
46
41
 
47
- // Warm up: fetch the dashboard entry to pre-transform modules
48
- fetch(`http://127.0.0.1:${ports.dashboard}/`)
49
- .then(r => r.text())
50
- .then(async html => {
51
- const scriptRe = /src="([^"]+\.tsx)"/g;
52
- let m;
53
- while ((m = scriptRe.exec(html)) !== null) {
54
- const url = `http://127.0.0.1:${ports.dashboard}${m[1].startsWith('/') ? '' : '/'}${m[1]}`;
55
- await fetch(url)
56
- .then(r => r.text())
57
- .catch(() => {});
58
- }
59
- console.log('__VITE_WARM__');
60
- })
61
- .catch(() => {
62
- console.log('__VITE_WARM__');
63
- });
42
+ // Warm up: fetch the dashboard entry to pre-transform modules
43
+ fetch(`http://127.0.0.1:${ports.dashboard}/`).then(r => r.text()).then(async (html) => {
44
+ const scriptRe = /src="([^"]+\.tsx)"/g;
45
+ let m;
46
+ while ((m = scriptRe.exec(html)) !== null) {
47
+ const url = `http://127.0.0.1:${ports.dashboard}${m[1].startsWith('/') ? '' : '/'}${m[1]}`;
48
+ await fetch(url).then(r => r.text()).catch(() => {});
49
+ }
50
+ console.log('__VITE_WARM__');
51
+ }).catch(() => {
52
+ console.log('__VITE_WARM__');
53
+ });
64
54
 
65
- return ports;
55
+ return ports;
66
56
  }
67
57
 
68
58
  /** Tell connected browsers to full-reload via Vite's HMR WebSocket */
69
59
  export function reloadDashboard(): void {
70
- if (!dashboardVite) return;
71
- console.log('[vite-dev] Sending full-reload to dashboard clients');
72
- dashboardVite.hot.send({ type: 'full-reload', path: '*' });
60
+ if (!dashboardVite) return;
61
+ console.log('[vite-dev] Sending full-reload to dashboard clients');
62
+ dashboardVite.hot.send({ type: 'full-reload', path: '*' });
73
63
  }
74
64
 
75
65
  export async function stopViteDevServers(): Promise<void> {
76
- console.log('[vite-dev] Stopping Vite dev servers...');
77
- if (dashboardVite) {
78
- await dashboardVite.close();
79
- dashboardVite = null;
80
- console.log('[vite-dev] Dashboard Vite stopped');
81
- }
66
+ console.log('[vite-dev] Stopping Vite dev servers...');
67
+ if (dashboardVite) {
68
+ await dashboardVite.close();
69
+ dashboardVite = null;
70
+ console.log('[vite-dev] Dashboard Vite stopped');
71
+ }
82
72
  }