fluxy-bot 0.8.9 → 0.9.0
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/README.md +53 -84
- package/bin/cli.js +1717 -6
- package/components.json +18 -18
- package/package.json +98 -109
- package/postcss.config.js +3 -3
- package/scripts/install.ps1 +1 -1
- package/scripts/install.sh +1 -1
- package/scripts/postinstall.js +47 -58
- package/shared/ai.ts +118 -202
- package/shared/config.ts +37 -39
- package/shared/logger.ts +6 -14
- package/shared/paths.ts +10 -16
- package/shared/relay.ts +89 -98
- package/supervisor/backend.ts +77 -89
- package/supervisor/chat/ARCHITECTURE.md +13 -14
- package/supervisor/chat/OnboardWizard.tsx +2194 -3419
- package/supervisor/chat/fluxy-main.tsx +495 -633
- package/supervisor/chat/fluxy.html +24 -36
- package/supervisor/chat/onboard-main.tsx +8 -8
- package/supervisor/chat/onboard.html +10 -13
- package/supervisor/chat/src/components/Chat/AudioBubble.tsx +94 -108
- package/supervisor/chat/src/components/Chat/ChatView.tsx +14 -36
- package/supervisor/chat/src/components/Chat/ImageLightbox.tsx +71 -89
- package/supervisor/chat/src/components/Chat/InputBar.tsx +498 -728
- package/supervisor/chat/src/components/Chat/MessageBubble.tsx +154 -246
- package/supervisor/chat/src/components/Chat/MessageList.tsx +114 -138
- package/supervisor/chat/src/components/Chat/TypingIndicator.tsx +38 -50
- package/supervisor/chat/src/components/LoginScreen.tsx +245 -296
- package/supervisor/chat/src/hooks/useChat.ts +210 -259
- package/supervisor/chat/src/hooks/useFluxyChat.ts +232 -322
- package/supervisor/chat/src/hooks/useSpeechRecognition.ts +126 -148
- package/supervisor/chat/src/lib/auth.ts +20 -23
- package/supervisor/chat/src/lib/ws-client.ts +113 -125
- package/supervisor/chat/src/styles/globals.css +76 -85
- package/supervisor/file-saver.ts +39 -52
- package/supervisor/fluxy-agent.ts +213 -292
- package/supervisor/index.ts +895 -1263
- package/supervisor/scheduler.ts +237 -299
- package/supervisor/tunnel.ts +113 -167
- package/supervisor/vite-dev.ts +51 -61
- package/supervisor/widget.js +135 -147
- package/supervisor/worker.ts +45 -49
- package/tsconfig.json +18 -24
- package/vite.config.ts +48 -55
- package/vite.fluxy.config.ts +34 -34
- package/worker/claude-auth.ts +202 -265
- package/worker/codex-auth.ts +144 -181
- package/worker/db.ts +63 -191
- package/worker/index.ts +601 -842
- package/workspace/MYSELF.md +1 -1
- package/workspace/PULSE.json +6 -6
- package/workspace/backend/index.ts +16 -19
- package/workspace/client/index.html +47 -60
- package/workspace/client/public/manifest.json +21 -21
- package/workspace/client/public/sw.js +29 -31
- package/workspace/client/src/App.tsx +91 -136
- package/workspace/client/src/components/Dashboard/DashboardPage.tsx +68 -72
- package/workspace/client/src/components/ErrorBoundary.tsx +11 -11
- package/workspace/client/src/components/Layout/DashboardLayout.tsx +35 -35
- package/workspace/client/src/components/Layout/Footer.tsx +12 -12
- package/workspace/client/src/components/Layout/MobileNav.tsx +23 -23
- package/workspace/client/src/components/Layout/Sidebar.tsx +81 -81
- package/workspace/client/src/components/ui/avatar.tsx +85 -85
- package/workspace/client/src/components/ui/badge.tsx +40 -41
- package/workspace/client/src/components/ui/button.tsx +56 -57
- package/workspace/client/src/components/ui/card.tsx +75 -75
- package/workspace/client/src/components/ui/dialog.tsx +114 -117
- package/workspace/client/src/components/ui/dropdown-menu.tsx +191 -199
- package/workspace/client/src/components/ui/input.tsx +17 -17
- package/workspace/client/src/components/ui/scroll-area.tsx +47 -47
- package/workspace/client/src/components/ui/select.tsx +145 -151
- package/workspace/client/src/components/ui/separator.tsx +21 -21
- package/workspace/client/src/components/ui/sheet.tsx +102 -102
- package/workspace/client/src/components/ui/skeleton.tsx +10 -10
- package/workspace/client/src/components/ui/switch.tsx +26 -26
- package/workspace/client/src/components/ui/tabs.tsx +69 -69
- package/workspace/client/src/components/ui/textarea.tsx +14 -14
- package/workspace/client/src/components/ui/tooltip.tsx +37 -37
- package/workspace/client/src/lib/utils.ts +1 -1
- package/workspace/client/src/main.tsx +3 -3
- package/workspace/client/src/styles/globals.css +76 -85
- package/workspace/skills/code-reviewer/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/code-reviewer/skills/code-reviewer/SKILL.md +0 -6
- package/workspace/skills/daily-standup/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/daily-standup/skills/daily-standup/SKILL.md +0 -7
- package/workspace/skills/workspace-helper/.claude-plugin/plugin.json +3 -3
- package/workspace/skills/workspace-helper/skills/workspace-helper/SKILL.md +0 -2
- package/bin/cli.backup.js +0 -2138
- package/cli/commands/daemon.ts +0 -42
- package/cli/commands/init.ts +0 -32
- package/cli/commands/start.ts +0 -113
- package/cli/commands/tunnel.temp.ts +0 -206
- package/cli/commands/tunnel.ts +0 -227
- package/cli/commands/update.ts +0 -163
- package/cli/core/base-adapter.ts +0 -156
- package/cli/core/cloudflared.ts +0 -113
- package/cli/core/config.ts +0 -73
- package/cli/core/os-detector.ts +0 -43
- package/cli/core/server.ts +0 -109
- package/cli/core/types.ts +0 -15
- package/cli/index.ts +0 -72
- package/cli/platforms/darwin.ts +0 -146
- package/cli/platforms/index.ts +0 -21
- package/cli/platforms/linux.ts +0 -156
- package/cli/platforms/win32.ts +0 -34
- package/cli/utils/ui.ts +0 -37
package/cli/commands/daemon.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// oxlint-disable no-console
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
|
|
5
|
-
import { DATA_DIR } from '../core/config.js';
|
|
6
|
-
import type { DaemonAction, DaemonConfig } from '../core/types.js';
|
|
7
|
-
import { getAdapter } from '../platforms/index.js';
|
|
8
|
-
|
|
9
|
-
export function registerDaemonCommand(program: Command) {
|
|
10
|
-
const adapter = getAdapter();
|
|
11
|
-
|
|
12
|
-
program
|
|
13
|
-
.command('daemon <action>')
|
|
14
|
-
.description(
|
|
15
|
-
'Manage the Fluxy background daemon (install, start, stop, restart, status, logs, uninstall)'
|
|
16
|
-
)
|
|
17
|
-
.action(async (action: string) => {
|
|
18
|
-
const validActions: DaemonAction[] = [
|
|
19
|
-
'install',
|
|
20
|
-
'start',
|
|
21
|
-
'stop',
|
|
22
|
-
'restart',
|
|
23
|
-
'status',
|
|
24
|
-
'logs',
|
|
25
|
-
'uninstall'
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
if (!validActions.includes(action as DaemonAction)) {
|
|
29
|
-
console.error(`Invalid daemon action: ${action}`);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const config: DaemonConfig = {
|
|
34
|
-
user: process.env.SUDO_USER || os.userInfo().username,
|
|
35
|
-
home: process.env.FLUXY_REAL_HOME || os.homedir(),
|
|
36
|
-
nodePath: process.env.FLUXY_NODE_PATH || process.execPath,
|
|
37
|
-
dataDir: DATA_DIR
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
await adapter.handleDaemonAction(action as DaemonAction, config);
|
|
41
|
-
});
|
|
42
|
-
}
|
package/cli/commands/init.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { safeLoadConfig, pkg, DATA_DIR, createConfig } from '../core/config.js';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import pc from 'picocolors';
|
|
6
|
-
import { runTunnelSetup } from './tunnel.js';
|
|
7
|
-
|
|
8
|
-
export function registerInitCommand(program: Command) {
|
|
9
|
-
program
|
|
10
|
-
.command('init')
|
|
11
|
-
.description('Initialize Fluxy configuration')
|
|
12
|
-
.action(async () => {
|
|
13
|
-
createConfig(); // Creates it if missing
|
|
14
|
-
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
15
|
-
fs.writeFileSync(
|
|
16
|
-
path.join(DATA_DIR, '.version'),
|
|
17
|
-
pkg.version || '1.0.0'
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
console.log(pc.green('✓ Initialized base config.\n'));
|
|
21
|
-
|
|
22
|
-
await runTunnelSetup();
|
|
23
|
-
|
|
24
|
-
console.log(
|
|
25
|
-
pc.dim(
|
|
26
|
-
'Run ' +
|
|
27
|
-
pc.magenta('fluxy start') +
|
|
28
|
-
' to launch the server.'
|
|
29
|
-
)
|
|
30
|
-
);
|
|
31
|
-
});
|
|
32
|
-
}
|
package/cli/commands/start.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { spinner } from '@clack/prompts';
|
|
3
|
-
import pc from 'picocolors';
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
|
|
6
|
-
import { loadConfig, createConfig, CONFIG_PATH } from '../core/config.ts';
|
|
7
|
-
import { getAdapter } from '../platforms/index.ts';
|
|
8
|
-
import { banner, commandExample } from '../utils/ui.ts';
|
|
9
|
-
import { bootServer } from '../core/server.ts';
|
|
10
|
-
import { CloudflaredManager } from '../core/cloudflared.ts';
|
|
11
|
-
|
|
12
|
-
export function registerStartCommand(program: Command) {
|
|
13
|
-
const adapter = getAdapter();
|
|
14
|
-
|
|
15
|
-
program
|
|
16
|
-
.command('start', { isDefault: true })
|
|
17
|
-
.description('Start the Fluxy server and tunnel')
|
|
18
|
-
.action(async () => {
|
|
19
|
-
banner();
|
|
20
|
-
|
|
21
|
-
if (!fs.existsSync(CONFIG_PATH)) {
|
|
22
|
-
console.log(
|
|
23
|
-
pc.yellow(
|
|
24
|
-
' ● Running first-time setup (creating config)...\n'
|
|
25
|
-
)
|
|
26
|
-
);
|
|
27
|
-
createConfig();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const config = loadConfig();
|
|
31
|
-
|
|
32
|
-
if (adapter.isInstalled && adapter.isActive) {
|
|
33
|
-
console.log(
|
|
34
|
-
pc.blue(' ● Fluxy is already running as a daemon.\n')
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
if (config.relay?.url) {
|
|
38
|
-
console.log(commandExample('URL: ', config.relay.url));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
console.log(commandExample(`Status: `, `fluxy daemon status`));
|
|
42
|
-
console.log(commandExample(`Logs: `, `fluxy daemon logs`));
|
|
43
|
-
console.log(commandExample(`Restart:`, `fluxy daemon restart`));
|
|
44
|
-
console.log(commandExample(`Stop: `, `fluxy daemon stop`));
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const tunnelMode =
|
|
49
|
-
config.tunnel?.mode ??
|
|
50
|
-
(config.tunnel?.enabled === false ? 'off' : 'quick');
|
|
51
|
-
const hasTunnel = tunnelMode !== 'off';
|
|
52
|
-
|
|
53
|
-
if (hasTunnel && tunnelMode !== 'named') {
|
|
54
|
-
const s1 = spinner();
|
|
55
|
-
s1.start('Ensuring Cloudflared is installed...');
|
|
56
|
-
CloudflaredManager.install();
|
|
57
|
-
s1.stop(pc.green('Cloudflared ready'));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const s = spinner();
|
|
61
|
-
s.start('Starting server');
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
const result = await bootServer({
|
|
65
|
-
onTunnelUp: url => {
|
|
66
|
-
if (!hasTunnel) return;
|
|
67
|
-
s.message(
|
|
68
|
-
`Connecting tunnel... up at ${pc.blue(url || '')}`
|
|
69
|
-
);
|
|
70
|
-
},
|
|
71
|
-
onReady: () => {
|
|
72
|
-
s.message('Preparing dashboard...');
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
s.stop(pc.green('Server running'));
|
|
77
|
-
|
|
78
|
-
console.log(`\n${pc.bold('Fluxy is ready!')}`);
|
|
79
|
-
console.log(
|
|
80
|
-
` ${pc.dim('Local:')} ${pc.blue(`http://localhost:${config.port || 3000}`)}`
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
if (result.tunnelUrl && hasTunnel) {
|
|
84
|
-
console.log(
|
|
85
|
-
` ${pc.dim('Tunnel:')} ${pc.blue(result.tunnelUrl)}`
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
if (result.relayUrl) {
|
|
89
|
-
console.log(
|
|
90
|
-
` ${pc.dim('Relay:')} ${pc.blue(result.relayUrl)}`
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// If not daemonized, we keep the process alive
|
|
95
|
-
console.log(`\n ${pc.dim('Press Ctrl+C to stop')}\n`);
|
|
96
|
-
|
|
97
|
-
// wait for Vite warm
|
|
98
|
-
Promise.race([
|
|
99
|
-
result.viteWarm,
|
|
100
|
-
new Promise(r => setTimeout(r, 60_000))
|
|
101
|
-
]).then(() => {
|
|
102
|
-
// done warming
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// Block forever
|
|
106
|
-
await new Promise(() => {});
|
|
107
|
-
} catch (err: any) {
|
|
108
|
-
s.stop(pc.red('Failed to start server'));
|
|
109
|
-
console.error(err.message);
|
|
110
|
-
process.exit(1);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
}
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { cancel, intro, isCancel, select, text, spinner } from '@clack/prompts';
|
|
3
|
-
import pc from 'picocolors';
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import os from 'node:os';
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
loadConfig,
|
|
10
|
-
safeLoadConfig,
|
|
11
|
-
CONFIG_PATH,
|
|
12
|
-
DATA_DIR
|
|
13
|
-
} from '../core/config.ts';
|
|
14
|
-
import { getAdapter } from '../platforms/index.ts';
|
|
15
|
-
import { CloudflaredManager } from '../core/cloudflared.ts';
|
|
16
|
-
|
|
17
|
-
async function runNamedTunnelSetup() {
|
|
18
|
-
CloudflaredManager.install();
|
|
19
|
-
|
|
20
|
-
console.log(pc.bold(pc.white('\n Step 1: Log in to Cloudflare\n')));
|
|
21
|
-
console.log(
|
|
22
|
-
pc.dim(
|
|
23
|
-
' This will open a browser window. Authorize the domain you want to use.\n'
|
|
24
|
-
)
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
CloudflaredManager.exec('tunnel login', { stdio: 'inherit' });
|
|
29
|
-
} catch (_e) {
|
|
30
|
-
console.error(pc.red('cloudflared login failed.'));
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const tunnelName = await text({
|
|
35
|
-
message: 'Tunnel name (default: fluxy):',
|
|
36
|
-
defaultValue: 'fluxy'
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
if (isCancel(tunnelName)) {
|
|
40
|
-
cancel('Setup cancelled.');
|
|
41
|
-
process.exit(0);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const s = spinner();
|
|
45
|
-
s.start(`Creating tunnel "${tunnelName}"...`);
|
|
46
|
-
|
|
47
|
-
let createOutput;
|
|
48
|
-
try {
|
|
49
|
-
createOutput = CloudflaredManager.exec(`tunnel create ${tunnelName}`, {
|
|
50
|
-
encoding: 'utf-8'
|
|
51
|
-
});
|
|
52
|
-
} catch (e: any) {
|
|
53
|
-
s.stop(pc.red('Failed to create tunnel. It may already exist.'));
|
|
54
|
-
console.log(
|
|
55
|
-
pc.dim('Try: fluxy tunnel list (or cloudflared tunnel list)')
|
|
56
|
-
);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const uuidMatch = createOutput?.match(
|
|
61
|
-
/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i
|
|
62
|
-
);
|
|
63
|
-
if (!uuidMatch) {
|
|
64
|
-
s.stop(pc.red('Could not parse tunnel UUID from output.'));
|
|
65
|
-
console.log(pc.dim(createOutput || ''));
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const tunnelUuid = uuidMatch[1];
|
|
70
|
-
s.stop(pc.green(`Tunnel created: ${pc.dim(tunnelUuid)}`));
|
|
71
|
-
|
|
72
|
-
const domain = await text({
|
|
73
|
-
message: 'Your domain (e.g. bot.mydomain.com):'
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
if (isCancel(domain) || !domain) {
|
|
77
|
-
cancel('Domain is required.');
|
|
78
|
-
process.exit(1);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const { port = 3000 } = safeLoadConfig();
|
|
82
|
-
const cfHome = path.join(os.homedir(), '.cloudflared');
|
|
83
|
-
const cfConfigPath = path.join(DATA_DIR, 'cloudflared-config.yml');
|
|
84
|
-
|
|
85
|
-
const yamlContent = `tunnel: ${tunnelUuid}\ncredentials-file: ${path.join(cfHome, `${tunnelUuid}.json`)}\ningress:\n - service: http://localhost:${port}\n`;
|
|
86
|
-
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
87
|
-
fs.writeFileSync(cfConfigPath, yamlContent);
|
|
88
|
-
|
|
89
|
-
console.log(`\n${pc.dim('─────────────────────────────────')}\n`);
|
|
90
|
-
console.log(` ${pc.bold(pc.white('Tunnel created!'))}`);
|
|
91
|
-
console.log(` ${pc.white('Add a CNAME record pointing to:')}\n`);
|
|
92
|
-
console.log(` ${pc.bold(pc.magenta(`${tunnelUuid}.cfargotunnel.com`))}\n`);
|
|
93
|
-
console.log(
|
|
94
|
-
` ${pc.dim(`Or run: cloudflared tunnel route dns ${tunnelName} ${domain}`)}\n`
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
return { tunnelName, domain, cfConfigPath };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function registerTunnelCommand(program: Command) {
|
|
101
|
-
const adapter = getAdapter();
|
|
102
|
-
|
|
103
|
-
program
|
|
104
|
-
.command('tunnel [subcommand]')
|
|
105
|
-
.description('Manage the Cloudflared tunnel (setup, login, reset)')
|
|
106
|
-
.action(async (subcommand?: string) => {
|
|
107
|
-
if (subcommand === 'login') {
|
|
108
|
-
CloudflaredManager.install();
|
|
109
|
-
console.log(pc.cyan('Logging into cloudflared...'));
|
|
110
|
-
CloudflaredManager.exec('tunnel login', { stdio: 'inherit' });
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (subcommand === 'reset') {
|
|
115
|
-
if (!fs.existsSync(CONFIG_PATH)) {
|
|
116
|
-
console.log(
|
|
117
|
-
pc.yellow('No config found. Run fluxy init first.')
|
|
118
|
-
);
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
const config = loadConfig();
|
|
122
|
-
config.tunnel = { mode: 'quick' };
|
|
123
|
-
delete config.tunnelUrl;
|
|
124
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
125
|
-
console.log(pc.green('Tunnel mode reset to quick.'));
|
|
126
|
-
if (adapter.isInstalled && adapter.isActive) {
|
|
127
|
-
console.log(
|
|
128
|
-
pc.dim(
|
|
129
|
-
'Restart the daemon to apply: ' +
|
|
130
|
-
pc.magenta('fluxy daemon restart')
|
|
131
|
-
)
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
intro(pc.inverse(' Fluxy Tunnel Setup '));
|
|
138
|
-
|
|
139
|
-
const mode = await select({
|
|
140
|
-
message: 'Select tunnel mode:',
|
|
141
|
-
options: [
|
|
142
|
-
{ value: 'quick', label: 'Quick (Random Cloudflare URL)' },
|
|
143
|
-
{
|
|
144
|
-
value: 'named',
|
|
145
|
-
label: 'Named (Persistent Custom Domain)'
|
|
146
|
-
},
|
|
147
|
-
{ value: 'off', label: 'Off (Local only)' }
|
|
148
|
-
]
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
if (isCancel(mode)) {
|
|
152
|
-
cancel('Operation cancelled.');
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
let setupData: any = {};
|
|
157
|
-
|
|
158
|
-
if (mode === 'named') {
|
|
159
|
-
setupData = await runNamedTunnelSetup();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const config = safeLoadConfig();
|
|
163
|
-
if (mode === 'named') {
|
|
164
|
-
config.tunnel = {
|
|
165
|
-
mode: 'named',
|
|
166
|
-
name: setupData.tunnelName,
|
|
167
|
-
domain: setupData.domain,
|
|
168
|
-
configPath: setupData.cfConfigPath
|
|
169
|
-
};
|
|
170
|
-
} else {
|
|
171
|
-
config.tunnel = { mode: mode as any };
|
|
172
|
-
delete config.tunnelUrl;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
176
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
177
|
-
|
|
178
|
-
console.log(pc.green('\n✓ Fluxy configuration updated.\n'));
|
|
179
|
-
|
|
180
|
-
if (adapter.isInstalled && adapter.isActive) {
|
|
181
|
-
const restart = await select({
|
|
182
|
-
message: 'Restart daemon now to apply changes?',
|
|
183
|
-
options: [
|
|
184
|
-
{ value: true, label: 'Yes' },
|
|
185
|
-
{ value: false, label: 'No' }
|
|
186
|
-
]
|
|
187
|
-
});
|
|
188
|
-
if (restart) {
|
|
189
|
-
adapter.handleDaemonAction('restart', {
|
|
190
|
-
user: process.env.SUDO_USER || os.userInfo().username,
|
|
191
|
-
home: process.env.FLUXY_REAL_HOME || os.homedir(),
|
|
192
|
-
nodePath:
|
|
193
|
-
process.env.FLUXY_NODE_PATH || process.execPath,
|
|
194
|
-
dataDir: DATA_DIR
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
} else {
|
|
198
|
-
console.log(
|
|
199
|
-
pc.dim(
|
|
200
|
-
'Start the server to apply changes: ' +
|
|
201
|
-
pc.magenta('fluxy start')
|
|
202
|
-
)
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
}
|
package/cli/commands/tunnel.ts
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { cancel, intro, isCancel, select, text, spinner } from '@clack/prompts';
|
|
3
|
-
import pc from 'picocolors';
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import os from 'node:os';
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
loadConfig,
|
|
10
|
-
safeLoadConfig,
|
|
11
|
-
CONFIG_PATH,
|
|
12
|
-
DATA_DIR
|
|
13
|
-
} from '../core/config.ts';
|
|
14
|
-
import { getAdapter } from '../platforms/index.ts';
|
|
15
|
-
import { CloudflaredManager } from '../core/cloudflared.ts';
|
|
16
|
-
|
|
17
|
-
async function runNamedTunnelSetup() {
|
|
18
|
-
CloudflaredManager.install();
|
|
19
|
-
|
|
20
|
-
console.log(pc.bold(pc.white('\n Step 1: Log in to Cloudflare\n')));
|
|
21
|
-
console.log(
|
|
22
|
-
pc.dim(
|
|
23
|
-
' This will open a browser window. Authorize the domain you want to use.\n'
|
|
24
|
-
)
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
CloudflaredManager.exec('tunnel login', { stdio: 'inherit' });
|
|
29
|
-
} catch (_e) {
|
|
30
|
-
console.error(pc.red('cloudflared login failed.'));
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const tunnelName = await text({
|
|
35
|
-
message: 'Tunnel name (default: fluxy):',
|
|
36
|
-
defaultValue: 'fluxy'
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
if (isCancel(tunnelName)) {
|
|
40
|
-
cancel('Setup cancelled.');
|
|
41
|
-
process.exit(0);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const s = spinner();
|
|
45
|
-
s.start(`Creating tunnel "${tunnelName}"...`);
|
|
46
|
-
|
|
47
|
-
let createOutput;
|
|
48
|
-
try {
|
|
49
|
-
createOutput = CloudflaredManager.exec(`tunnel create ${tunnelName}`, {
|
|
50
|
-
encoding: 'utf-8'
|
|
51
|
-
});
|
|
52
|
-
} catch (e: any) {
|
|
53
|
-
s.stop(pc.red('Failed to create tunnel. It may already exist.'));
|
|
54
|
-
console.log(
|
|
55
|
-
pc.dim('Try: fluxy tunnel list (or cloudflared tunnel list)')
|
|
56
|
-
);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const uuidMatch = createOutput?.match(
|
|
61
|
-
/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i
|
|
62
|
-
);
|
|
63
|
-
if (!uuidMatch) {
|
|
64
|
-
s.stop(pc.red('Could not parse tunnel UUID from output.'));
|
|
65
|
-
console.log(pc.dim(createOutput?.toString() || ''));
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const tunnelUuid = uuidMatch[1];
|
|
70
|
-
s.stop(pc.green(`Tunnel created: ${pc.dim(tunnelUuid)}`));
|
|
71
|
-
|
|
72
|
-
const domain = await text({
|
|
73
|
-
message: 'Your domain (e.g. bot.mydomain.com):'
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
if (isCancel(domain) || !domain) {
|
|
77
|
-
cancel('Domain is required.');
|
|
78
|
-
process.exit(1);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const { port = 3000 } = safeLoadConfig();
|
|
82
|
-
const cfHome = path.join(os.homedir(), '.cloudflared');
|
|
83
|
-
const cfConfigPath = path.join(DATA_DIR, 'cloudflared-config.yml');
|
|
84
|
-
|
|
85
|
-
const yamlContent = `tunnel: ${tunnelUuid}\ncredentials-file: ${path.join(cfHome, `${tunnelUuid}.json`)}\ningress:\n - service: http://localhost:${port}\n`;
|
|
86
|
-
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
87
|
-
fs.writeFileSync(cfConfigPath, yamlContent);
|
|
88
|
-
|
|
89
|
-
console.log(`\n${pc.dim('─────────────────────────────────')}\n`);
|
|
90
|
-
console.log(` ${pc.bold(pc.white('Tunnel created!'))}`);
|
|
91
|
-
console.log(` ${pc.white('Add a CNAME record pointing to:')}\n`);
|
|
92
|
-
console.log(` ${pc.bold(pc.magenta(`${tunnelUuid}.cfargotunnel.com`))}\n`);
|
|
93
|
-
console.log(
|
|
94
|
-
` ${pc.dim(`Or run: cloudflared tunnel route dns ${tunnelName} ${domain}`)}\n`
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
return { tunnelName, domain, cfConfigPath };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export async function runTunnelSetup() {
|
|
101
|
-
intro(pc.inverse(' Fluxy Tunnel Setup '));
|
|
102
|
-
|
|
103
|
-
const mode = await select({
|
|
104
|
-
message: 'How do you want to connect your bot?',
|
|
105
|
-
options: [
|
|
106
|
-
{
|
|
107
|
-
value: 'quick',
|
|
108
|
-
label: `Quick Tunnel ${pc.green('[Easy and Fast]')}`,
|
|
109
|
-
hint: pc.dim(
|
|
110
|
-
'Random CloudFlare tunnel URL on every start/update\n Optional: Use Fluxy Relay Server and access your bot at my.fluxy.bot/YOURBOT'
|
|
111
|
-
)
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
value: 'named',
|
|
115
|
-
label: `Named Tunnel ${pc.yellow('[Advanced]')}`,
|
|
116
|
-
hint: pc.dim(
|
|
117
|
-
'Persistent URL with your own domain\n Requires a CloudFlare account + domain'
|
|
118
|
-
)
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
value: 'off',
|
|
122
|
-
label: `Private Network ${pc.cyan('[Secure]')}`,
|
|
123
|
-
hint: pc.dim(
|
|
124
|
-
'No public URL — access via local network or VPN only\n Your bot stays invisible to the internet'
|
|
125
|
-
)
|
|
126
|
-
}
|
|
127
|
-
]
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
if (isCancel(mode)) {
|
|
131
|
-
cancel('Operation cancelled.');
|
|
132
|
-
process.exit(1);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
let setupData: any = {};
|
|
136
|
-
|
|
137
|
-
if (mode === 'named') {
|
|
138
|
-
setupData = await runNamedTunnelSetup();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const config = safeLoadConfig();
|
|
142
|
-
if (mode === 'named') {
|
|
143
|
-
config.tunnel = {
|
|
144
|
-
mode: 'named',
|
|
145
|
-
name: setupData.tunnelName,
|
|
146
|
-
domain: setupData.domain,
|
|
147
|
-
configPath: setupData.cfConfigPath
|
|
148
|
-
};
|
|
149
|
-
} else {
|
|
150
|
-
config.tunnel = { mode: mode as any };
|
|
151
|
-
delete config.tunnelUrl;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
155
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
156
|
-
|
|
157
|
-
console.log(pc.green('\n✓ Fluxy tunnel configuration updated.\n'));
|
|
158
|
-
|
|
159
|
-
return mode;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function registerTunnelCommand(program: Command) {
|
|
163
|
-
const adapter = getAdapter();
|
|
164
|
-
|
|
165
|
-
program
|
|
166
|
-
.command('tunnel [subcommand]')
|
|
167
|
-
.description('Manage the Cloudflared tunnel (setup, login, reset)')
|
|
168
|
-
.action(async (subcommand?: string) => {
|
|
169
|
-
if (subcommand === 'login') {
|
|
170
|
-
CloudflaredManager.install();
|
|
171
|
-
console.log(pc.cyan('Logging into cloudflared...'));
|
|
172
|
-
CloudflaredManager.exec('tunnel login', { stdio: 'inherit' });
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (subcommand === 'reset') {
|
|
177
|
-
if (!fs.existsSync(CONFIG_PATH)) {
|
|
178
|
-
console.log(
|
|
179
|
-
pc.yellow('No config found. Run fluxy init first.')
|
|
180
|
-
);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
const config = loadConfig();
|
|
184
|
-
config.tunnel = { mode: 'quick' };
|
|
185
|
-
delete config.tunnelUrl;
|
|
186
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
187
|
-
console.log(pc.green('Tunnel mode reset to quick.'));
|
|
188
|
-
if (adapter.isInstalled && adapter.isActive) {
|
|
189
|
-
console.log(
|
|
190
|
-
pc.dim(
|
|
191
|
-
'Restart the daemon to apply: ' +
|
|
192
|
-
pc.magenta('fluxy daemon restart')
|
|
193
|
-
)
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
await runTunnelSetup();
|
|
200
|
-
|
|
201
|
-
if (adapter.isInstalled && adapter.isActive) {
|
|
202
|
-
const restart = await select({
|
|
203
|
-
message: 'Restart daemon now to apply changes?',
|
|
204
|
-
options: [
|
|
205
|
-
{ value: true, label: 'Yes' },
|
|
206
|
-
{ value: false, label: 'No' }
|
|
207
|
-
]
|
|
208
|
-
});
|
|
209
|
-
if (restart === true) {
|
|
210
|
-
adapter.handleDaemonAction('restart', {
|
|
211
|
-
user: process.env.SUDO_USER || os.userInfo().username,
|
|
212
|
-
home: process.env.FLUXY_REAL_HOME || os.homedir(),
|
|
213
|
-
nodePath:
|
|
214
|
-
process.env.FLUXY_NODE_PATH || process.execPath,
|
|
215
|
-
dataDir: DATA_DIR
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
} else {
|
|
219
|
-
console.log(
|
|
220
|
-
pc.dim(
|
|
221
|
-
'Start the server to apply changes: ' +
|
|
222
|
-
pc.magenta('fluxy start')
|
|
223
|
-
)
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
}
|