fluxy-bot 0.11.4 → 0.11.6

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
@@ -1735,6 +1735,42 @@ async function tunnel(sub) {
1735
1735
  }
1736
1736
  }
1737
1737
 
1738
+ // ── Password Reset ──
1739
+
1740
+ async function passwordReset() {
1741
+ const DB_PATH = path.join(DATA_DIR, 'memory.db');
1742
+
1743
+ if (!fs.existsSync(DB_PATH)) {
1744
+ console.log(`\n ${c.red}✗${c.reset} No database found. Run ${c.pink}fluxy init${c.reset} and complete setup first.\n`);
1745
+ process.exit(1);
1746
+ }
1747
+
1748
+ const Database = (await import('better-sqlite3')).default;
1749
+ const db = new Database(DB_PATH);
1750
+
1751
+ // Clear password and TOTP credentials
1752
+ db.prepare("DELETE FROM settings WHERE key IN ('portal_pass', 'totp_enabled', 'totp_secret', 'totp_recovery_codes')").run();
1753
+
1754
+ // Reset onboard flag so the wizard appears on next visit
1755
+ db.prepare("INSERT INTO settings (key, value) VALUES ('onboard_complete', 'false') ON CONFLICT(key) DO UPDATE SET value = 'false', updated_at = CURRENT_TIMESTAMP").run();
1756
+
1757
+ // Invalidate all active sessions and trusted devices
1758
+ db.prepare('DELETE FROM sessions').run();
1759
+ db.prepare('DELETE FROM trusted_devices').run();
1760
+
1761
+ db.close();
1762
+
1763
+ console.log(`\n ${c.blue}✔${c.reset} Password reset successful.\n`);
1764
+ console.log(` ${c.dim}The onboard wizard will appear on your next visit`);
1765
+ console.log(` so you can create a new password.${c.reset}\n`);
1766
+
1767
+ // Auto-restart daemon if it's running
1768
+ if (isDaemonInstalled() && isDaemonActive()) {
1769
+ console.log(` ${c.dim}Restarting Fluxy daemon...${c.reset}`);
1770
+ daemon('restart');
1771
+ }
1772
+ }
1773
+
1738
1774
  // ── Route ──
1739
1775
 
