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
@@ -5,146 +5,122 @@ import TypingIndicator from './TypingIndicator';
5
5
  import ImageLightbox from './ImageLightbox';
6
6
 
7
7
  interface Props {
8
- messages: ChatMessage[];
9
- streaming: boolean;
10
- streamBuffer: string;
11
- tools: ToolActivity[];
12
- hasMore?: boolean;
13
- onLoadOlder?: () => void;
8
+ messages: ChatMessage[];
9
+ streaming: boolean;
10
+ streamBuffer: string;
11
+ tools: ToolActivity[];
12
+ hasMore?: boolean;
13
+ onLoadOlder?: () => void;
14
14
  }
15
15
 
16
- export default function MessageList({
17
- messages,
18
- streaming,
19
- streamBuffer,
20
- tools,
21
- hasMore,
22
- onLoadOlder
23
- }: Props) {
24
- const bottomRef = useRef<HTMLDivElement>(null);
25
- const scrollContainerRef = useRef<HTMLDivElement>(null);
26
- const sentinelRef = useRef<HTMLDivElement>(null);
27
- const isInitialLoad = useRef(true);
28
- const [loadingOlder, setLoadingOlder] = useState(false);
29
- const [lightbox, setLightbox] = useState<{
30
- images: string[];
31
- index: number;
32
- } | null>(null);
33
-
34
- const handleImageClick = useCallback((images: string[], index: number) => {
35
- setLightbox({ images, index });
36
- }, []);
37
-
38
- // Scroll to bottom — instant on initial load, smooth afterwards
39
- useEffect(() => {
40
- if (!bottomRef.current) return;
41
- if (isInitialLoad.current && messages.length > 0) {
42
- bottomRef.current.scrollIntoView({
43
- behavior: 'instant' as ScrollBehavior
44
- });
45
- isInitialLoad.current = false;
46
- } else if (!isInitialLoad.current) {
47
- bottomRef.current.scrollIntoView({ behavior: 'smooth' });
16
+ export default function MessageList({ messages, streaming, streamBuffer, tools, hasMore, onLoadOlder }: Props) {
17
+ const bottomRef = useRef<HTMLDivElement>(null);
18
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
19
+ const sentinelRef = useRef<HTMLDivElement>(null);
20
+ const isInitialLoad = useRef(true);
21
+ const [loadingOlder, setLoadingOlder] = useState(false);
22
+ const [lightbox, setLightbox] = useState<{ images: string[]; index: number } | null>(null);
23
+
24
+ const handleImageClick = useCallback((images: string[], index: number) => {
25
+ setLightbox({ images, index });
26
+ }, []);
27
+
28
+ // Scroll to bottom — instant on initial load, smooth afterwards
29
+ useEffect(() => {
30
+ if (!bottomRef.current) return;
31
+ if (isInitialLoad.current && messages.length > 0) {
32
+ bottomRef.current.scrollIntoView({ behavior: 'instant' as ScrollBehavior });
33
+ isInitialLoad.current = false;
34
+ } else if (!isInitialLoad.current) {
35
+ bottomRef.current.scrollIntoView({ behavior: 'smooth' });
36
+ }
37
+ }, [messages, streamBuffer, tools, streaming]);
38
+
39
+ // IntersectionObserver for infinite scroll up
40
+ useEffect(() => {
41
+ if (!sentinelRef.current || !onLoadOlder) return;
42
+ const sentinel = sentinelRef.current;
43
+
44
+ const observer = new IntersectionObserver(
45
+ async (entries) => {
46
+ if (entries[0].isIntersecting && hasMore && !loadingOlder) {
47
+ const container = scrollContainerRef.current;
48
+ if (!container) return;
49
+
50
+ // Save scroll position before loading
51
+ const prevScrollHeight = container.scrollHeight;
52
+
53
+ setLoadingOlder(true);
54
+ await onLoadOlder();
55
+ setLoadingOlder(false);
56
+
57
+ // Restore scroll position after DOM update
58
+ requestAnimationFrame(() => {
59
+ const newScrollHeight = container.scrollHeight;
60
+ container.scrollTop += newScrollHeight - prevScrollHeight;
61
+ });
48
62
  }
49
- }, [messages, streamBuffer, tools, streaming]);
50
-
51
- // IntersectionObserver for infinite scroll up
52
- useEffect(() => {
53
- if (!sentinelRef.current || !onLoadOlder) return;
54
- const sentinel = sentinelRef.current;
55
-
56
- const observer = new IntersectionObserver(
57
- async entries => {
58
- if (entries[0].isIntersecting && hasMore && !loadingOlder) {
59
- const container = scrollContainerRef.current;
60
- if (!container) return;
61
-
62
- // Save scroll position before loading
63
- const prevScrollHeight = container.scrollHeight;
64
-
65
- setLoadingOlder(true);
66
- await onLoadOlder();
67
- setLoadingOlder(false);
68
-
69
- // Restore scroll position after DOM update
70
- requestAnimationFrame(() => {
71
- const newScrollHeight = container.scrollHeight;
72
- container.scrollTop +=
73
- newScrollHeight - prevScrollHeight;
74
- });
75
- }
76
- },
77
- { root: scrollContainerRef.current, threshold: 0.1 }
78
- );
79
-
80
- observer.observe(sentinel);
81
- return () => observer.disconnect();
82
- }, [hasMore, loadingOlder, onLoadOlder]);
83
-
84
- // Get the latest running tool name (only the current one)
85
- const currentTool = tools.length > 0 ? tools[tools.length - 1] : null;
86
- const currentToolName =
87
- currentTool?.status === 'running' ? currentTool.name : undefined;
88
-
89
- return (
90
- <div
91
- ref={scrollContainerRef}
92
- className="flex-1 overflow-y-auto overflow-x-hidden p-4 flex flex-col min-h-0"
93
- >
94
- {/* Spacer pushes messages to the bottom when few */}
95
- <div className="flex-1" />
96
-
97
- {/* Sentinel for infinite scroll + loading spinner */}
98
- {hasMore && (
99
- <div ref={sentinelRef} className="flex justify-center py-2">
100
- {loadingOlder && (
101
- <div className="w-5 h-5 border-2 border-muted-foreground/20 border-t-muted-foreground/60 rounded-full animate-spin" />
102
- )}
103
- </div>
104
- )}
105
-
106
- {messages.length === 0 && !streaming && (
107
- <div className="flex items-center justify-center py-8 text-muted-foreground text-sm">
108
- Start a conversation
109
- </div>
110
- )}
111
-
112
- <div className="space-y-3">
113
- {messages.map(msg => (
114
- <MessageBubble
115
- key={msg.id}
116
- role={msg.role}
117
- content={msg.content}
118
- timestamp={msg.timestamp}
119
- hasAttachments={msg.hasAttachments}
120
- audioData={msg.audioData}
121
- attachments={msg.attachments}
122
- onImageClick={handleImageClick}
123
- />
124
- ))}
125
-
126
- {/* Typing / streaming bubble — shown as soon as agent starts */}
127
- {streaming && (
128
- <TypingIndicator
129
- text={streamBuffer}
130
- toolName={currentToolName}
131
- />
132
- )}
133
- </div>
134
- <div ref={bottomRef} />
135
-
136
- {lightbox && (
137
- <ImageLightbox
138
- images={lightbox.images}
139
- index={lightbox.index}
140
- onClose={() => setLightbox(null)}
141
- onNavigate={i =>
142
- setLightbox(prev =>
143
- prev ? { ...prev, index: i } : null
144
- )
145
- }
146
- />
147
- )}
148
- </div>
63
+ },
64
+ { root: scrollContainerRef.current, threshold: 0.1 },
149
65
  );
66
+
67
+ observer.observe(sentinel);
68
+ return () => observer.disconnect();
69
+ }, [hasMore, loadingOlder, onLoadOlder]);
70
+
71
+ // Get the latest running tool name (only the current one)
72
+ const currentTool = tools.length > 0 ? tools[tools.length - 1] : null;
73
+ const currentToolName = currentTool?.status === 'running' ? currentTool.name : undefined;
74
+
75
+ return (
76
+ <div ref={scrollContainerRef} className="flex-1 overflow-y-auto overflow-x-hidden p-4 flex flex-col min-h-0">
77
+ {/* Spacer pushes messages to the bottom when few */}
78
+ <div className="flex-1" />
79
+
80
+ {/* Sentinel for infinite scroll + loading spinner */}
81
+ {hasMore && (
82
+ <div ref={sentinelRef} className="flex justify-center py-2">
83
+ {loadingOlder && (
84
+ <div className="w-5 h-5 border-2 border-muted-foreground/20 border-t-muted-foreground/60 rounded-full animate-spin" />
85
+ )}
86
+ </div>
87
+ )}
88
+
89
+ {messages.length === 0 && !streaming && (
90
+ <div className="flex items-center justify-center py-8 text-muted-foreground text-sm">
91
+ Start a conversation
92
+ </div>
93
+ )}
94
+
95
+ <div className="space-y-3">
96
+ {messages.map((msg) => (
97
+ <MessageBubble
98
+ key={msg.id}
99
+ role={msg.role}
100
+ content={msg.content}
101
+ timestamp={msg.timestamp}
102
+ hasAttachments={msg.hasAttachments}
103
+ audioData={msg.audioData}
104
+ attachments={msg.attachments}
105
+ onImageClick={handleImageClick}
106
+ />
107
+ ))}
108
+
109
+ {/* Typing / streaming bubble — shown as soon as agent starts */}
110
+ {streaming && (
111
+ <TypingIndicator text={streamBuffer} toolName={currentToolName} />
112
+ )}
113
+ </div>
114
+ <div ref={bottomRef} />
115
+
116
+ {lightbox && (
117
+ <ImageLightbox
118
+ images={lightbox.images}
119
+ index={lightbox.index}
120
+ onClose={() => setLightbox(null)}
121
+ onNavigate={(i) => setLightbox((prev) => prev ? { ...prev, index: i } : null)}
122
+ />
123
+ )}
124
+ </div>
125
+ );
150
126
  }
@@ -1,59 +1,47 @@
1
1
  interface Props {
2
- text: string;
3
- toolName?: string;
2
+ text: string;
3
+ toolName?: string;
4
4
  }
5
5
 
6
6
  /** Friendly label for SDK tool names */
7
7
  function toolLabel(name: string): string {
8
- const labels: Record<string, string> = {
9
- Read: 'Reading file',
10
- Write: 'Writing file',
11
- Edit: 'Editing file',
12
- Bash: 'Running command',
13
- Grep: 'Searching code',
14
- Glob: 'Finding files',
15
- Task: 'Running task',
16
- WebFetch: 'Fetching URL',
17
- WebSearch: 'Searching web'
18
- };
19
- return labels[name] || `Using ${name}`;
8
+ const labels: Record<string, string> = {
9
+ Read: 'Reading file',
10
+ Write: 'Writing file',
11
+ Edit: 'Editing file',
12
+ Bash: 'Running command',
13
+ Grep: 'Searching code',
14
+ Glob: 'Finding files',
15
+ Task: 'Running task',
16
+ WebFetch: 'Fetching URL',
17
+ WebSearch: 'Searching web',
18
+ };
19
+ return labels[name] || `Using ${name}`;
20
20
  }
21
21
 
22
22
  export default function TypingIndicator({ text, toolName }: Props) {
23
- return (
24
- <div className="flex justify-start">
25
- <div
26
- className="max-w-[75%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-muted text-foreground break-words overflow-hidden"
27
- style={{ overflowWrap: 'anywhere' }}
28
- >
29
- {text ? (
30
- <>
31
- <span className="whitespace-pre-wrap">{text}</span>
32
- <span className="inline-block w-2 h-4 ml-0.5 bg-muted-foreground animate-pulse" />
33
- </>
34
- ) : (
35
- <span className="inline-flex items-center gap-1 h-5">
36
- <span
37
- className="w-1.5 h-1.5 rounded-full bg-muted-foreground/60 animate-bounce"
38
- style={{ animationDelay: '0ms' }}
39
- />
40
- <span
41
- className="w-1.5 h-1.5 rounded-full bg-muted-foreground/60 animate-bounce"
42
- style={{ animationDelay: '150ms' }}
43
- />
44
- <span
45
- className="w-1.5 h-1.5 rounded-full bg-muted-foreground/60 animate-bounce"
46
- style={{ animationDelay: '300ms' }}
47
- />
48
- </span>
49
- )}
50
- {toolName && (
51
- <div className="flex items-center gap-1.5 mt-1 text-xs text-muted-foreground">
52
- <div className="w-2.5 h-2.5 border-[1.5px] border-muted-foreground/30 border-t-primary rounded-full animate-spin" />
53
- <span>{toolLabel(toolName)}...</span>
54
- </div>
55
- )}
56
- </div>
57
- </div>
58
- );
23
+ return (
24
+ <div className="flex justify-start">
25
+ <div className="max-w-[75%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed bg-muted text-foreground break-words overflow-hidden" style={{ overflowWrap: 'anywhere' }}>
26
+ {text ? (
27
+ <>
28
+ <span className="whitespace-pre-wrap">{text}</span>
29
+ <span className="inline-block w-2 h-4 ml-0.5 bg-muted-foreground animate-pulse" />
30
+ </>
31
+ ) : (
32
+ <span className="inline-flex items-center gap-1 h-5">
33
+ <span className="w-1.5 h-1.5 rounded-full bg-muted-foreground/60 animate-bounce" style={{ animationDelay: '0ms' }} />
34
+ <span className="w-1.5 h-1.5 rounded-full bg-muted-foreground/60 animate-bounce" style={{ animationDelay: '150ms' }} />
35
+ <span className="w-1.5 h-1.5 rounded-full bg-muted-foreground/60 animate-bounce" style={{ animationDelay: '300ms' }} />
36
+ </span>
37
+ )}
38
+ {toolName && (
39
+ <div className="flex items-center gap-1.5 mt-1 text-xs text-muted-foreground">
40
+ <div className="w-2.5 h-2.5 border-[1.5px] border-muted-foreground/30 border-t-primary rounded-full animate-spin" />
41
+ <span>{toolLabel(toolName)}...</span>
42
+ </div>
43
+ )}
44
+ </div>
45
+ </div>
46
+ );
59
47
  }