fluxy-bot 0.2.24 → 0.2.26

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/bin/cli.js CHANGED
@@ -219,6 +219,10 @@ function bootServer() {
219
219
  let resolved = false;
220
220
  let stderrBuf = '';
221
221
 
222
+ // Vite warmup tracking
223
+ let viteWarmResolve;
224
+ const viteWarm = new Promise((r) => { viteWarmResolve = r; });
225
+
222
226
  const doResolve = () => {
223
227
  if (resolved) return;
224
228
  resolved = true;
@@ -227,6 +231,7 @@ function bootServer() {
227
231
  child,
228
232
  tunnelUrl: tunnelUrl || `http://localhost:${config.port}`,
229
233
  relayUrl: relayUrl || config.relay?.url || null,
234
+ viteWarm,
230
235
  });
231
236
  };
232
237
 
@@ -239,6 +244,10 @@ function bootServer() {
239
244
  const relayMatch = text.match(/__RELAY_URL__=(\S+)/);
240
245
  if (relayMatch) relayUrl = relayMatch[1];
241
246
 
247
+ if (text.includes('__VITE_WARM__')) {
248
+ viteWarmResolve();
249
+ }
250
+
242
251
  if (tunnelUrl && relayUrl) {
243
252
  doResolve();
244
253
  return;
@@ -308,18 +317,11 @@ async function init() {
308
317
  console.error(` ${c.dim}${err.message}${c.reset}\n`);
309
318
  process.exit(1);
310
319
  }
311
- const { child, tunnelUrl, relayUrl } = result;
320
+ const { child, tunnelUrl, relayUrl, viteWarm } = result;
312
321
  stepper.advance();
313
322
 
314
- // Warm up Vite by fetching pages (triggers dep pre-bundling)
315
- const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
316
- const localUrl = `http://localhost:${config.port}`;
317
- try {
318
- await Promise.all([
319
- fetch(localUrl + '/').then(r => r.text()),
320
- fetch(localUrl + '/fluxy/onboard.html').then(r => r.text()),
321
- ]);
322
- } catch {}
323
+ // Wait for Vite to finish pre-transforming all modules (with timeout)
324
+ await Promise.race([viteWarm, new Promise(r => setTimeout(r, 30_000))]);
323
325
  stepper.advance();
324
326
 
325
327
  stepper.finish();
@@ -358,18 +360,11 @@ async function start() {
358
360
  console.error(` ${c.dim}${err.message}${c.reset}\n`);
359
361
  process.exit(1);
360
362
  }
361
- const { child, tunnelUrl, relayUrl } = result;
363
+ const { child, tunnelUrl, relayUrl, viteWarm } = result;
362
364
  stepper.advance();
363
365
 
364
- // Warm up Vite by fetching pages (triggers dep pre-bundling)
365
- const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
366
- const local = `http://localhost:${cfg.port}`;
367
- try {
368
- await Promise.all([
369
- fetch(local + '/').then(r => r.text()),
370
- fetch(local + '/fluxy/onboard.html').then(r => r.text()),
371
- ]);
372
- } catch {}
366
+ // Wait for Vite to finish pre-transforming all modules (with timeout)
367
+ await Promise.race([viteWarm, new Promise(r => setTimeout(r, 30_000))]);
373
368
  stepper.advance();
374
369
 
375
370
  stepper.finish();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxy-bot",
3
- "version": "0.2.24",
3
+ "version": "0.2.26",
4
4
  "description": "Self-hosted AI bot — run your own AI assistant from anywhere",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -69,9 +69,14 @@ ok "Files copied"
69
69
  # ── Install dependencies ──
70
70
  info "Installing dependencies (this may take a moment)..."
71
71
  cd "$FLUXY_HOME"
72
- npm install --omit=dev --ignore-scripts 2>/dev/null
72
+ npm install --omit=dev 2>/dev/null
73
73
  ok "Dependencies installed"
74
74
 
75
+ # ── Build fluxy chat + onboard (served as static files) ──
76
+ info "Building chat interface..."
77
+ npm run build:fluxy 2>/dev/null
78
+ ok "Chat interface built"
79
+
75
80
  # ── Create symlink ──
76
81
  info "Creating fluxy command..."
77
82
  chmod +x "$FLUXY_HOME/bin/cli.js"
@@ -61,6 +61,17 @@ try {
61
61
  // Non-fatal: deps may already exist from a previous install
62
62
  }
63
63
 
64
+ // ── Build fluxy chat + onboard (served as static files) ──
65
+
66
+ try {
67
+ execSync('npm run build:fluxy', {
68
+ cwd: FLUXY_HOME,
69
+ stdio: 'ignore',
70
+ });
71
+ } catch {
72
+ // Non-fatal: will be built on first start if needed
73
+ }
74
+
64
75
  // ── Create fluxy symlink ──
65
76
 
66
77
  const cliPath = path.join(FLUXY_HOME, 'bin', 'cli.js');
@@ -1,10 +1,12 @@
1
1
  import http from 'http';
2
2
  import net from 'net';
3
3
  import fs from 'fs';
4
+ import path from 'path';
4
5
  import { WebSocketServer, WebSocket } from 'ws';
5
6
  import { loadConfig, saveConfig } from '../shared/config.js';
6
7
  import { createProvider, type AiProvider, type ChatMessage } from '../shared/ai.js';
7
8
  import { paths } from '../shared/paths.js';
9
+ import { PKG_DIR } from '../shared/paths.js';
8
10
  import { log } from '../shared/logger.js';
9
11
  import { startTunnel, stopTunnel, isTunnelAlive, restartTunnel } from './tunnel.js';
10
12
  import { spawnWorker, stopWorker, getWorkerPort, isWorkerAlive } from './worker.js';
@@ -12,6 +14,20 @@ import { updateTunnelUrl, startHeartbeat, stopHeartbeat, disconnect } from '../s
12
14
  import { startFluxyAgentQuery, stopFluxyAgentQuery } from './fluxy-agent.js';
13
15
  import { startViteDevServers, stopViteDevServers } from './vite-dev.js';
14
16
 
17
+ const DIST_FLUXY = path.join(PKG_DIR, 'dist-fluxy');
18
+
19
+ const MIME_TYPES: Record<string, string> = {
20
+ '.html': 'text/html',
21
+ '.js': 'application/javascript',
22
+ '.css': 'text/css',
23
+ '.png': 'image/png',
24
+ '.jpg': 'image/jpeg',
25
+ '.svg': 'image/svg+xml',
26
+ '.webm': 'video/webm',
27
+ '.woff2': 'font/woff2',
28
+ '.json': 'application/json',
29
+ };
30
+
15
31
  const RECOVERING_HTML = `<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Recovering</title>
16
32
  <style>body{background:#0a0a0f;color:#94a3b8;font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
17
33
  div{text-align:center}h1{font-size:18px;margin-bottom:8px;color:#e2e8f0}p{font-size:14px}a{color:#60a5fa}</style></head>
@@ -23,10 +39,10 @@ export async function startSupervisor() {
23
39
  const config = loadConfig();
24
40
  const workerPort = getWorkerPort(config.port);
25
41
 
26
- // Start Vite dev servers for instant HMR
27
- console.log('[supervisor] Starting Vite dev servers...');
42
+ // Start Vite dev server for dashboard HMR
43
+ console.log('[supervisor] Starting Vite dev server...');
28
44
  const vitePorts = await startViteDevServers(config.port);
29
- console.log(`[supervisor] Vite ready — dashboard :${vitePorts.dashboard}, fluxy :${vitePorts.fluxy}`);
45
+ console.log(`[supervisor] Vite ready — dashboard :${vitePorts.dashboard}`);
30
46
 
31
47
  // Fluxy's AI brain
32
48
  let ai: AiProvider | null = null;
@@ -73,25 +89,37 @@ export async function startSupervisor() {
73
89
  return;
74
90
  }
75
91
 
76
- // Fluxy routes → proxy to fluxy Vite dev server
92
+ // Fluxy routes → serve pre-built static files from dist-fluxy/
77
93
  // Note: must check '/fluxy/' (with slash) to avoid matching '/fluxy_tilts.webm' etc.
78
94
  if (req.url === '/fluxy' || req.url?.startsWith('/fluxy/')) {
79
- // Rewrite bare /fluxy or /fluxy/ to /fluxy/fluxy.html — Vite dev defaults to index.html otherwise
80
- const proxyPath = (req.url === '/fluxy' || req.url === '/fluxy/') ? '/fluxy/fluxy.html' : req.url;
81
- console.log(`[supervisor] fluxy Vite :${vitePorts.fluxy} | ${req.method} ${proxyPath}${proxyPath !== req.url ? ` (rewritten from ${req.url})` : ''}`);
82
- const proxy = http.request(
83
- { host: '127.0.0.1', port: vitePorts.fluxy, path: proxyPath, method: req.method, headers: req.headers },
84
- (proxyRes) => {
85
- res.writeHead(proxyRes.statusCode!, proxyRes.headers);
86
- proxyRes.pipe(res);
87
- },
88
- );
89
- proxy.on('error', (e) => {
90
- console.error(`[supervisor] Fluxy Vite proxy error: ${req.url}`, e.message);
91
- res.writeHead(503, { 'Content-Type': 'text/html' });
92
- res.end('<html><body style="background:#212121;color:#f5f5f5;display:flex;align-items:center;justify-content:center;height:100vh;font-family:system-ui"><p>Fluxy chat starting up...</p></body></html>');
93
- });
94
- req.pipe(proxy);
95
+ // Strip /fluxy prefix and resolve file path
96
+ let filePath = req.url!.replace(/^\/fluxy\/?/, '') || 'fluxy.html';
97
+ // Strip query strings (e.g. ?v=xxx)
98
+ filePath = filePath.split('?')[0];
99
+ const fullPath = path.join(DIST_FLUXY, filePath);
100
+
101
+ // Security: prevent directory traversal
102
+ if (!fullPath.startsWith(DIST_FLUXY)) {
103
+ res.writeHead(403);
104
+ res.end('Forbidden');
105
+ return;
106
+ }
107
+
108
+ try {
109
+ const stat = fs.statSync(fullPath);
110
+ if (stat.isFile()) {
111
+ const ext = path.extname(fullPath);
112
+ const mime = MIME_TYPES[ext] || 'application/octet-stream';
113
+ res.writeHead(200, { 'Content-Type': mime, 'Cache-Control': 'public, max-age=31536000, immutable' });
114
+ fs.createReadStream(fullPath).pipe(res);
115
+ } else {
116
+ res.writeHead(404);
117
+ res.end('Not found');
118
+ }
119
+ } catch {
120
+ res.writeHead(404);
121
+ res.end('Not found');
122
+ }
95
123
  return;
96
124
  }
97
125
 
@@ -213,10 +241,9 @@ export async function startSupervisor() {
213
241
  return;
214
242
  }
215
243
 
216
- // Route HMR WebSocket to correct Vite dev server
217
- const isFluxy = req.url === '/fluxy' || req.url?.startsWith('/fluxy/');
218
- const targetPort = isFluxy ? vitePorts.fluxy : vitePorts.dashboard;
219
- console.log(`[supervisor] → Vite HMR (${isFluxy ? 'fluxy' : 'dashboard'}) :${targetPort}`);
244
+ // Route HMR WebSocket to dashboard Vite dev server
245
+ const targetPort = vitePorts.dashboard;
246
+ console.log(`[supervisor] Vite HMR (dashboard) :${targetPort}`);
220
247
 
221
248
  const proxy = net.connect(targetPort, () => {
222
249
  const headers = Object.entries(req.headers).map(([k, v]) => `${k}: ${v}`).join('\r\n');
@@ -4,21 +4,16 @@ import { PKG_DIR } from '../shared/paths.js';
4
4
  import { log } from '../shared/logger.js';
5
5
 
6
6
  let dashboardVite: ViteDevServer | null = null;
7
- let fluxyVite: ViteDevServer | null = null;
8
7
 
9
- export async function startViteDevServers(supervisorPort: number): Promise<{ dashboard: number; fluxy: number }> {
8
+ export async function startViteDevServers(supervisorPort: number): Promise<{ dashboard: number }> {
10
9
  const ports = {
11
10
  dashboard: supervisorPort + 2,
12
- fluxy: supervisorPort + 3,
13
11
  };
14
12
 
15
- console.log(`[vite-dev] Starting Vite dev servers...`);
16
- console.log(`[vite-dev] Dashboard → :${ports.dashboard}, Fluxy → :${ports.fluxy}`);
13
+ console.log(`[vite-dev] Starting dashboard Vite dev server on :${ports.dashboard}`);
17
14
  console.log(`[vite-dev] HMR clientPort → :${supervisorPort} (browser connects through supervisor proxy)`);
18
15
  console.log(`[vite-dev] PKG_DIR = ${PKG_DIR}`);
19
16
 
20
- // Dashboard Vite dev server
21
- console.log('[vite-dev] Creating dashboard Vite server...');
22
17
  try {
23
18
  dashboardVite = await createViteServer({
24
19
  configFile: path.join(PKG_DIR, 'vite.config.ts'),
@@ -38,28 +33,21 @@ export async function startViteDevServers(supervisorPort: number): Promise<{ das
38
33
  throw err;
39
34
  }
40
35
 
41
- // Fluxy chat Vite dev server
42
- console.log('[vite-dev] Creating fluxy Vite server...');
43
- try {
44
- fluxyVite = await createViteServer({
45
- configFile: path.join(PKG_DIR, 'vite.fluxy.config.ts'),
46
- server: {
47
- port: ports.fluxy,
48
- host: '127.0.0.1',
49
- strictPort: true,
50
- allowedHosts: true,
51
- hmr: { clientPort: supervisorPort },
52
- },
53
- logLevel: 'info',
54
- });
55
- await fluxyVite.listen();
56
- console.log(`[vite-dev] ✓ Fluxy Vite ready on :${ports.fluxy}`);
57
- } catch (err) {
58
- console.error('[vite-dev] ✗ Fluxy Vite failed:', err);
59
- throw err;
60
- }
36
+ log.ok(`Vite HMR active dashboard :${ports.dashboard}`);
37
+
38
+ // Warm up: fetch the dashboard entry to pre-transform modules
39
+ fetch(`http://127.0.0.1:${ports.dashboard}/`).then(r => r.text()).then(async (html) => {
40
+ const scriptRe = /src="([^"]+\.tsx)"/g;
41
+ let m;
42
+ while ((m = scriptRe.exec(html)) !== null) {
43
+ const url = `http://127.0.0.1:${ports.dashboard}${m[1].startsWith('/') ? '' : '/'}${m[1]}`;
44
+ await fetch(url).then(r => r.text()).catch(() => {});
45
+ }
46
+ console.log('__VITE_WARM__');
47
+ }).catch(() => {
48
+ console.log('__VITE_WARM__');
49
+ });
61
50
 
