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.
Files changed (106) hide show
  1. package/README.md +53 -84
  2. package/bin/cli.js +1717 -6
  3. package/components.json +18 -18
  4. package/package.json +98 -109
  5. package/postcss.config.js +3 -3
  6. package/scripts/install.ps1 +1 -1
  7. package/scripts/install.sh +1 -1
  8. package/scripts/postinstall.js +47 -58
  9. package/shared/ai.ts +118 -202
  10. package/shared/config.ts +37 -39
  11. package/shared/logger.ts +6 -14
  12. package/shared/paths.ts +10 -16
  13. package/shared/relay.ts +89 -98
  14. package/supervisor/backend.ts +77 -89
  15. package/supervisor/chat/ARCHITECTURE.md +13 -14
  16. package/supervisor/chat/OnboardWizard.tsx +2194 -3419
  17. package/supervisor/chat/fluxy-main.tsx +495 -633
  18. package/supervisor/chat/fluxy.html +24 -36
  19. package/supervisor/chat/onboard-main.tsx +8 -8
  20. package/supervisor/chat/onboard.html +10 -13
  21. package/supervisor/chat/src/components/Chat/AudioBubble.tsx +94 -108
  22. package/supervisor/chat/src/components/Chat/ChatView.tsx +14 -36
  23. package/supervisor/chat/src/components/Chat/ImageLightbox.tsx +71 -89
  24. package/supervisor/chat/src/components/Chat/InputBar.tsx +498 -728
  25. package/supervisor/chat/src/components/Chat/MessageBubble.tsx +154 -246
  26. package/supervisor/chat/src/components/Chat/MessageList.tsx +114 -138
  27. package/supervisor/chat/src/components/Chat/TypingIndicator.tsx +38 -50
  28. package/supervisor/chat/src/components/LoginScreen.tsx +245 -296
  29. package/supervisor/chat/src/hooks/useChat.ts +210 -259
  30. package/supervisor/chat/src/hooks/useFluxyChat.ts +232 -322
  31. package/supervisor/chat/src/hooks/useSpeechRecognition.ts +126 -148
  32. package/supervisor/chat/src/lib/auth.ts +20 -23
  33. package/supervisor/chat/src/lib/ws-client.ts +113 -125
  34. package/supervisor/chat/src/styles/globals.css +76 -85
  35. package/supervisor/file-saver.ts +39 -52
  36. package/supervisor/fluxy-agent.ts +213 -292
  37. package/supervisor/index.ts +895 -1263
  38. package/supervisor/scheduler.ts +237 -299
  39. package/supervisor/tunnel.ts +113 -167
  40. package/supervisor/vite-dev.ts +51 -61
  41. package/supervisor/widget.js +135 -147
  42. package/supervisor/worker.ts +45 -49
  43. package/tsconfig.json +18 -24
  44. package/vite.config.ts +48 -55
  45. package/vite.fluxy.config.ts +34 -34
  46. package/worker/claude-auth.ts +202 -265
  47. package/worker/codex-auth.ts +144 -181
  48. package/worker/db.ts +63 -191
  49. package/worker/index.ts +601 -842
  50. package/workspace/MYSELF.md +1 -1
  51. package/workspace/PULSE.json +6 -6
  52. package/workspace/backend/index.ts +16 -19
  53. package/workspace/client/index.html +47 -60
  54. package/workspace/client/public/manifest.json +21 -21
  55. package/workspace/client/public/sw.js +29 -31
  56. package/workspace/client/src/App.tsx +91 -136
  57. package/workspace/client/src/components/Dashboard/DashboardPage.tsx +68 -72
  58. package/workspace/client/src/components/ErrorBoundary.tsx +11 -11
  59. package/workspace/client/src/components/Layout/DashboardLayout.tsx +35 -35
  60. package/workspace/client/src/components/Layout/Footer.tsx +12 -12
  61. package/workspace/client/src/components/Layout/MobileNav.tsx +23 -23
  62. package/workspace/client/src/components/Layout/Sidebar.tsx +81 -81
  63. package/workspace/client/src/components/ui/avatar.tsx +85 -85
  64. package/workspace/client/src/components/ui/badge.tsx +40 -41
  65. package/workspace/client/src/components/ui/button.tsx +56 -57
  66. package/workspace/client/src/components/ui/card.tsx +75 -75
  67. package/workspace/client/src/components/ui/dialog.tsx +114 -117
  68. package/workspace/client/src/components/ui/dropdown-menu.tsx +191 -199
  69. package/workspace/client/src/components/ui/input.tsx +17 -17
  70. package/workspace/client/src/components/ui/scroll-area.tsx +47 -47
  71. package/workspace/client/src/components/ui/select.tsx +145 -151
  72. package/workspace/client/src/components/ui/separator.tsx +21 -21
  73. package/workspace/client/src/components/ui/sheet.tsx +102 -102
  74. package/workspace/client/src/components/ui/skeleton.tsx +10 -10
  75. package/workspace/client/src/components/ui/switch.tsx +26 -26
  76. package/workspace/client/src/components/ui/tabs.tsx +69 -69
  77. package/workspace/client/src/components/ui/textarea.tsx +14 -14
  78. package/workspace/client/src/components/ui/tooltip.tsx +37 -37
  79. package/workspace/client/src/lib/utils.ts +1 -1
  80. package/workspace/client/src/main.tsx +3 -3
  81. package/workspace/client/src/styles/globals.css +76 -85
  82. package/workspace/skills/code-reviewer/.claude-plugin/plugin.json +3 -3
  83. package/workspace/skills/code-reviewer/skills/code-reviewer/SKILL.md +0 -6
  84. package/workspace/skills/daily-standup/.claude-plugin/plugin.json +3 -3
  85. package/workspace/skills/daily-standup/skills/daily-standup/SKILL.md +0 -7
  86. package/workspace/skills/workspace-helper/.claude-plugin/plugin.json +3 -3
  87. package/workspace/skills/workspace-helper/skills/workspace-helper/SKILL.md +0 -2
  88. package/bin/cli.backup.js +0 -2138
  89. package/cli/commands/daemon.ts +0 -42
  90. package/cli/commands/init.ts +0 -32
  91. package/cli/commands/start.ts +0 -113
  92. package/cli/commands/tunnel.temp.ts +0 -206
  93. package/cli/commands/tunnel.ts +0 -227
  94. package/cli/commands/update.ts +0 -163
  95. package/cli/core/base-adapter.ts +0 -156
  96. package/cli/core/cloudflared.ts +0 -113
  97. package/cli/core/config.ts +0 -73
  98. package/cli/core/os-detector.ts +0 -43
  99. package/cli/core/server.ts +0 -109
  100. package/cli/core/types.ts +0 -15
  101. package/cli/index.ts +0 -72
  102. package/cli/platforms/darwin.ts +0 -146
  103. package/cli/platforms/index.ts +0 -21
  104. package/cli/platforms/linux.ts +0 -156
  105. package/cli/platforms/win32.ts +0 -34
  106. package/cli/utils/ui.ts +0 -37
