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
@@ -8,268 +8,176 @@ import AudioBubble from './AudioBubble';
8
8
  import type { StoredAttachment } from '../../hooks/useChat';
9
9
 
10
10
  interface Props {
11
- role: 'user' | 'assistant';
12
- content: string;
13
- timestamp?: string;
14
- hasAttachments?: boolean;
15
- audioData?: string;
16
- attachments?: StoredAttachment[];
17
- onImageClick?: (images: string[], index: number) => void;
11
+ role: 'user' | 'assistant';
12
+ content: string;
13
+ timestamp?: string;
14
+ hasAttachments?: boolean;
15
+ audioData?: string;
16
+ attachments?: StoredAttachment[];
17
+ onImageClick?: (images: string[], index: number) => void;
18
18
  }
19
19
 
20
20
  function formatTime(iso: string): string {
21
- try {
22
- const d = new Date(iso);
23
- return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
24
- } catch {
25
- return '';
26
- }
21
+ try {
22
+ const d = new Date(iso);
23
+ return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
24
+ } catch {
25
+ return '';
26
+ }
27
27
  }
28
28
 
29
29
  function CopyButton({ text }: { text: string }) {
30
- const [copied, setCopied] = useState(false);
31
- const handleCopy = async () => {
32
- try {
33
- await navigator.clipboard.writeText(text);
34
- setCopied(true);
35
- setTimeout(() => setCopied(false), 1500);
36
- } catch {
37
- /* clipboard not available */
38
- }
39
- };
40
- return (
41
- <button
42
- onClick={handleCopy}
43
- className="shrink-0 opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded hover:bg-muted-foreground/10 text-muted-foreground/50 hover:text-muted-foreground"
44
- title="Copy message"
45
- >
46
- {copied ? (
47
- <Check className="h-3.5 w-3.5" />
48
- ) : (
49
- <Copy className="h-3.5 w-3.5" />
50
- )}
51
- </button>
52
- );
30
+ const [copied, setCopied] = useState(false);
31
+ const handleCopy = async () => {
32
+ try {
33
+ await navigator.clipboard.writeText(text);
34
+ setCopied(true);
35
+ setTimeout(() => setCopied(false), 1500);
36
+ } catch { /* clipboard not available */ }
37
+ };
38
+ return (
39
+ <button
40
+ onClick={handleCopy}
41
+ className="shrink-0 opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded hover:bg-muted-foreground/10 text-muted-foreground/50 hover:text-muted-foreground"
42
+ title="Copy message"
43
+ >
44
+ {copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
45
+ </button>
46
+ );
53
47
  }
54
48
 
55
- export default function MessageBubble({
56
- role,
57
- content,
58
- timestamp,
59
- hasAttachments,
60
- audioData,
61
- attachments,
62
- onImageClick
63
- }: Props) {
64
- const isUser = role === 'user';
65
- const time = timestamp ? formatTime(timestamp) : '';
66
-
67
- // Separate image and document attachments
68
- const imageAtts =
69
- attachments?.filter(a => a.mediaType?.startsWith('image/')) || [];
70
- const docAtts =
71
- attachments?.filter(a => !a.mediaType?.startsWith('image/')) || [];
49
+ export default function MessageBubble({ role, content, timestamp, hasAttachments, audioData, attachments, onImageClick }: Props) {
50
+ const isUser = role === 'user';
51
+ const time = timestamp ? formatTime(timestamp) : '';
72
52
 
73
- // Resolve image URLs
74
- const imageUrls = imageAtts
75
- .filter(a => a.filePath)
76
- .map(a =>
77
- a.filePath.startsWith('data:')
78
- ? a.filePath
79
- : `/api/files/${a.filePath}`
80
- );
53
+ // Separate image and document attachments
54
+ const imageAtts = attachments?.filter((a) => a.mediaType?.startsWith('image/')) || [];
55
+ const docAtts = attachments?.filter((a) => !a.mediaType?.startsWith('image/')) || [];
81
56
 
82
- if (isUser) {
83
- // Voice message: audio-only, no transcript text
84
- if (audioData) {
85
- return (
86
- <div className="flex flex-col items-end gap-0.5">
87
- <div className="group flex items-start gap-1.5 justify-end">
88
- <CopyButton text={content} />
89
- <div className="max-w-[92%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-primary text-primary-foreground">
90
- <AudioBubble audioData={audioData} />
91
- </div>
92
- </div>
93
- {time && (
94
- <span className="text-[10px] text-muted-foreground/50 px-1">
95
- {time}
96
- </span>
97
- )}
98
- </div>
99
- );
100
- }
57
+ // Resolve image URLs
58
+ const imageUrls = imageAtts
59
+ .filter((a) => a.filePath)
60
+ .map((a) =>
61
+ a.filePath.startsWith('data:') ? a.filePath : `/api/files/${a.filePath}`
62
+ );
101
63
 
102
- return (
103
- <div className="flex flex-col items-end gap-0.5">
104
- <div className="group flex items-start gap-1.5 justify-end">
105
- <CopyButton text={content} />
106
- <div
107
- className="max-w-[92%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed whitespace-pre-wrap bg-primary text-primary-foreground break-words overflow-hidden"
108
- style={{ overflowWrap: 'anywhere' }}
109
- >
110
- {/* Image thumbnails */}
111
- {imageUrls.length > 0 && (
112
- <div className="flex gap-1.5 flex-wrap mb-2">
113
- {imageUrls.map((url, i) => (
114
- <img
115
- key={i}
116
- src={url}
117
- alt={imageAtts[i]?.name || 'attachment'}
118
- className="w-28 h-28 rounded-lg object-cover cursor-pointer border border-white/10 hover:opacity-80 transition-opacity"
119
- role="presentation"
120
- tabIndex={0}
121
- onKeyDown={e => {
122
- if (
123
- e.key === 'Enter' ||
124
- e.key === ' '
125
- ) {
126
- onImageClick?.(imageUrls, i);
127
- e.preventDefault();
128
- }
129
- }}
130
- onClick={() =>
131
- onImageClick?.(imageUrls, i)
132
- }
133
- />
134
- ))}
135
- </div>
136
- )}
137
- {/* Document attachments */}
138
- {docAtts.length > 0 && (
139
- <span className="inline-flex items-center gap-1 text-primary-foreground/60 mr-1.5">
140
- <Paperclip className="h-3 w-3" />
141
- {docAtts.length > 1 && (
142
- <span className="text-xs">
143
- {docAtts.length}
144
- </span>
145
- )}
146
- </span>
147
- )}
148
- {/* Fallback paperclip for legacy messages with no parsed attachments */}
149
- {!attachments?.length && hasAttachments && (
150
- <span className="inline-flex items-center gap-1 text-primary-foreground/60 mr-1.5">
151
- <Paperclip className="h-3 w-3" />
152
- </span>
153
- )}
154
- {content}
155
- </div>
156
- </div>
157
- {time && (
158
- <span className="text-[10px] text-muted-foreground/50 px-1">
159
- {time}
160
- </span>
161
- )}
64
+ if (isUser) {
65
+ // Voice message: audio-only, no transcript text
66
+ if (audioData) {
67
+ return (
68
+ <div className="flex flex-col items-end gap-0.5">
69
+ <div className="group flex items-start gap-1.5 justify-end">
70
+ <CopyButton text={content} />
71
+ <div className="max-w-[92%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-primary text-primary-foreground">
72
+ <AudioBubble audioData={audioData} />
162
73
  </div>
163
- );
74
+ </div>
75
+ {time && <span className="text-[10px] text-muted-foreground/50 px-1">{time}</span>}
76
+ </div>
77
+ );
164
78
  }
165
79
 
166
80
  return (
167
- <div className="flex flex-col items-start gap-0.5">
168
- <div className="group flex items-start gap-1.5">
169
- <div
170
- className="min-w-0 rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-muted text-foreground prose prose-sm prose-invert max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 break-words overflow-hidden"
171
- style={{ maxWidth: '92%', overflowWrap: 'anywhere' }}
172
- >
173
- <ReactMarkdown
174
- remarkPlugins={[remarkGfm]}
175
- components={{
176
- pre({ children }) {
177
- return (
178
- <pre className="overflow-x-auto my-2">
179
- {children}
180
- </pre>
181
- );
182
- },
183
- code({ className, children, ...props }) {
184
- const match = /language-(\w+)/.exec(
185
- className || ''
186
- );
187
- const code = String(children).replace(
188
- /\n$/,
189
- ''
190
- );
191
- if (match) {
192
- return (
193
- <SyntaxHighlighter
194
- style={oneDark}
195
- language={match[1]}
196
- PreTag="div"
197
- customStyle={{
198
- margin: '0.5rem 0',
199
- borderRadius: '0.5rem',
200
- fontSize: '0.8rem',
201
- overflowX: 'auto'
202
- }}
203
- >
204
- {code}
205
- </SyntaxHighlighter>
206
- );
207
- }
208
- return (
209
- <code
210
- className="bg-white/10 rounded px-1 py-0.5 text-[0.8rem] break-all"
211
- style={{
212
- whiteSpace: 'pre-wrap',
213
- overflowWrap: 'anywhere'
214
- }}
215
- {...props}
216
- >
217
- {children}
218
- </code>
219
- );
220
- },
221
- p({ children }) {
222
- return <p className="my-1.5">{children}</p>;
223
- },
224
- ul({ children }) {
225
- return (
226
- <ul className="my-1.5 pl-4 list-disc">
227
- {children}
228
- </ul>
229
- );
230
- },
231
- ol({ children }) {
232
- return (
233
- <ol className="my-1.5 pl-4 list-decimal">
234
- {children}
235
- </ol>
236
- );
237
- },
238
- table({ children }) {
239
- return (
240
- <div className="overflow-x-auto my-2">
241
- <table className="border-collapse text-xs">
242
- {children}
243
- </table>
244
- </div>
245
- );
246
- },
247
- th({ children }) {
248
- return (
249
- <th className="border border-white/10 px-2 py-1 text-left bg-white/5">
250
- {children}
251
- </th>
252
- );
253
- },
254
- td({ children }) {
255
- return (
256
- <td className="border border-white/10 px-2 py-1">
257
- {children}
258
- </td>
259
- );
260
- }
261
- }}
262
- >
263
- {content}
264
- </ReactMarkdown>
265
- </div>
266
- <CopyButton text={content} />
267
- </div>
268
- {time && (
269
- <span className="text-[10px] text-muted-foreground/50 px-1">
270
- {time}
271
- </span>
81
+ <div className="flex flex-col items-end gap-0.5">
82
+ <div className="group flex items-start gap-1.5 justify-end">
83
+ <CopyButton text={content} />
84
+ <div className="max-w-[92%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed whitespace-pre-wrap bg-primary text-primary-foreground break-words overflow-hidden" style={{ overflowWrap: 'anywhere' }}>
85
+ {/* Image thumbnails */}
86
+ {imageUrls.length > 0 && (
87
+ <div className="flex gap-1.5 flex-wrap mb-2">
88
+ {imageUrls.map((url, i) => (
89
+ <img
90
+ key={i}
91
+ src={url}
92
+ alt={imageAtts[i]?.name || 'attachment'}
93
+ className="w-28 h-28 rounded-lg object-cover cursor-pointer border border-white/10 hover:opacity-80 transition-opacity"
94
+ onClick={() => onImageClick?.(imageUrls, i)}
95
+ />
96
+ ))}
97
+ </div>
98
+ )}
99
+ {/* Document attachments */}
100
+ {docAtts.length > 0 && (
101
+ <span className="inline-flex items-center gap-1 text-primary-foreground/60 mr-1.5">
102
+ <Paperclip className="h-3 w-3" />
103
+ {docAtts.length > 1 && <span className="text-xs">{docAtts.length}</span>}
104
+ </span>
272
105
  )}
106
+ {/* Fallback paperclip for legacy messages with no parsed attachments */}
107
+ {!attachments?.length && hasAttachments && (
108
+ <span className="inline-flex items-center gap-1 text-primary-foreground/60 mr-1.5">
109
+ <Paperclip className="h-3 w-3" />
110
+ </span>
111
+ )}
112
+ {content}
113
+ </div>
273
114
  </div>
115
+ {time && <span className="text-[10px] text-muted-foreground/50 px-1">{time}</span>}
116
+ </div>
274
117
  );
118
+ }
119
+
120
+ return (
121
+ <div className="flex flex-col items-start gap-0.5">
122
+ <div className="group flex items-start gap-1.5">
123
+ <div className="min-w-0 rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-muted text-foreground prose prose-sm prose-invert max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 break-words overflow-hidden" style={{ maxWidth: '92%', overflowWrap: 'anywhere' }}>
124
+ <ReactMarkdown
125
+ remarkPlugins={[remarkGfm]}
126
+ components={{
127
+ pre({ children }) {
128
+ return <pre className="overflow-x-auto my-2">{children}</pre>;
129
+ },
130
+ code({ className, children, ...props }) {
131
+ const match = /language-(\w+)/.exec(className || '');
132
+ const code = String(children).replace(/\n$/, '');
133
+ if (match) {
134
+ return (
135
+ <SyntaxHighlighter
136
+ style={oneDark}
137
+ language={match[1]}
138
+ PreTag="div"
139
+ customStyle={{ margin: '0.5rem 0', borderRadius: '0.5rem', fontSize: '0.8rem', overflowX: 'auto' }}
140
+ >
141
+ {code}
142
+ </SyntaxHighlighter>
143
+ );
144
+ }
145
+ return (
146
+ <code className="bg-white/10 rounded px-1 py-0.5 text-[0.8rem] break-all" style={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }} {...props}>
147
+ {children}
148
+ </code>
149
+ );
150
+ },
151
+ p({ children }) {
152
+ return <p className="my-1.5">{children}</p>;
153
+ },
154
+ ul({ children }) {
155
+ return <ul className="my-1.5 pl-4 list-disc">{children}</ul>;
156
+ },
157
+ ol({ children }) {
158
+ return <ol className="my-1.5 pl-4 list-decimal">{children}</ol>;
159
+ },
160
+ table({ children }) {
161
+ return (
162
+ <div className="overflow-x-auto my-2">
163
+ <table className="border-collapse text-xs">{children}</table>
164
+ </div>
165
+ );
166
+ },
167
+ th({ children }) {
168
+ return <th className="border border-white/10 px-2 py-1 text-left bg-white/5">{children}</th>;
169
+ },
170
+ td({ children }) {
171
+ return <td className="border border-white/10 px-2 py-1">{children}</td>;
172
+ },
173
+ }}
174
+ >
175
+ {content}
176
+ </ReactMarkdown>
177
+ </div>
178
+ <CopyButton text={content} />
179
+ </div>
180
+ {time && <span className="text-[10px] text-muted-foreground/50 px-1">{time}</span>}
181
+ </div>
182
+ );
275
183
  }