62
- log.ok(`Vite HMR active — dashboard :${ports.dashboard}, fluxy :${ports.fluxy}`);
63
51
  return ports;
64
52
  }
65
53
 
@@ -70,9 +58,4 @@ export async function stopViteDevServers(): Promise<void> {
70
58
  dashboardVite = null;
71
59
  console.log('[vite-dev] Dashboard Vite stopped');
72
60
  }
73
- if (fluxyVite) {
74
- await fluxyVite.close();
75
- fluxyVite = null;
76
- console.log('[vite-dev] Fluxy Vite stopped');
77
- }
78
61
  }
package/vite.config.ts CHANGED
@@ -17,9 +17,20 @@ export default defineConfig({
17
17
  proxy: {
18
18
  '/api': 'http://localhost:3000',
19
19
  },
20
+ warmup: {
21
+ clientFiles: ['./src/main.tsx'],
22
+ },
20
23
  },
21
24
  optimizeDeps: {
22
25
  include: [
26
+ 'react',
27
+ 'react-dom/client',
28
+ 'react/jsx-runtime',
29
+ 'lucide-react',
30
+ 'framer-motion',
31
+ 'recharts',
32
+ 'zustand',
33
+ 'sonner',
23
34
  'use-sync-external-store',
24
35
  'use-sync-external-store/shim',
25
36
  ],
@@ -19,8 +19,22 @@ export default defineConfig({
19
19
  },
20
20
  },
21
21
  },
22
+ server: {
23
+ warmup: {
24
+ clientFiles: ['./fluxy-main.tsx', './onboard-main.tsx'],
25
+ },
26
+ },
22
27
  optimizeDeps: {
23
28
  include: [
29
+ 'react',
30
+ 'react-dom/client',
31
+ 'react/jsx-runtime',
32
+ 'lucide-react',
33
+ 'framer-motion',
34
+ 'react-markdown',
35
+ 'remark-gfm',
36
+ 'react-syntax-highlighter',
37
+ 'react-syntax-highlighter/dist/esm/styles/prism/one-dark',
24
38
  'use-sync-external-store',
25
39
  'use-sync-external-store/shim',
26
40
  ],