@@ -14,12 +14,12 @@ import os from 'os';
14
14
  import { log } from '../shared/logger.js';
15
15
 
16
16
  const OAUTH_CONFIG = {
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
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
- if (callbackServer) {
36
- try {
37
- callbackServer.close();
38
- } catch {}
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
- if (!fs.existsSync(AUTH_DIR)) {
45
- fs.mkdirSync(AUTH_DIR, { recursive: true });
46
- }
47
-
48
- const credentials: Record<string, any> = {
49
- access_token: tokens.access_token,
50
- token_type: tokens.token_type || 'Bearer'
51
- };
52
-
53
- if (tokens.refresh_token) credentials.refresh_token = tokens.refresh_token;
54
- if (tokens.id_token) credentials.id_token = tokens.id_token;
55
- if (tokens.expires_in) {
56
- credentials.expires_at = Date.now() + (tokens.expires_in - 300) * 1000;
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
- // Decode JWT claims for account info
60
- try {
61
- const payload = JSON.parse(
62
- Buffer.from(
63
- tokens.access_token.split('.')[1],
64
- 'base64url'
65
- ).toString()
66
- );
67
- const authClaims = payload['https://api.openai.com/auth'] || {};
68
- if (authClaims.chatgpt_account_id)
69
- credentials.chatgpt_account_id = authClaims.chatgpt_account_id;
70
- if (authClaims.chatgpt_plan_type)
71
- credentials.chatgpt_plan_type = authClaims.chatgpt_plan_type;
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
- fs.writeFileSync(AUTH_FILE, JSON.stringify(credentials, null, 2), 'utf-8');
77
- try {
78
- fs.chmodSync(AUTH_FILE, 0o600);
79
- } catch {}
80
- log.ok('Codex credentials stored');
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
- async function exchangeCode(
84
- code: string
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
- try {
94
- const payload = new URLSearchParams({
95
- grant_type: 'authorization_code',
96
- client_id: OAUTH_CONFIG.CLIENT_ID,
97
- code,
98
- redirect_uri: OAUTH_CONFIG.REDIRECT_URI,
99
- code_verifier: codeVerifier
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
- success: boolean;
129
- authUrl?: string;
130
- error?: string;
131
- }> {
132
- return new Promise(resolve => {
133
- stopCallbackServer();
134
-
135
- // Generate PKCE
136
- codeVerifier = crypto.randomBytes(32).toString('base64url');
137
- const codeChallenge = crypto
138
- .createHash('sha256')
139
- .update(codeVerifier)
140
- .digest('base64url');
141
- oauthState = crypto.randomUUID();
142
-
143
- // Start local callback server
144
- callbackServer = http.createServer((req, res) => {
145
- if (!req.url?.startsWith('/auth/callback')) {
146
- res.writeHead(404);
147
- res.end();
148
- return;
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
- stopCallbackServer();
166
-
167
- if (error || !code || returnedState !== oauthState) return;
168
- exchangeCode(code);
169
- });
170
-
171
- callbackServer.listen(OAUTH_CONFIG.PORT, '127.0.0.1', () => {
172
- log.ok(`Codex OAuth callback server on port ${OAUTH_CONFIG.PORT}`);
173
-
174
- const params = new URLSearchParams({
175
- response_type: 'code',
176
- client_id: OAUTH_CONFIG.CLIENT_ID,
177
- redirect_uri: OAUTH_CONFIG.REDIRECT_URI,
178
- scope: OAUTH_CONFIG.SCOPES,
179
- code_challenge: codeChallenge,
180
- code_challenge_method: 'S256',
181
- state: oauthState!,
182
- id_token_add_organizations: 'true',
183
- codex_cli_simplified_flow: 'true'
184
- });
185
-
186
- resolve({
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
- stopCallbackServer();
204
- codeVerifier = null;
205
- oauthState = null;
170
+ stopCallbackServer();
171
+ codeVerifier = null;
172
+ oauthState = null;
206
173
  }
207
174
 
208
- export function getCodexAuthStatus(): {
209
- authenticated: boolean;
210
- plan?: string;
211
- error?: string;
212
- } {
213
- try {
214
- if (!fs.existsSync(AUTH_FILE)) return { authenticated: false };
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
- try {
228
- if (!fs.existsSync(AUTH_FILE)) return null;
229
- const creds = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf-8'));
230
- if (!creds.access_token) return null;
231
- if (creds.expires_at && Date.now() >= creds.expires_at) return null;
232
- return creds.access_token;
233
- } catch {
234
- return null;
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
  }