fluxy-bot 0.8.9 → 0.8.10
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/cli/commands/init.ts +152 -11
- package/cli/core/server.ts +8 -2
- package/package.json +1 -1
package/cli/commands/init.ts
CHANGED
|
@@ -1,32 +1,173 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import {
|
|
2
|
+
import { spinner } from '@clack/prompts';
|
|
3
|
+
import pc from 'picocolors';
|
|
3
4
|
import fs from 'node:fs';
|
|
4
5
|
import path from 'node:path';
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
import { loadConfig, createConfig, CONFIG_PATH, DATA_DIR, pkg } from '../core/config.ts';
|
|
10
|
+
import { getAdapter } from '../platforms/index.ts';
|
|
11
|
+
import { banner } from '../utils/ui.ts';
|
|
12
|
+
import { bootServer } from '../core/server.ts';
|
|
13
|
+
import { CloudflaredManager } from '../core/cloudflared.ts';
|
|
14
|
+
import { runTunnelSetup } from './tunnel.ts';
|
|
7
15
|
|
|
8
16
|
export function registerInitCommand(program: Command) {
|
|
9
17
|
program
|
|
10
18
|
.command('init')
|
|
11
19
|
.description('Initialize Fluxy configuration')
|
|
12
20
|
.action(async () => {
|
|
13
|
-
|
|
21
|
+
banner();
|
|
22
|
+
|
|
23
|
+
// ── Step 1: Create config ──
|
|
24
|
+
createConfig();
|
|
14
25
|
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
15
26
|
fs.writeFileSync(
|
|
16
27
|
path.join(DATA_DIR, '.version'),
|
|
17
28
|
pkg.version || '1.0.0'
|
|
18
29
|
);
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
// ── Step 2: Tunnel mode selection ──
|
|
32
|
+
const tunnelMode = await runTunnelSetup();
|
|
33
|
+
|
|
34
|
+
const config = loadConfig();
|
|
35
|
+
const hasTunnel = config.tunnel?.mode !== 'off';
|
|
36
|
+
const adapter = getAdapter();
|
|
37
|
+
|
|
38
|
+
// ── Step 3: Install cloudflared ──
|
|
39
|
+
if (hasTunnel && config.tunnel?.mode !== 'named') {
|
|
40
|
+
const s1 = spinner();
|
|
41
|
+
s1.start('Installing cloudflared...');
|
|
42
|
+
CloudflaredManager.install();
|
|
43
|
+
s1.stop(pc.green('Cloudflared ready'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Step 4: Boot server ──
|
|
47
|
+
const s = spinner();
|
|
48
|
+
s.start('Starting server...');
|
|
49
|
+
|
|
50
|
+
let result;
|
|
51
|
+
try {
|
|
52
|
+
result = await bootServer({
|
|
53
|
+
onTunnelUp: hasTunnel
|
|
54
|
+
? url => {
|
|
55
|
+
s.message(
|
|
56
|
+
url
|
|
57
|
+
? `Tunnel connected: ${pc.blue(url)}`
|
|
58
|
+
: 'Connecting tunnel...'
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
: undefined,
|
|
62
|
+
onReady: () => {
|
|
63
|
+
s.message('Preparing dashboard...');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
} catch (err: any) {
|
|
67
|
+
s.stop(pc.red('Failed to start server'));
|
|
68
|
+
console.error(` ${err.message}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let { child, tunnelUrl, relayUrl, tunnelFailed, viteWarm } = result;
|
|
73
|
+
|
|
74
|
+
// ── Step 5: Wait for Vite warmup ──
|
|
75
|
+
s.message('Preparing dashboard...');
|
|
76
|
+
await Promise.race([viteWarm, new Promise(r => setTimeout(r, 30_000))]);
|
|
21
77
|
|
|
22
|
-
|
|
78
|
+
// ── Step 6: Install daemon (if supported) ──
|
|
79
|
+
if (adapter.hasDaemonSupport) {
|
|
80
|
+
s.message('Installing auto-start daemon...');
|
|
81
|
+
|
|
82
|
+
// Kill the temp server — daemon will start its own
|
|
83
|
+
child.kill('SIGTERM');
|
|
84
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
85
|
+
|
|
86
|
+
const res = spawnSync(
|
|
87
|
+
process.execPath,
|
|
88
|
+
[process.argv[1], 'daemon', 'install'],
|
|
89
|
+
{
|
|
90
|
+
stdio: 'pipe',
|
|
91
|
+
env: {
|
|
92
|
+
...process.env,
|
|
93
|
+
FLUXY_NODE_PATH: process.execPath,
|
|
94
|
+
FLUXY_REAL_HOME: os.homedir()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Wait for the daemon's supervisor to get a new tunnel URL
|
|
100
|
+
if (res.status === 0 && hasTunnel) {
|
|
101
|
+
for (let i = 0; i < 30; i++) {
|
|
102
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
103
|
+
try {
|
|
104
|
+
const cfg = JSON.parse(
|
|
105
|
+
fs.readFileSync(CONFIG_PATH, 'utf-8')
|
|
106
|
+
);
|
|
107
|
+
if (cfg.tunnelUrl && cfg.tunnelUrl !== tunnelUrl) {
|
|
108
|
+
tunnelUrl = cfg.tunnelUrl;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
} catch {}
|
|
112
|
+
}
|
|
113
|
+
// Pick up relay URL
|
|
114
|
+
try {
|
|
115
|
+
const cfg = JSON.parse(
|
|
116
|
+
fs.readFileSync(CONFIG_PATH, 'utf-8')
|
|
117
|
+
);
|
|
118
|
+
if (cfg.relay?.url) relayUrl = cfg.relay.url;
|
|
119
|
+
} catch {}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
s.stop(pc.green('Fluxy is ready'));
|
|
123
|
+
|
|
124
|
+
if (res.status === 0) {
|
|
125
|
+
console.log(
|
|
126
|
+
`\n ${pc.blue('✔')} Daemon installed — Fluxy will auto-start on ${os.platform() === 'darwin' ? 'login' : 'boot'}.`
|
|
127
|
+
);
|
|
128
|
+
} else {
|
|
129
|
+
console.log(
|
|
130
|
+
`\n ${pc.yellow('⚠')} Daemon install failed. Run ${pc.magenta('fluxy daemon install')} manually.`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
s.stop(pc.green('Server running'));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── Final message ──
|
|
138
|
+
console.log(`\n${pc.bold('Fluxy is ready!')}\n`);
|
|
23
139
|
|
|
24
140
|
console.log(
|
|
25
|
-
pc.dim(
|
|
26
|
-
'Run ' +
|
|
27
|
-
pc.magenta('fluxy start') +
|
|
28
|
-
' to launch the server.'
|
|
29
|
-
)
|
|
141
|
+
` ${pc.dim('Local:')} ${pc.blue(`http://localhost:${config.port || 3000}`)}`
|
|
30
142
|
);
|
|
143
|
+
|
|
144
|
+
if (hasTunnel && tunnelUrl && !tunnelFailed) {
|
|
145
|
+
console.log(
|
|
146
|
+
` ${pc.dim('Tunnel:')} ${pc.blue(tunnelUrl)}`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (relayUrl) {
|
|
151
|
+
console.log(
|
|
152
|
+
` ${pc.dim('Relay:')} ${pc.blue(relayUrl)}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (tunnelFailed) {
|
|
157
|
+
console.log(
|
|
158
|
+
`\n ${pc.yellow('⚠')} Tunnel failed to connect.`
|
|
159
|
+
);
|
|
160
|
+
console.log(
|
|
161
|
+
` ${pc.dim('Your bot is accessible on the local network at the URL above.')}`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!adapter.hasDaemonSupport) {
|
|
166
|
+
console.log(`\n ${pc.dim('Press Ctrl+C to stop')}\n`);
|
|
167
|
+
// Keep process alive
|
|
168
|
+
await new Promise(() => {});
|
|
169
|
+
} else {
|
|
170
|
+
console.log('');
|
|
171
|
+
}
|
|
31
172
|
});
|
|
32
173
|
}
|
package/cli/core/server.ts
CHANGED
|
@@ -57,10 +57,16 @@ export function bootServer({
|
|
|
57
57
|
config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// Fallback: supervisor writes tunnelUrl to config.json
|
|
61
|
+
// even if we missed the stdout signal
|
|
62
|
+
const finalTunnelUrl =
|
|
63
|
+
tunnelUrl ||
|
|
64
|
+
(config as any).tunnelUrl ||
|
|
65
|
+
`http://localhost:${config.port || 3000}`;
|
|
66
|
+
|
|
60
67
|
resolve({
|
|
61
68
|
child,
|
|
62
|
-
tunnelUrl:
|
|
63
|
-
tunnelUrl || `http://localhost:${config.port || 3000}`,
|
|
69
|
+
tunnelUrl: finalTunnelUrl,
|
|
64
70
|
relayUrl: relayUrl || config.relay?.url || null,
|
|
65
71
|
tunnelFailed,
|
|
66
72
|
viteWarm
|