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 +37 -0
- package/cli/index.ts +0 -2
- package/package.json +1 -1
- package/supervisor/index.ts +15 -40
- package/worker/index.ts +4 -9
- package/cli/commands/password-reset.ts +0 -44
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
package/supervisor/index.ts
CHANGED
|
@@ -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 {
|
|
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(
|
|
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:${
|
|
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
|
|
256
|
-
|
|
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
|
|
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 →
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|