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/update.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { spinner, intro, outro } 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
|
-
import { execSync } from 'node:child_process';
|
|
8
|
-
import { pkg, DATA_DIR } from '../core/config.js';
|
|
9
|
-
import { getAdapter } from '../platforms/index.js';
|
|
10
|
-
|
|
11
|
-
export function registerUpdateCommand(program: Command) {
|
|
12
|
-
program
|
|
13
|
-
.command('update')
|
|
14
|
-
.description('Update Fluxy to the latest version')
|
|
15
|
-
.action(async () => {
|
|
16
|
-
intro(pc.inverse(' Fluxy Update '));
|
|
17
|
-
const currentVersion = pkg.version;
|
|
18
|
-
console.log(pc.dim(`Current version: v${currentVersion}`));
|
|
19
|
-
|
|
20
|
-
const s = spinner();
|
|
21
|
-
s.start('Checking for updates...');
|
|
22
|
-
|
|
23
|
-
let latest;
|
|
24
|
-
try {
|
|
25
|
-
const res = await fetch(
|
|
26
|
-
'https://registry.npmjs.org/fluxy-bot/latest'
|
|
27
|
-
);
|
|
28
|
-
if (!res.ok) throw new Error('Refresh failed');
|
|
29
|
-
latest = await res.json();
|
|
30
|
-
} catch (e: any) {
|
|
31
|
-
s.stop(pc.red('Failed to check for updates. ' + e.message));
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (currentVersion === latest.version) {
|
|
36
|
-
s.stop(pc.green(`Already up to date (v${currentVersion})`));
|
|
37
|
-
outro('Nothing to do!');
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
s.message(`Found v${latest.version}. Downloading...`);
|
|
42
|
-
|
|
43
|
-
const adapter = getAdapter();
|
|
44
|
-
const daemonWasRunning =
|
|
45
|
-
fs.existsSync(DATA_DIR) && adapter.isInstalled; // simplified check
|
|
46
|
-
|
|
47
|
-
if (daemonWasRunning && adapter.isActive) {
|
|
48
|
-
s.message('Stopping daemon...');
|
|
49
|
-
try {
|
|
50
|
-
adapter.handleDaemonAction('stop', {
|
|
51
|
-
user: os.userInfo().username,
|
|
52
|
-
home: os.homedir(),
|
|
53
|
-
nodePath: process.execPath,
|
|
54
|
-
dataDir: DATA_DIR
|
|
55
|
-
});
|
|
56
|
-
} catch {}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const tmpDir = path.join(os.tmpdir(), `fluxy-update-${Date.now()}`);
|
|
60
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
61
|
-
const tarballFilePath = path.join(tmpDir, 'fluxy.tgz');
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
const res = await fetch(latest.dist.tarball);
|
|
65
|
-
if (!res.ok) throw new Error('Download failed');
|
|
66
|
-
const buf = Buffer.from(await res.arrayBuffer());
|
|
67
|
-
fs.writeFileSync(tarballFilePath, buf);
|
|
68
|
-
execSync(`tar xzf "${tarballFilePath}" -C "${tmpDir}"`, {
|
|
69
|
-
stdio: 'ignore'
|
|
70
|
-
});
|
|
71
|
-
} catch (e: any) {
|
|
72
|
-
s.stop(pc.red('Download failed: ' + e.message));
|
|
73
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
s.message('Updating files...');
|
|
78
|
-
const extracted = path.join(tmpDir, 'package');
|
|
79
|
-
|
|
80
|
-
for (const dir of [
|
|
81
|
-
'bin',
|
|
82
|
-
'supervisor',
|
|
83
|
-
'worker',
|
|
84
|
-
'shared',
|
|
85
|
-
'scripts'
|
|
86
|
-
]) {
|
|
87
|
-
const src = path.join(extracted, dir);
|
|
88
|
-
if (fs.existsSync(src)) {
|
|
89
|
-
fs.cpSync(src, path.join(DATA_DIR, dir), {
|
|
90
|
-
recursive: true,
|
|
91
|
-
force: true
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const wsSrc = path.join(extracted, 'workspace');
|
|
97
|
-
if (
|
|
98
|
-
!fs.existsSync(path.join(DATA_DIR, 'workspace')) &&
|
|
99
|
-
fs.existsSync(wsSrc)
|
|
100
|
-
) {
|
|
101
|
-
fs.cpSync(wsSrc, path.join(DATA_DIR, 'workspace'), {
|
|
102
|
-
recursive: true
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
for (const file of [
|
|
107
|
-
'package.json',
|
|
108
|
-
'vite.config.ts',
|
|
109
|
-
'vite.fluxy.config.ts',
|
|
110
|
-
'tsconfig.json',
|
|
111
|
-
'postcss.config.js',
|
|
112
|
-
'components.json'
|
|
113
|
-
]) {
|
|
114
|
-
const src = path.join(extracted, file);
|
|
115
|
-
if (fs.existsSync(src)) {
|
|
116
|
-
fs.cpSync(src, path.join(DATA_DIR, file), { force: true });
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const distSrc = path.join(extracted, 'dist-fluxy');
|
|
121
|
-
const distDst = path.join(DATA_DIR, 'dist-fluxy');
|
|
122
|
-
if (fs.existsSync(distSrc)) {
|
|
123
|
-
fs.rmSync(distDst, { recursive: true, force: true });
|
|
124
|
-
fs.cpSync(distSrc, distDst, { recursive: true });
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
s.message('Installing dependencies...');
|
|
128
|
-
try {
|
|
129
|
-
execSync('npm install --omit=dev', {
|
|
130
|
-
cwd: DATA_DIR,
|
|
131
|
-
stdio: 'ignore'
|
|
132
|
-
});
|
|
133
|
-
} catch {}
|
|
134
|
-
|
|
135
|
-
if (!fs.existsSync(path.join(distDst, 'onboard.html'))) {
|
|
136
|
-
s.message('Building interface...');
|
|
137
|
-
try {
|
|
138
|
-
execSync('npm run build:fluxy', {
|
|
139
|
-
cwd: DATA_DIR,
|
|
140
|
-
stdio: 'ignore'
|
|
141
|
-
});
|
|
142
|
-
} catch {}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
fs.writeFileSync(path.join(DATA_DIR, '.version'), latest.version);
|
|
146
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
147
|
-
|
|
148
|
-
if (daemonWasRunning) {
|
|
149
|
-
s.message('Restarting daemon...');
|
|
150
|
-
try {
|
|
151
|
-
adapter.handleDaemonAction('start', {
|
|
152
|
-
user: os.userInfo().username,
|
|
153
|
-
home: os.homedir(),
|
|
154
|
-
nodePath: process.execPath,
|
|
155
|
-
dataDir: DATA_DIR
|
|
156
|
-
});
|
|
157
|
-
} catch {}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
s.stop(pc.green(`Successfully updated to v${latest.version}`));
|
|
161
|
-
outro('Done!');
|
|
162
|
-
});
|
|
163
|
-
}
|
package/cli/core/base-adapter.ts
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
// oxlint-disable no-unused-vars
|
|
2
|
-
// oxlint-disable no-console
|
|
3
|
-
import pc from 'picocolors';
|
|
4
|
-
|
|
5
|
-
import { CloudflaredManager } from './cloudflared.js';
|
|
6
|
-
import { isUnixLike } from './os-detector.js';
|
|
7
|
-
import type { DaemonAction, DaemonConfig } from './types.js';
|
|
8
|
-
|
|
9
|
-
export abstract class BaseAdapter {
|
|
10
|
-
/**
|
|
11
|
-
* Properties
|
|
12
|
-
*/
|
|
13
|
-
abstract get hasDaemonSupport(): boolean;
|
|
14
|
-
abstract get isInstalled(): boolean;
|
|
15
|
-
abstract get isActive(): boolean;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Checks if the user is running as root (only relevant for unix-like).
|
|
19
|
-
*/
|
|
20
|
-
get isRoot(): boolean {
|
|
21
|
-
return isUnixLike() && process.getuid?.() === 0;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Subclasses can override this if a command needs privilege escalation.
|
|
26
|
-
*/
|
|
27
|
-
protected requiresPrivilegeEscalation(action: DaemonAction): boolean {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* If requiresPrivilegeEscalation returns true, this is called.
|
|
33
|
-
*/
|
|
34
|
-
protected escalatePrivileges(): void {
|
|
35
|
-
console.error(
|
|
36
|
-
pc.red(
|
|
37
|
-
'Privilege escalation is required for this action but not implemented for this platform.'
|
|
38
|
-
)
|
|
39
|
-
);
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Template Method handling the standard control flow for all OS daemons.
|
|
45
|
-
*/
|
|
46
|
-
public async handleDaemonAction(
|
|
47
|
-
action: DaemonAction,
|
|
48
|
-
config: DaemonConfig
|
|
49
|
-
): Promise<void> {
|
|
50
|
-
if (!this.hasDaemonSupport) {
|
|
51
|
-
console.log(
|
|
52
|
-
pc.yellow('\nDaemon mode is not supported on this platform.\n')
|
|
53
|
-
);
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (this.requiresPrivilegeEscalation(action)) {
|
|
58
|
-
return this.escalatePrivileges();
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
switch (action) {
|
|
62
|
-
case 'install':
|
|
63
|
-
console.log(pc.cyan('Ensuring Cloudflared is installed...'));
|
|
64
|
-
CloudflaredManager.install();
|
|
65
|
-
|
|
66
|
-
if (this.isInstalled) {
|
|
67
|
-
console.log(
|
|
68
|
-
pc.yellow(
|
|
69
|
-
'Daemon is already installed. Use "fluxy daemon restart" if needed.'
|
|
70
|
-
)
|
|
71
|
-
);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
console.log(pc.cyan('Installing service...'));
|
|
76
|
-
this.installService(config);
|
|
77
|
-
console.log(pc.green('✓ Daemon installed and started.'));
|
|
78
|
-
break;
|
|
79
|
-
|
|
80
|
-
case 'start':
|
|
81
|
-
if (!this.isInstalled) {
|
|
82
|
-
console.error(
|
|
83
|
-
pc.red(
|
|
84
|
-
'Daemon is not installed. Run `fluxy daemon install` first.'
|
|
85
|
-
)
|
|
86
|
-
);
|
|
87
|
-
process.exit(1);
|
|
88
|
-
}
|
|
89
|
-
if (this.isActive) {
|
|
90
|
-
console.log(pc.yellow('Daemon is already running.'));
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
console.log(pc.cyan('Starting daemon...'));
|
|
94
|
-
this.startService();
|
|
95
|
-
console.log(pc.green('✓ Daemon started.'));
|
|
96
|
-
break;
|
|
97
|
-
|
|
98
|
-
case 'stop':
|
|
99
|
-
if (!this.isInstalled) {
|
|
100
|
-
console.error(pc.red('Daemon is not installed.'));
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
console.log(pc.cyan('Stopping daemon...'));
|
|
104
|
-
this.stopService();
|
|
105
|
-
console.log(pc.green('✓ Daemon stopped.'));
|
|
106
|
-
break;
|
|
107
|
-
|
|
108
|
-
case 'restart':
|
|
109
|
-
if (!this.isInstalled) {
|
|
110
|
-
console.error(pc.red('Daemon is not installed.'));
|
|
111
|
-
process.exit(1);
|
|
112
|
-
}
|
|
113
|
-
console.log(pc.cyan('Restarting daemon...'));
|
|
114
|
-
this.stopService();
|
|
115
|
-
this.startService();
|
|
116
|
-
console.log(pc.green('✓ Daemon restarted.'));
|
|
117
|
-
break;
|
|
118
|
-
|
|
119
|
-
case 'status':
|
|
120
|
-
this.checkStatus();
|
|
121
|
-
break;
|
|
122
|
-
|
|
123
|
-
case 'logs':
|
|
124
|
-
if (!this.isInstalled) {
|
|
125
|
-
console.error(pc.red('Daemon is not installed.'));
|
|
126
|
-
process.exit(1);
|
|
127
|
-
}
|
|
128
|
-
this.showLogs();
|
|
129
|
-
break;
|
|
130
|
-
|
|
131
|
-
case 'uninstall':
|
|
132
|
-
if (!this.isInstalled) {
|
|
133
|
-
console.log(pc.yellow('Daemon is not installed.'));
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
console.log(pc.cyan('Uninstalling daemon...'));
|
|
137
|
-
this.uninstallService();
|
|
138
|
-
console.log(pc.green('✓ Daemon uninstalled.'));
|
|
139
|
-
break;
|
|
140
|
-
|
|
141
|
-
default:
|
|
142
|
-
console.error(pc.red(`Unknown daemon action: ${action}`));
|
|
143
|
-
process.exit(1);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* OS-specific implementation hooks
|
|
149
|
-
*/
|
|
150
|
-
protected abstract installService(config: DaemonConfig): void;
|
|
151
|
-
protected abstract startService(): void;
|
|
152
|
-
protected abstract stopService(): void;
|
|
153
|
-
protected abstract uninstallService(): void;
|
|
154
|
-
protected abstract showLogs(): void;
|
|
155
|
-
protected abstract checkStatus(): void;
|
|
156
|
-
}
|
package/cli/core/cloudflared.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import { execSync, spawnSync } from 'node:child_process';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
getNormalizedArchitecture,
|
|
6
|
-
getPlatform,
|
|
7
|
-
isWindows
|
|
8
|
-
} from './os-detector.js';
|
|
9
|
-
import { BIN_DIR, CF_PATH } from './config.js';
|
|
10
|
-
|
|
11
|
-
export class CloudflaredManager {
|
|
12
|
-
/**
|
|
13
|
-
* Determines the correct download URL for Cloudflared based on OS and Arch.
|
|
14
|
-
*/
|
|
15
|
-
static getDownloadUrl(): string {
|
|
16
|
-
const arch = getNormalizedArchitecture();
|
|
17
|
-
const platform = getPlatform();
|
|
18
|
-
|
|
19
|
-
const baseUrl =
|
|
20
|
-
'https://github.com/cloudflare/cloudflared/releases/latest/download';
|
|
21
|
-
|
|
22
|
-
if (platform === 'darwin') {
|
|
23
|
-
return `${baseUrl}/cloudflared-darwin-${arch}.tgz`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (platform === 'win32') {
|
|
27
|
-
return `${baseUrl}/cloudflared-windows-${arch}.exe`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Linux default
|
|
31
|
-
return `${baseUrl}/cloudflared-linux-${arch}`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Checks if a valid cloudflared binary is available (either system-wide or locally).
|
|
36
|
-
*/
|
|
37
|
-
static hasBinary(): boolean {
|
|
38
|
-
// 1. Try system-wide
|
|
39
|
-
try {
|
|
40
|
-
execSync('cloudflared --version', { stdio: 'ignore' });
|
|
41
|
-
return true;
|
|
42
|
-
} catch {
|
|
43
|
-
// not installed globally
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 2. Try local
|
|
47
|
-
if (fs.existsSync(CF_PATH)) {
|
|
48
|
-
const stats = fs.statSync(CF_PATH);
|
|
49
|
-
if (stats.size > 10 * 1024 * 1024) {
|
|
50
|
-
// >10MB
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Installs the local cloudflared binary if required.
|
|
60
|
-
*/
|
|
61
|
-
static install(): void {
|
|
62
|
-
if (this.hasBinary()) return;
|
|
63
|
-
|
|
64
|
-
const url = this.getDownloadUrl();
|
|
65
|
-
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
66
|
-
|
|
67
|
-
if (isWindows()) {
|
|
68
|
-
execSync(`curl.exe -fsSL -o "${CF_PATH}" "${url}"`, {
|
|
69
|
-
stdio: 'inherit'
|
|
70
|
-
});
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (url.endsWith('.tgz')) {
|
|
75
|
-
execSync(`curl -fsSL "${url}" | tar xz -C "${BIN_DIR}"`, {
|
|
76
|
-
stdio: 'inherit'
|
|
77
|
-
});
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
execSync(`curl -fsSL -o "${CF_PATH}" "${url}"`, { stdio: 'inherit' });
|
|
82
|
-
fs.chmodSync(CF_PATH, 0o755);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Executes cloudflared with the correct binary, trying system first, then local.
|
|
87
|
-
*/
|
|
88
|
-
static spawn(args: string[], options: any = {}) {
|
|
89
|
-
try {
|
|
90
|
-
// Check if global exists by running it with --version
|
|
91
|
-
execSync('cloudflared --version', { stdio: 'ignore' });
|
|
92
|
-
return spawnSync('cloudflared', args, options);
|
|
93
|
-
} catch {
|
|
94
|
-
// Use local
|
|
95
|
-
if (!this.hasBinary()) {
|
|
96
|
-
this.install();
|
|
97
|
-
}
|
|
98
|
-
return spawnSync(CF_PATH, args, options);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
static exec(command: string, options: any = {}) {
|
|
103
|
-
try {
|
|
104
|
-
execSync('cloudflared --version', { stdio: 'ignore' });
|
|
105
|
-
return execSync(`cloudflared ${command}`, options);
|
|
106
|
-
} catch {
|
|
107
|
-
if (!this.hasBinary()) {
|
|
108
|
-
this.install();
|
|
109
|
-
}
|
|
110
|
-
return execSync(`"${CF_PATH}" ${command}`, options);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
package/cli/core/config.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import url from 'node:url';
|
|
5
|
-
|
|
6
|
-
import type { BotConfig } from '../../shared/config.js';
|
|
7
|
-
|
|
8
|
-
export const PLATFORM = os.platform();
|
|
9
|
-
|
|
10
|
-
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
|
|
11
|
-
export const REPO_ROOT = path.resolve(__dirname, '../..');
|
|
12
|
-
|
|
13
|
-
export const DATA_DIR = path.join(
|
|
14
|
-
process.env.FLUXY_REAL_HOME || os.homedir(),
|
|
15
|
-
'.fluxy'
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
export const IS_DEV = fs.existsSync(path.join(REPO_ROOT, '.git'));
|
|
19
|
-
export const ROOT = IS_DEV ? REPO_ROOT : DATA_DIR;
|
|
20
|
-
export const CONFIG_PATH = path.join(DATA_DIR, 'config.json');
|
|
21
|
-
export const BIN_DIR = path.join(DATA_DIR, 'bin');
|
|
22
|
-
export const CF_PATH = path.join(
|
|
23
|
-
BIN_DIR,
|
|
24
|
-
'cloudflared' + (PLATFORM === 'win32' ? '.exe' : '')
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
export const SERVICE_NAME = 'fluxy';
|
|
28
|
-
|
|
29
|
-
export const pkg = JSON.parse(
|
|
30
|
-
fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8')
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
export function loadConfig(): BotConfig {
|
|
34
|
-
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function createConfig() {
|
|
38
|
-
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
39
|
-
if (fs.existsSync(CONFIG_PATH)) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const config: Partial<BotConfig> = {
|
|
44
|
-
port: 3000,
|
|
45
|
-
username: '',
|
|
46
|
-
ai: {
|
|
47
|
-
provider: '',
|
|
48
|
-
model: '',
|
|
49
|
-
apiKey: ''
|
|
50
|
-
},
|
|
51
|
-
tunnel: {
|
|
52
|
-
mode: 'quick'
|
|
53
|
-
},
|
|
54
|
-
relay: {
|
|
55
|
-
token: '',
|
|
56
|
-
tier: '',
|
|
57
|
-
url: ''
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function safeLoadConfig(fallback: Partial<BotConfig> = {}): BotConfig {
|
|
64
|
-
if (!fs.existsSync(CONFIG_PATH)) {
|
|
65
|
-
return fallback as BotConfig;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
return loadConfig();
|
|
70
|
-
} catch {
|
|
71
|
-
return fallback as BotConfig;
|
|
72
|
-
}
|
|
73
|
-
}
|
package/cli/core/os-detector.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
|
|
3
|
-
export function getPlatform(): string {
|
|
4
|
-
return os.platform();
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function isWindows(): boolean {
|
|
8
|
-
return getPlatform() === 'win32';
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function isUnixLike(): boolean {
|
|
12
|
-
return getPlatform() !== 'win32';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function getRawArchitecture(): string {
|
|
16
|
-
return (process.env.PROCESSOR_ARCHITECTURE || os.arch()).toLowerCase();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Normalizes the architecture specifically for cloudflared download links.
|
|
21
|
-
*/
|
|
22
|
-
export function getNormalizedArchitecture(): string {
|
|
23
|
-
const rawArch = getRawArchitecture();
|
|
24
|
-
|
|
25
|
-
if (['arm64', 'aarch64'].includes(rawArch)) {
|
|
26
|
-
return 'arm64';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (['arm', 'armv7l'].includes(rawArch)) {
|
|
30
|
-
return 'arm';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (rawArch === 'x64' || rawArch === 'amd64' || rawArch === 'x86_64') {
|
|
34
|
-
return 'amd64';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (rawArch === '386' || rawArch === 'ia32' || rawArch === 'x86') {
|
|
38
|
-
return '386';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Default fallback
|
|
42
|
-
return 'amd64';
|
|
43
|
-
}
|
package/cli/core/server.ts
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { spawn, type ChildProcess } from 'node:child_process';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import { ROOT, CONFIG_PATH, type BotConfig } from './config.ts';
|
|
5
|
-
|
|
6
|
-
export interface BootResult {
|
|
7
|
-
child: ChildProcess;
|
|
8
|
-
tunnelUrl: string;
|
|
9
|
-
relayUrl: string | null;
|
|
10
|
-
tunnelFailed: boolean;
|
|
11
|
-
viteWarm: Promise<void>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function bootServer({
|
|
15
|
-
onTunnelUp,
|
|
16
|
-
onReady
|
|
17
|
-
}: {
|
|
18
|
-
onTunnelUp?: (url?: string) => void;
|
|
19
|
-
onReady?: () => void;
|
|
20
|
-
} = {}): Promise<BootResult> {
|
|
21
|
-
return new Promise(resolve => {
|
|
22
|
-
const child = spawn(
|
|
23
|
-
process.execPath,
|
|
24
|
-
['--import', 'tsx/esm', path.join(ROOT, 'supervisor/index.ts')],
|
|
25
|
-
{
|
|
26
|
-
cwd: ROOT,
|
|
27
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
28
|
-
env: { ...process.env }
|
|
29
|
-
}
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
let tunnelUrl: string | undefined;
|
|
33
|
-
let relayUrl: string | undefined;
|
|
34
|
-
let resolved = false;
|
|
35
|
-
let tunnelFired = false;
|
|
36
|
-
let tunnelFailed = false;
|
|
37
|
-
|
|
38
|
-
// Vite warmup tracking
|
|
39
|
-
let viteWarmResolve: (value: void | PromiseLike<void>) => void;
|
|
40
|
-
const viteWarm = new Promise<void>(resolve => {
|
|
41
|
-
viteWarmResolve = resolve as any;
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const doResolve = () => {
|
|
45
|
-
if (resolved) return;
|
|
46
|
-
resolved = true;
|
|
47
|
-
|
|
48
|
-
if (!tunnelFired && onTunnelUp) {
|
|
49
|
-
onTunnelUp();
|
|
50
|
-
}
|
|
51
|
-
if (onReady) {
|
|
52
|
-
onReady();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let config: Partial<BotConfig> = {};
|
|
56
|
-
if (fs.existsSync(CONFIG_PATH)) {
|
|
57
|
-
config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
resolve({
|
|
61
|
-
child,
|
|
62
|
-
tunnelUrl:
|
|
63
|
-
tunnelUrl || `http://localhost:${config.port || 3000}`,
|
|
64
|
-
relayUrl: relayUrl || config.relay?.url || null,
|
|
65
|
-
tunnelFailed,
|
|
66
|
-
viteWarm
|
|
67
|
-
});
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const handleData = (data: any) => {
|
|
71
|
-
const text = data.toString();
|
|
72
|
-
|
|
73
|
-
const tunnelMatch = text.match(/__TUNNEL_URL__=(\S+)/);
|
|
74
|
-
if (tunnelMatch) {
|
|
75
|
-
tunnelUrl = tunnelMatch[1];
|
|
76
|
-
if (!tunnelFired && onTunnelUp) {
|
|
77
|
-
tunnelFired = true;
|
|
78
|
-
onTunnelUp(tunnelUrl);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const relayMatch = text.match(/__RELAY_URL__=(\S+)/);
|
|
83
|
-
if (relayMatch) relayUrl = relayMatch[1];
|
|
84
|
-
|
|
85
|
-
if (text.includes('__VITE_WARM__')) {
|
|
86
|
-
viteWarmResolve();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (text.includes('__READY__')) {
|
|
90
|
-
doResolve();
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (text.includes('__TUNNEL_FAILED__')) {
|
|
95
|
-
tunnelFailed = true;
|
|
96
|
-
doResolve();
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// Safety-net timeout
|
|
101
|
-
setTimeout(doResolve, 45_000);
|
|
102
|
-
|
|
103
|
-
child.stdout?.on('data', handleData);
|
|
104
|
-
child.stderr?.on('data', handleData);
|
|
105
|
-
|
|
106
|
-
process.on('SIGINT', () => child.kill('SIGINT'));
|
|
107
|
-
process.on('SIGTERM', () => child.kill('SIGTERM'));
|
|
108
|
-
});
|
|
109
|
-
}
|
package/cli/core/types.ts
DELETED