fluxy-bot 0.9.1 → 0.9.3
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 +57 -45
- package/cli/commands/update.ts +43 -34
- package/package.json +2 -2
- package/supervisor/index.ts +12 -2
package/bin/cli.js
CHANGED
|
@@ -1232,8 +1232,8 @@ async function update() {
|
|
|
1232
1232
|
const daemonWasRunning = isDaemonInstalled() && isDaemonActive();
|
|
1233
1233
|
|
|
1234
1234
|
const steps = [
|
|
1235
|
-
...(daemonWasRunning ? ['Stopping daemon'] : []),
|
|
1236
1235
|
'Downloading update',
|
|
1236
|
+
...(daemonWasRunning ? ['Stopping daemon'] : []),
|
|
1237
1237
|
'Updating files',
|
|
1238
1238
|
'Installing dependencies',
|
|
1239
1239
|
'Building interface',
|
|
@@ -1243,20 +1243,7 @@ async function update() {
|
|
|
1243
1243
|
const stepper = new Stepper(steps);
|
|
1244
1244
|
stepper.start();
|
|
1245
1245
|
|
|
1246
|
-
//
|
|
1247
|
-
if (daemonWasRunning) {
|
|
1248
|
-
try {
|
|
1249
|
-
if (PLATFORM === 'darwin') {
|
|
1250
|
-
execSync(`launchctl unload "${LAUNCHD_PLIST_PATH}"`, { stdio: 'ignore' });
|
|
1251
|
-
} else {
|
|
1252
|
-
const cmd = needsSudo() ? `sudo systemctl stop ${SERVICE_NAME}` : `systemctl stop ${SERVICE_NAME}`;
|
|
1253
|
-
execSync(cmd, { stdio: 'ignore' });
|
|
1254
|
-
}
|
|
1255
|
-
} catch {}
|
|
1256
|
-
stepper.advance();
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
// Download tarball
|
|
1246
|
+
// Download tarball FIRST — before stopping daemon, so failure doesn't leave bot offline
|
|
1260
1247
|
const tarballUrl = latest.dist.tarball;
|
|
1261
1248
|
const tmpDir = path.join(os.tmpdir(), `fluxy-update-${Date.now()}`);
|
|
1262
1249
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -1264,65 +1251,90 @@ async function update() {
|
|
|
1264
1251
|
|
|
1265
1252
|
try {
|
|
1266
1253
|
const res = await fetch(tarballUrl);
|
|
1267
|
-
if (!res.ok) throw new Error();
|
|
1254
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1268
1255
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
1269
1256
|
fs.writeFileSync(tarball, buf);
|
|
1270
1257
|
execSync(`tar xzf "${tarball}" -C "${tmpDir}"`, { stdio: 'ignore' });
|
|
1271
|
-
} catch {
|
|
1258
|
+
} catch (e) {
|
|
1272
1259
|
stepper.finish();
|
|
1273
|
-
console.log(`\n ${c.red}✗${c.reset} Download failed\n`);
|
|
1260
|
+
console.log(`\n ${c.red}✗${c.reset} Download failed: ${e.message}\n`);
|
|
1274
1261
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
1275
1262
|
process.exit(1);
|
|
1276
1263
|
}
|
|
1277
1264
|
stepper.advance();
|
|
1278
1265
|
|
|
1266
|
+
// Stop daemon AFTER download succeeds — minimizes downtime
|
|
1267
|
+
if (daemonWasRunning) {
|
|
1268
|
+
try {
|
|
1269
|
+
if (PLATFORM === 'darwin') {
|
|
1270
|
+
execSync(`launchctl unload "${LAUNCHD_PLIST_PATH}"`, { stdio: 'ignore' });
|
|
1271
|
+
} else {
|
|
1272
|
+
const cmd = needsSudo() ? `sudo systemctl stop ${SERVICE_NAME}` : `systemctl stop ${SERVICE_NAME}`;
|
|
1273
|
+
execSync(cmd, { stdio: 'ignore' });
|
|
1274
|
+
}
|
|
1275
|
+
} catch (e) {
|
|
1276
|
+
console.log(` ${c.yellow}⚠${c.reset} Could not stop daemon: ${e.message}`);
|
|
1277
|
+
}
|
|
1278
|
+
stepper.advance();
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1279
1281
|
const extracted = path.join(tmpDir, 'package');
|
|
1280
1282
|
|
|
1281
1283
|
// Update code directories (preserve workspace/ user data)
|
|
1282
|
-
|
|
1283
|
-
const
|
|
1284
|
-
|
|
1285
|
-
fs.
|
|
1284
|
+
try {
|
|
1285
|
+
for (const dir of ['bin', 'supervisor', 'worker', 'shared', 'scripts']) {
|
|
1286
|
+
const src = path.join(extracted, dir);
|
|
1287
|
+
if (fs.existsSync(src)) {
|
|
1288
|
+
fs.cpSync(src, path.join(DATA_DIR, dir), { recursive: true, force: true });
|
|
1289
|
+
}
|
|
1286
1290
|
}
|
|
1287
|
-
}
|
|
1288
1291
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1292
|
+
// Copy workspace template only if it doesn't exist yet
|
|
1293
|
+
if (!fs.existsSync(path.join(DATA_DIR, 'workspace'))) {
|
|
1294
|
+
const wsSrc = path.join(extracted, 'workspace');
|
|
1295
|
+
if (fs.existsSync(wsSrc)) {
|
|
1296
|
+
fs.cpSync(wsSrc, path.join(DATA_DIR, 'workspace'), { recursive: true });
|
|
1297
|
+
}
|
|
1294
1298
|
}
|
|
1295
|
-
}
|
|
1296
1299
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1300
|
+
// Update code files (never touches config.json, memory.db, etc.)
|
|
1301
|
+
for (const file of ['package.json', 'vite.config.ts', 'vite.fluxy.config.ts', 'tsconfig.json', 'postcss.config.js', 'components.json']) {
|
|
1302
|
+
const src = path.join(extracted, file);
|
|
1303
|
+
if (fs.existsSync(src)) {
|
|
1304
|
+
fs.copyFileSync(src, path.join(DATA_DIR, file));
|
|
1305
|
+
}
|
|
1302
1306
|
}
|
|
1303
|
-
}
|
|
1304
1307
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1308
|
+
// Update pre-built UI from tarball
|
|
1309
|
+
const distSrc = path.join(extracted, 'dist-fluxy');
|
|
1310
|
+
if (fs.existsSync(distSrc)) {
|
|
1311
|
+
const dst = path.join(DATA_DIR, 'dist-fluxy');
|
|
1312
|
+
if (fs.existsSync(dst)) fs.rmSync(dst, { recursive: true });
|
|
1313
|
+
fs.cpSync(distSrc, dst, { recursive: true });
|
|
1314
|
+
}
|
|
1315
|
+
} catch (e) {
|
|
1316
|
+
console.log(` ${c.red}✗${c.reset} File copy failed: ${e.message}`);
|
|
1317
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
1318
|
+
process.exit(1);
|
|
1311
1319
|
}
|
|
1312
1320
|
|
|
1313
1321
|
stepper.advance();
|
|
1314
1322
|
|
|
1315
|
-
|
|
1323
|
+
const distDst = path.join(DATA_DIR, 'dist-fluxy');
|
|
1324
|
+
|
|
1325
|
+
// Install dependencies (5 min timeout to prevent hanging forever)
|
|
1316
1326
|
try {
|
|
1317
|
-
execSync('npm install --omit=dev', { cwd: DATA_DIR, stdio: 'ignore' });
|
|
1318
|
-
} catch {
|
|
1327
|
+
execSync('npm install --omit=dev', { cwd: DATA_DIR, stdio: 'ignore', timeout: 300_000 });
|
|
1328
|
+
} catch (e) {
|
|
1329
|
+
console.log(` ${c.yellow}⚠${c.reset} npm install issue: ${e.message}`);
|
|
1330
|
+
}
|
|
1319
1331
|
stepper.advance();
|
|
1320
1332
|
|
|
1321
1333
|
// Rebuild UI if not in tarball
|
|
1322
1334
|
if (!fs.existsSync(path.join(distDst, 'onboard.html'))) {
|
|
1323
1335
|
try {
|
|
1324
1336
|
if (fs.existsSync(distDst)) fs.rmSync(distDst, { recursive: true });
|
|
1325
|
-
execSync('npm run build:fluxy', { cwd: DATA_DIR, stdio: 'ignore' });
|
|
1337
|
+
execSync('npm run build:fluxy', { cwd: DATA_DIR, stdio: 'ignore', timeout: 300_000 });
|
|
1326
1338
|
} catch {}
|
|
1327
1339
|
}
|
|
1328
1340
|
stepper.advance();
|
package/cli/commands/update.ts
CHANGED
|
@@ -42,18 +42,7 @@ export function registerUpdateCommand(program: Command) {
|
|
|
42
42
|
const adapter = getAdapter();
|
|
43
43
|
const daemonWasRunning = fs.existsSync(DATA_DIR) && adapter.isInstalled;
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
s.message('Stopping daemon...');
|
|
47
|
-
try {
|
|
48
|
-
adapter.handleDaemonAction('stop', {
|
|
49
|
-
user: os.userInfo().username,
|
|
50
|
-
home: os.homedir(),
|
|
51
|
-
nodePath: process.execPath,
|
|
52
|
-
dataDir: DATA_DIR,
|
|
53
|
-
});
|
|
54
|
-
} catch {}
|
|
55
|
-
}
|
|
56
|
-
|
|
45
|
+
// Download tarball FIRST — before stopping daemon, so failure doesn't leave bot offline
|
|
57
46
|
const tmpDir = path.join(os.tmpdir(), `fluxy-update-${Date.now()}`);
|
|
58
47
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
59
48
|
const tarballFilePath = path.join(tmpDir, 'fluxy.tgz');
|
|
@@ -70,48 +59,68 @@ export function registerUpdateCommand(program: Command) {
|
|
|
70
59
|
process.exit(1);
|
|
71
60
|
}
|
|
72
61
|
|
|
62
|
+
// Stop daemon AFTER download succeeds — minimizes downtime
|
|
63
|
+
if (daemonWasRunning && adapter.isActive) {
|
|
64
|
+
s.message('Stopping daemon...');
|
|
65
|
+
try {
|
|
66
|
+
adapter.handleDaemonAction('stop', {
|
|
67
|
+
user: os.userInfo().username,
|
|
68
|
+
home: os.homedir(),
|
|
69
|
+
nodePath: process.execPath,
|
|
70
|
+
dataDir: DATA_DIR,
|
|
71
|
+
});
|
|
72
|
+
} catch {}
|
|
73
|
+
}
|
|
74
|
+
|
|
73
75
|
s.message('Updating files...');
|
|
74
76
|
const extracted = path.join(tmpDir, 'package');
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
fs.
|
|
78
|
+
try {
|
|
79
|
+
for (const dir of ['bin', 'supervisor', 'worker', 'shared', 'scripts']) {
|
|
80
|
+
const src = path.join(extracted, dir);
|
|
81
|
+
if (fs.existsSync(src)) {
|
|
82
|
+
fs.cpSync(src, path.join(DATA_DIR, dir), { recursive: true, force: true });
|
|
83
|
+
}
|
|
80
84
|
}
|
|
81
|
-
}
|
|
82
85
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
const wsSrc = path.join(extracted, 'workspace');
|
|
87
|
+
if (!fs.existsSync(path.join(DATA_DIR, 'workspace')) && fs.existsSync(wsSrc)) {
|
|
88
|
+
fs.cpSync(wsSrc, path.join(DATA_DIR, 'workspace'), { recursive: true });
|
|
89
|
+
}
|
|
87
90
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
for (const file of ['package.json', 'vite.config.ts', 'vite.fluxy.config.ts', 'tsconfig.json', 'postcss.config.js', 'components.json']) {
|
|
92
|
+
const src = path.join(extracted, file);
|
|
93
|
+
if (fs.existsSync(src)) {
|
|
94
|
+
fs.cpSync(src, path.join(DATA_DIR, file), { force: true });
|
|
95
|
+
}
|
|
92
96
|
}
|
|
93
|
-
}
|
|
94
97
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
const distSrc = path.join(extracted, 'dist-fluxy');
|
|
99
|
+
const distDst = path.join(DATA_DIR, 'dist-fluxy');
|
|
100
|
+
if (fs.existsSync(distSrc)) {
|
|
101
|
+
fs.rmSync(distDst, { recursive: true, force: true });
|
|
102
|
+
fs.cpSync(distSrc, distDst, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
} catch (e: any) {
|
|
105
|
+
s.stop(pc.red('File copy failed: ' + e.message));
|
|
106
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
107
|
+
process.exit(1);
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
s.message('Installing dependencies...');
|
|
103
111
|
try {
|
|
104
|
-
execSync('npm install --omit=dev', { cwd: DATA_DIR, stdio: 'ignore' });
|
|
112
|
+
execSync('npm install --omit=dev', { cwd: DATA_DIR, stdio: 'ignore', timeout: 300_000 });
|
|
105
113
|
} catch {}
|
|
106
114
|
|
|
115
|
+
const distDst = path.join(DATA_DIR, 'dist-fluxy');
|
|
107
116
|
if (!fs.existsSync(path.join(distDst, 'onboard.html'))) {
|
|
108
117
|
s.message('Building interface...');
|
|
109
118
|
try {
|
|
110
|
-
execSync('npm run build:fluxy', { cwd: DATA_DIR, stdio: 'ignore' });
|
|
119
|
+
execSync('npm run build:fluxy', { cwd: DATA_DIR, stdio: 'ignore', timeout: 300_000 });
|
|
111
120
|
} catch {}
|
|
112
121
|
}
|
|
113
122
|
|
|
114
|
-
fs.writeFileSync(path.join(DATA_DIR, '
|
|
123
|
+
fs.writeFileSync(path.join(DATA_DIR, 'VERSION'), latest.version);
|
|
115
124
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
116
125
|
|
|
117
126
|
if (daemonWasRunning) {
|
package/package.json
CHANGED
package/supervisor/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { WebSocketServer, WebSocket } from 'ws';
|
|
|
6
6
|
import { loadConfig, saveConfig } from '../shared/config.js';
|
|
7
7
|
import { createProvider, type AiProvider, type ChatMessage } from '../shared/ai.js';
|
|
8
8
|
import { paths } from '../shared/paths.js';
|
|
9
|
-
import { PKG_DIR, WORKSPACE_DIR } from '../shared/paths.js';
|
|
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
12
|
import { spawnWorker, stopWorker, getWorkerPort, isWorkerAlive } from './worker.js';
|
|
@@ -782,13 +782,23 @@ export async function startSupervisor() {
|
|
|
782
782
|
// Run fluxy update in a detached process so it survives daemon stop/restart
|
|
783
783
|
function runDeferredUpdate() {
|
|
784
784
|
const cliPath = path.join(PKG_DIR, 'bin', 'cli.js');
|
|
785
|
+
const updateLog = path.join(DATA_DIR, 'update.log');
|
|
785
786
|
log.info('Deferred update triggered — running fluxy update...');
|
|
786
787
|
try {
|
|
788
|
+
const logFd = fs.openSync(updateLog, 'w');
|
|
787
789
|
const child = cpSpawn(process.execPath, [cliPath, 'update'], {
|
|
788
790
|
detached: true,
|
|
789
|
-
stdio: 'ignore',
|
|
791
|
+
stdio: ['ignore', logFd, logFd],
|
|
790
792
|
env: { ...process.env },
|
|
791
793
|
});
|
|
794
|
+
child.on('exit', (code) => {
|
|
795
|
+
try { fs.closeSync(logFd); } catch {}
|
|
796
|
+
if (code === 0) {
|
|
797
|
+
log.ok('Update process completed successfully');
|
|
798
|
+
} else {
|
|
799
|
+
log.error(`Update process exited with code ${code} — see ${updateLog}`);
|
|
800
|
+
}
|
|
801
|
+
});
|
|
792
802
|
child.unref();
|
|
793
803
|
} catch (err) {
|
|
794
804
|
log.error(`Deferred update failed: ${err instanceof Error ? err.message : err}`);
|