1740
1776
  switch (command) {
@@ -1746,6 +1782,7 @@ switch (command) {
1746
1782
  case 'update': update(); break;
1747
1783
  case 'daemon': daemon(subcommand); break;
1748
1784
  case 'tunnel': tunnel(subcommand); break;
1785
+ case 'password-reset': passwordReset(); break;
1749
1786
  default:
1750
1787
  fs.existsSync(CONFIG_PATH) ? start() : init();
1751
1788
  }
package/cli/index.ts CHANGED
@@ -9,7 +9,6 @@ import { registerStartCommand } from './commands/start.js';
9
9
  import { registerTunnelCommand } from './commands/tunnel.js';
10
10
  import { registerUpdateCommand } from './commands/update.js';
11
11
  import { registerInitCommand } from './commands/init.js';
12
- import { registerPasswordResetCommand } from './commands/password-reset.js';
13
12
 
14
13
  const program = new Command();
15
14
 
@@ -23,7 +22,6 @@ registerDaemonCommand(program);
23
22
  registerTunnelCommand(program);
24
23
  registerUpdateCommand(program);
25
24
  registerInitCommand(program);
26
- registerPasswordResetCommand(program);
27
25
 
28
26
  // Aliases for convenience matching the old CLI
29
27
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fluxy-bot",
3
- "version": "0.11.4",
3
+ "version": "0.11.6",
4
4
  "releaseNotes": [
5
5
  "Adding a way for users to claim their fluxies on the fluxy.bot dashboard",
6
6
  "2. ",
@@ -9,7 +9,8 @@ import { paths } from '../shared/paths.js';
9
9
  import { PKG_DIR, DATA_DIR, WORKSPACE_DIR } from '../shared/paths.js';
10
10
  import { log } from '../shared/logger.js';
11
11
  import { startTunnel, stopTunnel, isTunnelAlive, restartTunnel, startNamedTunnel, restartNamedTunnel } from './tunnel.js';
12
- import { spawnWorker, stopWorker, getWorkerPort, isWorkerAlive } from './worker.js';
12
+ import { createWorkerApp } from '../worker/index.js';
13
+ import { closeDb, getSession, getSetting } from '../worker/db.js';
13
14
  import { spawnBackend, stopBackend, getBackendPort, isBackendAlive, resetBackendRestarts } from './backend.js';
14
15
  import { updateTunnelUrl, startHeartbeat, stopHeartbeat, disconnect } from '../shared/relay.js';
15
16
  import { startFluxyAgentQuery, stopFluxyAgentQuery, type RecentMessage } from './fluxy-agent.js';
@@ -195,7 +196,6 @@ const RECOVERING_HTML = `<!DOCTYPE html><html style="background:#222122"><head><
195
196
 
196
197
  export async function startSupervisor() {
197
198
  const config = loadConfig();
198
- const workerPort = getWorkerPort(config.port);
199
199
  const backendPort = getBackendPort(config.port);
200
200
 
201
201
  // Create HTTP server first (Vite needs it for HMR WebSocket)
@@ -211,6 +211,9 @@ export async function startSupervisor() {
211
211
  // Ensure file storage dirs exist
212
212
  ensureFileDirs();
213
213
 
214
+ // Initialize worker routes in-process (no separate child process)
215
+ const workerApp = createWorkerApp();
216
+
214
217
  // Fluxy's AI brain
215
218
  let ai: AiProvider | null = null;
216
219
  if (config.ai.provider && (config.ai.apiKey || config.ai.provider === 'ollama')) {
@@ -227,11 +230,11 @@ export async function startSupervisor() {
227
230
  let currentStreamConvId: string | null = null;
228
231
  let currentStreamBuffer = '';
229
232
 
230
- /** Call worker API endpoints */
231
- async function workerApi(path: string, method = 'GET', body?: any) {
233
+ /** Call worker API endpoints (in-process via supervisor's own HTTP server) */
234
+ async function workerApi(apiPath: string, method = 'GET', body?: any) {
232
235
  const opts: RequestInit = { method, headers: { 'Content-Type': 'application/json' } };
233
236
  if (body) opts.body = JSON.stringify(body);
234
- const res = await fetch(`http://127.0.0.1:${workerPort}${path}`, opts);
237
+ const res = await fetch(`http://127.0.0.1:${config.port}${apiPath}`, opts);
235
238
  return res.json();
236
239
  }
237
240
 
@@ -252,13 +255,8 @@ export async function startSupervisor() {
252
255
  if (cached && cached > Date.now()) return true;
253
256
 
254
257
  try {
255
- const res = await fetch(`http://127.0.0.1:${workerPort}/api/portal/validate-token`, {
256
- method: 'POST',
257
- headers: { 'Content-Type': 'application/json' },
258
- body: JSON.stringify({ token }),
259
- });
260
- const data = await res.json() as { valid: boolean };
261
- if (data.valid) {
258
+ const session = getSession(token);
259
+ if (session) {
262
260
  tokenCache.set(token, Date.now() + TOKEN_CACHE_TTL);
263
261
  return true;
264
262
  }
@@ -274,9 +272,7 @@ export async function startSupervisor() {
274
272
  async function isAuthRequired(): Promise<boolean> {
275
273
  if (authRequiredCache && authRequiredCache.expires > Date.now()) return authRequiredCache.value;
276
274
  try {
277
- const res = await fetch(`http://127.0.0.1:${workerPort}/api/onboard/status`);
278
- const data = await res.json() as { portalConfigured: boolean };
279
- const required = !!data.portalConfigured;
275
+ const required = !!getSetting('portal_pass');
280
276
  authRequiredCache = { value: required, expires: Date.now() + 30_000 };
281
277
  return required;
282
278
  } catch {
@@ -368,16 +364,8 @@ export async function startSupervisor() {
368
364
  return;
369
365
  }
370
366
 
371
- // API routes → proxy to worker
367
+ // API routes → handled in-process by worker Express app
372
368
  if (req.url?.startsWith('/api')) {
373
- console.log(`[supervisor] → worker :${workerPort} | ${req.method} ${req.url}`);
374
- if (!isWorkerAlive()) {
375
- console.log('[supervisor] Worker down — returning 503');
376
- res.writeHead(503, { 'Content-Type': 'text/html' });
377
- res.end(RECOVERING_HTML);
378
- return;
379
- }
380
-
381
369
  // Auth check for mutation routes (POST/PUT/DELETE) — GET/HEAD are read-only, skip auth
382
370
  const method = req.method || 'GET';
383
371
  if (method !== 'GET' && method !== 'HEAD' && !isExemptRoute(method, req.url || '')) {
@@ -393,19 +381,7 @@ export async function startSupervisor() {
393
381
  }
394
382
  }
395
383
 
396
- const proxy = http.request(
397
- { host: '127.0.0.1', port: workerPort, path: req.url, method: req.method, headers: req.headers },
398
- (proxyRes) => {
399
- res.writeHead(proxyRes.statusCode!, proxyRes.headers);
400
- proxyRes.pipe(res);
401
- },
402
- );
403
- proxy.on('error', (e) => {
404
- console.error(`[supervisor] Worker proxy error: ${req.url}`, e.message);
405
- res.writeHead(503, { 'Content-Type': 'text/html' });
406
- res.end(RECOVERING_HTML);
407
- });
408
- req.pipe(proxy);
384
+ workerApp(req, res);
409
385
  return;
410
386
  }
411
387
 
@@ -1047,8 +1023,7 @@ export async function startSupervisor() {
1047
1023
  }
1048
1024
  }
1049
1025
 
1050
- // Spawn worker + backend
1051
- spawnWorker(workerPort);
1026
+ // Spawn backend (worker runs in-process)
1052
1027
  spawnBackend(backendPort);
1053
1028
 
1054
1029
  // Start pulse/cron scheduler
@@ -1277,7 +1252,7 @@ export async function startSupervisor() {
1277
1252
  // Clear persisted tunnel URL so stale values aren't reused
1278
1253
  delete latestConfig.tunnelUrl;
1279
1254
  saveConfig(latestConfig);
1280
- stopWorker();
1255
+ closeDb();
1281
1256
  await stopBackend();
1282
1257
  stopTunnel();
1283
1258
  console.log('[supervisor] Stopping Vite dev servers...');
package/worker/index.ts CHANGED
@@ -67,9 +67,7 @@ function parseCookie(cookieHeader: string | undefined, name: string): string | u
67
67
  return match ? match.slice(name.length + 1) : undefined;
68
68
  }
69
69
 
70
- const port = parseInt(process.env.WORKER_PORT || '3001', 10);
71
- const config = loadConfig();
72
-
70
+ export function createWorkerApp() {
73
71
  // Database
74
72
  initDb();
75
73
 
@@ -882,10 +880,7 @@ app.post('/api/whisper/transcribe', express.json({ limit: '10mb' }), async (req,
882
880
  // Serve stored files (audio, images, documents)
883
881
  app.use('/api/files', express.static(paths.files));
884
882
 
885
- // Dashboard is served by Vite dev server via supervisor proxy — no static files here
886
- console.log('[worker] API-only mode — dashboard served by Vite dev server');
887
-
888
- // HTTP server (no WebSocket — chat lives in supervisor)
889
- const server = app.listen(port, () => log.ok(`Worker on port ${port}`));
883
+ log.ok('Worker routes initialized (in-process)');
890
884
 
891
- process.on('SIGTERM', () => { closeDb(); server.close(); process.exit(0); });
885
+ return app;
886
+ }
@@ -1,44 +0,0 @@
1
- import { Command } from 'commander';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import pc from 'picocolors';
5
- import Database from 'better-sqlite3';
6
-
7
- import { DATA_DIR } from '../core/config.js';
8
-
9
- const DB_PATH = path.join(DATA_DIR, 'memory.db');
10
-
11
- export function registerPasswordResetCommand(program: Command) {
12
- program
13
- .command('password-reset')
14
- .description('Reset your portal password via the onboard wizard')
15
- .action(() => {
16
- if (!fs.existsSync(DB_PATH)) {
17
- console.log(pc.red('✗ No database found. Run ' + pc.magenta('fluxy init') + ' first.'));
18
- process.exit(1);
19
- }
20
-
21
- const db = new Database(DB_PATH);
22
-
23
- // Clear password and TOTP credentials
24
- db.prepare("DELETE FROM settings WHERE key IN ('portal_pass', 'totp_enabled', 'totp_secret', 'totp_recovery_codes')").run();
25
-
26
- // Reset onboard flag so the wizard appears on next visit
27
- db.prepare("INSERT INTO settings (key, value) VALUES ('onboard_complete', 'false') ON CONFLICT(key) DO UPDATE SET value = 'false', updated_at = CURRENT_TIMESTAMP").run();
28
-
29
- // Invalidate all active sessions
30
- db.prepare('DELETE FROM sessions').run();
31
- db.prepare('DELETE FROM trusted_devices').run();
32
-
33
- db.close();
34
-
35
- console.log();
36
- console.log(pc.green('✓ Password reset successful.'));
37
- console.log();
38
- console.log(pc.dim(' The onboard wizard will appear on your next visit'));
39
- console.log(pc.dim(' so you can create a new password.'));
40
- console.log();
41
- console.log(pc.dim(' Restart Fluxy if it\'s running: ') + pc.magenta('fluxy stop && fluxy start'));
42
- console.log();
43
- });
44
- }