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/worker/codex-auth.ts
CHANGED
|
@@ -14,12 +14,12 @@ import os from 'os';
|
|
|
14
14
|
import { log } from '../shared/logger.js';
|
|
15
15
|
|
|
16
16
|
const OAUTH_CONFIG = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
AUTHORIZE_URL: 'https://auth.openai.com/oauth/authorize',
|
|
18
|
+
TOKEN_URL: 'https://auth.openai.com/oauth/token',
|
|
19
|
+
REDIRECT_URI: 'http://localhost:1455/auth/callback',
|
|
20
|
+
CLIENT_ID: 'app_EMoamEEZ73f0CkXaXp7hrann',
|
|
21
|
+
SCOPES: 'openid profile email offline_access',
|
|
22
|
+
PORT: 1455,
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
const AUTH_DIR = path.join(os.homedir(), '.codex');
|
|
@@ -32,205 +32,168 @@ let callbackServer: http.Server | null = null;
|
|
|
32
32
|
/* ── Helpers ── */
|
|
33
33
|
|
|
34
34
|
function stopCallbackServer(): void {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
callbackServer = null;
|
|
40
|
-
}
|
|
35
|
+
if (callbackServer) {
|
|
36
|
+
try { callbackServer.close(); } catch {}
|
|
37
|
+
callbackServer = null;
|
|
38
|
+
}
|
|
41
39
|
}
|
|
42
40
|
|
|
43
41
|
function storeCredentials(tokens: any): void {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
42
|
+
if (!fs.existsSync(AUTH_DIR)) {
|
|
43
|
+
fs.mkdirSync(AUTH_DIR, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const credentials: Record<string, any> = {
|
|
47
|
+
access_token: tokens.access_token,
|
|
48
|
+
token_type: tokens.token_type || 'Bearer',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (tokens.refresh_token) credentials.refresh_token = tokens.refresh_token;
|
|
52
|
+
if (tokens.id_token) credentials.id_token = tokens.id_token;
|
|
53
|
+
if (tokens.expires_in) {
|
|
54
|
+
credentials.expires_at = Date.now() + (tokens.expires_in - 300) * 1000;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Decode JWT claims for account info
|
|
58
|
+
try {
|
|
59
|
+
const payload = JSON.parse(Buffer.from(tokens.access_token.split('.')[1], 'base64url').toString());
|
|
60
|
+
const authClaims = payload['https://api.openai.com/auth'] || {};
|
|
61
|
+
if (authClaims.chatgpt_account_id) credentials.chatgpt_account_id = authClaims.chatgpt_account_id;
|
|
62
|
+
if (authClaims.chatgpt_plan_type) credentials.chatgpt_plan_type = authClaims.chatgpt_plan_type;
|
|
63
|
+
} catch {
|
|
64
|
+
log.warn('Codex: failed to decode JWT claims');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fs.writeFileSync(AUTH_FILE, JSON.stringify(credentials, null, 2), 'utf-8');
|
|
68
|
+
try { fs.chmodSync(AUTH_FILE, 0o600); } catch {}
|
|
69
|
+
log.ok('Codex credentials stored');
|
|
70
|
+
}
|
|
58
71
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
} catch {
|
|
73
|
-
log.warn('Codex: failed to decode JWT claims');
|
|
74
|
-
}
|
|
72
|
+
async function exchangeCode(code: string): Promise<{ success: boolean; error?: string }> {
|
|
73
|
+
if (!codeVerifier) {
|
|
74
|
+
return { success: false, error: 'No code verifier — OAuth flow was not started.' };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const payload = new URLSearchParams({
|
|
79
|
+
grant_type: 'authorization_code',
|
|
80
|
+
client_id: OAUTH_CONFIG.CLIENT_ID,
|
|
81
|
+
code,
|
|
82
|
+
redirect_uri: OAUTH_CONFIG.REDIRECT_URI,
|
|
83
|
+
code_verifier: codeVerifier,
|
|
84
|
+
});
|
|
75
85
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
86
|
+
const response = await fetch(OAUTH_CONFIG.TOKEN_URL, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
89
|
+
body: payload.toString(),
|
|
90
|
+
});
|
|
82
91
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
): Promise<{ success: boolean; error?: string }> {
|
|
86
|
-
if (!codeVerifier) {
|
|
87
|
-
return {
|
|
88
|
-
success: false,
|
|
89
|
-
error: 'No code verifier — OAuth flow was not started.'
|
|
90
|
-
};
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
return { success: false, error: `Authentication failed (${response.status}). Please try again.` };
|
|
91
94
|
}
|
|
92
95
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const response = await fetch(OAUTH_CONFIG.TOKEN_URL, {
|
|
103
|
-
method: 'POST',
|
|
104
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
105
|
-
body: payload.toString()
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
if (!response.ok) {
|
|
109
|
-
return {
|
|
110
|
-
success: false,
|
|
111
|
-
error: `Authentication failed (${response.status}). Please try again.`
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const tokens = await response.json();
|
|
116
|
-
storeCredentials(tokens);
|
|
117
|
-
codeVerifier = null;
|
|
118
|
-
oauthState = null;
|
|
119
|
-
return { success: true };
|
|
120
|
-
} catch (err: any) {
|
|
121
|
-
return { success: false, error: err.message };
|
|
122
|
-
}
|
|
96
|
+
const tokens = await response.json();
|
|
97
|
+
storeCredentials(tokens);
|
|
98
|
+
codeVerifier = null;
|
|
99
|
+
oauthState = null;
|
|
100
|
+
return { success: true };
|
|
101
|
+
} catch (err: any) {
|
|
102
|
+
return { success: false, error: err.message };
|
|
103
|
+
}
|
|
123
104
|
}
|
|
124
105
|
|
|
125
106
|
/* ── Public API ── */
|
|
126
107
|
|
|
127
|
-
export function startCodexOAuth(): Promise<{
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const url = new URL(
|
|
152
|
-
req.url,
|
|
153
|
-
`http://localhost:${OAUTH_CONFIG.PORT}`
|
|
154
|
-
);
|
|
155
|
-
const code = url.searchParams.get('code');
|
|
156
|
-
const returnedState = url.searchParams.get('state');
|
|
157
|
-
const error = url.searchParams.get('error');
|
|
158
|
-
|
|
159
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
160
|
-
res.end(`<html><body style="font-family:sans-serif;text-align:center;padding:60px;background:#0a0a0a;color:#fff">
|
|
108
|
+
export function startCodexOAuth(): Promise<{ success: boolean; authUrl?: string; error?: string }> {
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
stopCallbackServer();
|
|
111
|
+
|
|
112
|
+
// Generate PKCE
|
|
113
|
+
codeVerifier = crypto.randomBytes(32).toString('base64url');
|
|
114
|
+
const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
|
|
115
|
+
oauthState = crypto.randomUUID();
|
|
116
|
+
|
|
117
|
+
// Start local callback server
|
|
118
|
+
callbackServer = http.createServer((req, res) => {
|
|
119
|
+
if (!req.url?.startsWith('/auth/callback')) {
|
|
120
|
+
res.writeHead(404);
|
|
121
|
+
res.end();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const url = new URL(req.url, `http://localhost:${OAUTH_CONFIG.PORT}`);
|
|
126
|
+
const code = url.searchParams.get('code');
|
|
127
|
+
const returnedState = url.searchParams.get('state');
|
|
128
|
+
const error = url.searchParams.get('error');
|
|
129
|
+
|
|
130
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
131
|
+
res.end(`<html><body style="font-family:sans-serif;text-align:center;padding:60px;background:#0a0a0a;color:#fff">
|
|
161
132
|
<h2>${error ? 'Authentication Failed' : 'Authenticated!'}</h2>
|
|
162
133
|
<p style="color:#888">${error || 'You can close this tab and return to Fluxy.'}</p>
|
|
163
134
|
</body></html>`);
|
|
164
135
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
success: true,
|
|
188
|
-
authUrl: `${OAUTH_CONFIG.AUTHORIZE_URL}?${params.toString()}`
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
callbackServer.on('error', (err: any) => {
|
|
193
|
-
const error =
|
|
194
|
-
err.code === 'EADDRINUSE'
|
|
195
|
-
? `Port ${OAUTH_CONFIG.PORT} is busy. Close other Codex instances.`
|
|
196
|
-
: err.message;
|
|
197
|
-
resolve({ success: false, error });
|
|
198
|
-
});
|
|
136
|
+
stopCallbackServer();
|
|
137
|
+
|
|
138
|
+
if (error || !code || returnedState !== oauthState) return;
|
|
139
|
+
exchangeCode(code);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
callbackServer.listen(OAUTH_CONFIG.PORT, '127.0.0.1', () => {
|
|
143
|
+
log.ok(`Codex OAuth callback server on port ${OAUTH_CONFIG.PORT}`);
|
|
144
|
+
|
|
145
|
+
const params = new URLSearchParams({
|
|
146
|
+
response_type: 'code',
|
|
147
|
+
client_id: OAUTH_CONFIG.CLIENT_ID,
|
|
148
|
+
redirect_uri: OAUTH_CONFIG.REDIRECT_URI,
|
|
149
|
+
scope: OAUTH_CONFIG.SCOPES,
|
|
150
|
+
code_challenge: codeChallenge,
|
|
151
|
+
code_challenge_method: 'S256',
|
|
152
|
+
state: oauthState!,
|
|
153
|
+
id_token_add_organizations: 'true',
|
|
154
|
+
codex_cli_simplified_flow: 'true',
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
resolve({ success: true, authUrl: `${OAUTH_CONFIG.AUTHORIZE_URL}?${params.toString()}` });
|
|
199
158
|
});
|
|
159
|
+
|
|
160
|
+
callbackServer.on('error', (err: any) => {
|
|
161
|
+
const error = err.code === 'EADDRINUSE'
|
|
162
|
+
? `Port ${OAUTH_CONFIG.PORT} is busy. Close other Codex instances.`
|
|
163
|
+
: err.message;
|
|
164
|
+
resolve({ success: false, error });
|
|
165
|
+
});
|
|
166
|
+
});
|
|
200
167
|
}
|
|
201
168
|
|
|
202
169
|
export function cancelCodexOAuth(): void {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
170
|
+
stopCallbackServer();
|
|
171
|
+
codeVerifier = null;
|
|
172
|
+
oauthState = null;
|
|
206
173
|
}
|
|
207
174
|
|
|
208
|
-
export function getCodexAuthStatus(): {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const creds = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf-8'));
|
|
216
|
-
if (!creds.access_token) return { authenticated: false };
|
|
217
|
-
if (creds.expires_at && Date.now() >= creds.expires_at) {
|
|
218
|
-
return { authenticated: false, error: 'Token expired' };
|
|
219
|
-
}
|
|
220
|
-
return { authenticated: true, plan: creds.chatgpt_plan_type || 'plus' };
|
|
221
|
-
} catch (err: any) {
|
|
222
|
-
return { authenticated: false, error: err.message };
|
|
175
|
+
export function getCodexAuthStatus(): { authenticated: boolean; plan?: string; error?: string } {
|
|
176
|
+
try {
|
|
177
|
+
if (!fs.existsSync(AUTH_FILE)) return { authenticated: false };
|
|
178
|
+
const creds = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf-8'));
|
|
179
|
+
if (!creds.access_token) return { authenticated: false };
|
|
180
|
+
if (creds.expires_at && Date.now() >= creds.expires_at) {
|
|
181
|
+
return { authenticated: false, error: 'Token expired' };
|
|
223
182
|
}
|
|
183
|
+
return { authenticated: true, plan: creds.chatgpt_plan_type || 'plus' };
|
|
184
|
+
} catch (err: any) {
|
|
185
|
+
return { authenticated: false, error: err.message };
|
|
186
|
+
}
|
|
224
187
|
}
|
|
225
188
|
|
|
226
189
|
export function readCodexAccessToken(): string | null {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
190
|
+
try {
|
|
191
|
+
if (!fs.existsSync(AUTH_FILE)) return null;
|
|
192
|
+
const creds = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf-8'));
|
|
193
|
+
if (!creds.access_token) return null;
|
|
194
|
+
if (creds.expires_at && Date.now() >= creds.expires_at) return null;
|
|
195
|
+
return creds.access_token;
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
236
199
|
}
|