otherwise-cli 0.1.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 (81) hide show
  1. package/README.md +193 -0
  2. package/bin/otherwise.js +5 -0
  3. package/frontend/404.html +84 -0
  4. package/frontend/assets/OpenDyslexic3-Bold-CDyRs55Y.ttf +0 -0
  5. package/frontend/assets/OpenDyslexic3-Regular-CIBXa4WE.ttf +0 -0
  6. package/frontend/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  7. package/frontend/assets/conversational-worker-CeKiciGk.js +2929 -0
  8. package/frontend/assets/dictation-worker-D0aYfq8b.js +29 -0
  9. package/frontend/assets/gemini-color-CgSQmmva.png +0 -0
  10. package/frontend/assets/index-BLux5ps4.js +21 -0
  11. package/frontend/assets/index-Blh8_TEM.js +5272 -0
  12. package/frontend/assets/index-BpQ1PuKu.js +18 -0
  13. package/frontend/assets/index-Df737c8w.css +1 -0
  14. package/frontend/assets/index-xaYHL6wb.js +113 -0
  15. package/frontend/assets/ort-wasm-simd-threaded.asyncify-BynIiDiv.wasm +0 -0
  16. package/frontend/assets/ort-wasm-simd-threaded.jsep-B0T3yYHD.wasm +0 -0
  17. package/frontend/assets/transformers-tULNc5V3.js +31 -0
  18. package/frontend/assets/tts-worker-DPJWqT7N.js +2899 -0
  19. package/frontend/assets/voice-mode-worker-GzvIE_uh.js +2927 -0
  20. package/frontend/assets/worker-2d5ABSLU.js +31 -0
  21. package/frontend/banner.png +0 -0
  22. package/frontend/favicon.svg +3 -0
  23. package/frontend/google55e5ec47ee14a5f8.html +1 -0
  24. package/frontend/index.html +234 -0
  25. package/frontend/manifest.json +17 -0
  26. package/frontend/pdf.worker.min.mjs +21 -0
  27. package/frontend/robots.txt +5 -0
  28. package/frontend/sitemap.xml +27 -0
  29. package/package.json +81 -0
  30. package/src/agent/index.js +1066 -0
  31. package/src/agent/location.js +51 -0
  32. package/src/agent/prompt.js +548 -0
  33. package/src/agent/tools.js +4372 -0
  34. package/src/browser/detect.js +68 -0
  35. package/src/browser/session.js +1109 -0
  36. package/src/config.js +137 -0
  37. package/src/email/client.js +503 -0
  38. package/src/index.js +557 -0
  39. package/src/inference/anthropic.js +113 -0
  40. package/src/inference/google.js +373 -0
  41. package/src/inference/index.js +81 -0
  42. package/src/inference/ollama.js +383 -0
  43. package/src/inference/openai.js +140 -0
  44. package/src/inference/openrouter.js +378 -0
  45. package/src/inference/xai.js +200 -0
  46. package/src/logBridge.js +9 -0
  47. package/src/models.js +146 -0
  48. package/src/remote/client.js +225 -0
  49. package/src/scheduler/cron.js +243 -0
  50. package/src/server.js +3876 -0
  51. package/src/storage/db.js +1135 -0
  52. package/src/storage/supabase.js +364 -0
  53. package/src/tunnel/cloudflare.js +241 -0
  54. package/src/ui/components/App.jsx +687 -0
  55. package/src/ui/components/BrowserSelect.jsx +111 -0
  56. package/src/ui/components/FilePicker.jsx +472 -0
  57. package/src/ui/components/Header.jsx +444 -0
  58. package/src/ui/components/HelpPanel.jsx +173 -0
  59. package/src/ui/components/HistoryPanel.jsx +158 -0
  60. package/src/ui/components/MessageList.jsx +235 -0
  61. package/src/ui/components/ModelSelector.jsx +304 -0
  62. package/src/ui/components/PromptInput.jsx +515 -0
  63. package/src/ui/components/StreamingResponse.jsx +134 -0
  64. package/src/ui/components/ThinkingIndicator.jsx +365 -0
  65. package/src/ui/components/ToolExecution.jsx +714 -0
  66. package/src/ui/components/index.js +82 -0
  67. package/src/ui/context/TerminalContext.jsx +150 -0
  68. package/src/ui/context/index.js +13 -0
  69. package/src/ui/hooks/index.js +16 -0
  70. package/src/ui/hooks/useChatState.js +675 -0
  71. package/src/ui/hooks/useCommands.js +280 -0
  72. package/src/ui/hooks/useFileAttachments.js +216 -0
  73. package/src/ui/hooks/useKeyboardShortcuts.js +173 -0
  74. package/src/ui/hooks/useNotifications.js +185 -0
  75. package/src/ui/hooks/useTerminalSize.js +151 -0
  76. package/src/ui/hooks/useWebSocket.js +273 -0
  77. package/src/ui/index.js +94 -0
  78. package/src/ui/ink-runner.js +22 -0
  79. package/src/ui/utils/formatters.js +424 -0
  80. package/src/ui/utils/index.js +6 -0
  81. package/src/ui/utils/markdown.js +166 -0
@@ -0,0 +1,365 @@
1
+ /**
2
+ * ThinkingIndicator component
3
+ * Displays an animated thinking/reasoning indicator with elapsed time and char count
4
+ * Features gradient cycling colors and smooth animations
5
+ */
6
+
7
+ import React, { useState, useEffect, useMemo } from 'react';
8
+ import { Box, Text } from 'ink';
9
+ import { formatElapsed, formatCharCount } from '../utils/formatters.js';
10
+
11
+ /**
12
+ * Gradient color palette for shimmer effect
13
+ */
14
+ const GRADIENT_COLORS = [
15
+ '#a855f7', // Purple 500
16
+ '#c084fc', // Purple 400
17
+ '#d8b4fe', // Purple 300
18
+ '#e9d5ff', // Purple 200
19
+ '#d8b4fe', // Purple 300
20
+ '#c084fc', // Purple 400
21
+ ];
22
+
23
+ /**
24
+ * Braille spinner frames for smooth animation
25
+ */
26
+ const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
27
+
28
+ /**
29
+ * Dots spinner frames
30
+ */
31
+ const DOTS_FRAMES = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
32
+
33
+ /**
34
+ * Bouncing bar frames
35
+ */
36
+ const BOUNCE_FRAMES = ['▐', '▐', '▐', '▌', '▌', '▌'];
37
+
38
+ /**
39
+ * Custom animated spinner with gradient colors
40
+ */
41
+ function GradientSpinner({ type = 'braille', speed = 80 }) {
42
+ const [frame, setFrame] = useState(0);
43
+ const [colorIndex, setColorIndex] = useState(0);
44
+
45
+ const frames = useMemo(() => {
46
+ switch (type) {
47
+ case 'dots': return DOTS_FRAMES;
48
+ case 'bounce': return BOUNCE_FRAMES;
49
+ default: return SPINNER_FRAMES;
50
+ }
51
+ }, [type]);
52
+
53
+ useEffect(() => {
54
+ const spinnerTimer = setInterval(() => {
55
+ setFrame(f => (f + 1) % frames.length);
56
+ }, speed);
57
+
58
+ const colorTimer = setInterval(() => {
59
+ setColorIndex(c => (c + 1) % GRADIENT_COLORS.length);
60
+ }, 150);
61
+
62
+ return () => {
63
+ clearInterval(spinnerTimer);
64
+ clearInterval(colorTimer);
65
+ };
66
+ }, [frames.length, speed]);
67
+
68
+ return (
69
+ <Text color={GRADIENT_COLORS[colorIndex]} bold>
70
+ {frames[frame]}
71
+ </Text>
72
+ );
73
+ }
74
+
75
+ /**
76
+ * Pulsing dots animation
77
+ */
78
+ function PulsingDots({ count = 3, speed = 300 }) {
79
+ const [activeIndex, setActiveIndex] = useState(0);
80
+
81
+ useEffect(() => {
82
+ const timer = setInterval(() => {
83
+ setActiveIndex(i => (i + 1) % (count + 1));
84
+ }, speed);
85
+ return () => clearInterval(timer);
86
+ }, [count, speed]);
87
+
88
+ return (
89
+ <Text>
90
+ {Array.from({ length: count }).map((_, i) => (
91
+ <Text key={i} color={i < activeIndex ? '#a855f7' : '#4b5563'}>●</Text>
92
+ ))}
93
+ </Text>
94
+ );
95
+ }
96
+
97
+ /**
98
+ * Shimmer text effect - cycles gradient through text
99
+ */
100
+ function ShimmerText({ text, speed = 100 }) {
101
+ const [offset, setOffset] = useState(0);
102
+
103
+ useEffect(() => {
104
+ const timer = setInterval(() => {
105
+ setOffset(o => (o + 1) % GRADIENT_COLORS.length);
106
+ }, speed);
107
+ return () => clearInterval(timer);
108
+ }, [speed]);
109
+
110
+ return (
111
+ <Text>
112
+ {text.split('').map((char, i) => (
113
+ <Text
114
+ key={i}
115
+ color={GRADIENT_COLORS[(i + offset) % GRADIENT_COLORS.length]}
116
+ >
117
+ {char}
118
+ </Text>
119
+ ))}
120
+ </Text>
121
+ );
122
+ }
123
+
124
+ /**
125
+ * ThinkingIndicator component
126
+ * @param {object} props - Component props
127
+ * @param {number} props.charCount - Character count of thinking content
128
+ * @param {number} props.startTime - Start timestamp
129
+ * @param {string} props.label - Custom label (default: "Reasoning")
130
+ * @param {string} props.suffix - Optional suffix text (e.g., "(web)")
131
+ * @param {boolean} props.shimmer - Enable shimmer effect on label
132
+ * @param {string} props.spinnerType - Spinner type: 'braille', 'dots', 'bounce'
133
+ */
134
+ export function ThinkingIndicator({
135
+ charCount = 0,
136
+ startTime,
137
+ label = 'Reasoning',
138
+ suffix = '',
139
+ shimmer = true,
140
+ spinnerType = 'braille',
141
+ }) {
142
+ const [elapsed, setElapsed] = useState(0);
143
+ const [colorIndex, setColorIndex] = useState(0);
144
+
145
+ // Update elapsed time every 100ms
146
+ useEffect(() => {
147
+ if (!startTime) return;
148
+
149
+ const timer = setInterval(() => {
150
+ setElapsed(Date.now() - startTime);
151
+ }, 100);
152
+
153
+ return () => clearInterval(timer);
154
+ }, [startTime]);
155
+
156
+ // Gradient color cycling for the label
157
+ useEffect(() => {
158
+ if (!shimmer) return;
159
+
160
+ const timer = setInterval(() => {
161
+ setColorIndex(c => (c + 1) % GRADIENT_COLORS.length);
162
+ }, 200);
163
+
164
+ return () => clearInterval(timer);
165
+ }, [shimmer]);
166
+
167
+ const elapsedStr = formatElapsed(elapsed);
168
+ const charStr = charCount > 0 ? formatCharCount(charCount) : '';
169
+ const labelColor = shimmer ? GRADIENT_COLORS[colorIndex] : '#a855f7';
170
+
171
+ return (
172
+ <Box>
173
+ <GradientSpinner type={spinnerType} />
174
+ <Text color={labelColor}> {label}</Text>
175
+ {charStr && (
176
+ <Text dimColor> · {charStr}</Text>
177
+ )}
178
+ {elapsedStr && (
179
+ <Text dimColor> ({elapsedStr})</Text>
180
+ )}
181
+ {suffix && (
182
+ <Text dimColor> {suffix}</Text>
183
+ )}
184
+ </Box>
185
+ );
186
+ }
187
+
188
+ /**
189
+ * Loading indicator with customizable style
190
+ */
191
+ export function LoadingIndicator({
192
+ text = 'Loading',
193
+ type = 'spinner', // 'spinner', 'dots', 'bar', 'shimmer'
194
+ color = '#a855f7',
195
+ }) {
196
+ if (type === 'shimmer') {
197
+ return (
198
+ <Box>
199
+ <ShimmerText text={text} />
200
+ </Box>
201
+ );
202
+ }
203
+
204
+ if (type === 'dots') {
205
+ return (
206
+ <Box>
207
+ <Text color={color}>{text}</Text>
208
+ <Text> </Text>
209
+ <PulsingDots />
210
+ </Box>
211
+ );
212
+ }
213
+
214
+ return (
215
+ <Box>
216
+ <GradientSpinner type={type === 'bar' ? 'bounce' : 'braille'} />
217
+ <Text color={color}> {text}</Text>
218
+ </Box>
219
+ );
220
+ }
221
+
222
+ /**
223
+ * CompletedThinking component
224
+ * Shows a summary of completed thinking with fade-in effect
225
+ */
226
+ export function CompletedThinking({ charCount, elapsed, animate = true }) {
227
+ const [opacity, setOpacity] = useState(animate ? 0 : 1);
228
+ const charStr = charCount > 0 ? formatCharCount(charCount) : '';
229
+ const elapsedStr = formatElapsed(elapsed);
230
+
231
+ // Fade in effect
232
+ useEffect(() => {
233
+ if (!animate) return;
234
+
235
+ const steps = [0.3, 0.6, 1];
236
+ let step = 0;
237
+
238
+ const timer = setInterval(() => {
239
+ if (step < steps.length) {
240
+ setOpacity(steps[step]);
241
+ step++;
242
+ } else {
243
+ clearInterval(timer);
244
+ }
245
+ }, 100);
246
+
247
+ return () => clearInterval(timer);
248
+ }, [animate]);
249
+
250
+ const color = opacity < 1 ? '#6b7280' : '#a855f7';
251
+
252
+ return (
253
+ <Box>
254
+ <Text color={color}>💭</Text>
255
+ <Text color={color === '#a855f7' ? undefined : '#6b7280'} dimColor={opacity >= 1}>
256
+ {' '}Reasoned
257
+ </Text>
258
+ {charStr && (
259
+ <Text dimColor> · {charStr}</Text>
260
+ )}
261
+ {elapsedStr && (
262
+ <Text dimColor> · {elapsedStr}</Text>
263
+ )}
264
+ </Box>
265
+ );
266
+ }
267
+
268
+ /**
269
+ * Success indicator with checkmark animation
270
+ */
271
+ export function SuccessIndicator({ text = 'Done', animate = true }) {
272
+ const [frame, setFrame] = useState(animate ? 0 : 2);
273
+ const frames = ['○', '◐', '✓'];
274
+ const colors = ['#f59e0b', '#22c55e', '#22c55e'];
275
+
276
+ useEffect(() => {
277
+ if (!animate || frame >= 2) return;
278
+
279
+ const timer = setTimeout(() => {
280
+ setFrame(f => f + 1);
281
+ }, 150);
282
+
283
+ return () => clearTimeout(timer);
284
+ }, [animate, frame]);
285
+
286
+ return (
287
+ <Box>
288
+ <Text color={colors[frame]} bold>{frames[frame]}</Text>
289
+ <Text color="#22c55e"> {text}</Text>
290
+ </Box>
291
+ );
292
+ }
293
+
294
+ /**
295
+ * Error indicator with shake effect (visual via color pulse)
296
+ */
297
+ export function ErrorIndicator({ text = 'Error', details = '' }) {
298
+ const [pulse, setPulse] = useState(true);
299
+
300
+ useEffect(() => {
301
+ const timer = setInterval(() => {
302
+ setPulse(p => !p);
303
+ }, 500);
304
+
305
+ // Stop pulsing after 3 seconds
306
+ const stopTimer = setTimeout(() => {
307
+ clearInterval(timer);
308
+ setPulse(true);
309
+ }, 3000);
310
+
311
+ return () => {
312
+ clearInterval(timer);
313
+ clearTimeout(stopTimer);
314
+ };
315
+ }, []);
316
+
317
+ return (
318
+ <Box flexDirection="column">
319
+ <Box>
320
+ <Text color={pulse ? '#ef4444' : '#dc2626'} bold>✗</Text>
321
+ <Text color="#ef4444"> {text}</Text>
322
+ </Box>
323
+ {details && (
324
+ <Box marginLeft={2}>
325
+ <Text dimColor>{details}</Text>
326
+ </Box>
327
+ )}
328
+ </Box>
329
+ );
330
+ }
331
+
332
+ /**
333
+ * Warning indicator
334
+ */
335
+ export function WarningIndicator({ text, details = '' }) {
336
+ return (
337
+ <Box flexDirection="column">
338
+ <Box>
339
+ <Text color="#f59e0b" bold>⚠</Text>
340
+ <Text color="#f59e0b"> {text}</Text>
341
+ </Box>
342
+ {details && (
343
+ <Box marginLeft={2}>
344
+ <Text dimColor>{details}</Text>
345
+ </Box>
346
+ )}
347
+ </Box>
348
+ );
349
+ }
350
+
351
+ /**
352
+ * Info indicator
353
+ */
354
+ export function InfoIndicator({ text }) {
355
+ return (
356
+ <Box>
357
+ <Text color="#3b82f6" bold>ℹ</Text>
358
+ <Text color="#3b82f6"> {text}</Text>
359
+ </Box>
360
+ );
361
+ }
362
+
363
+ // Export all components
364
+ export { GradientSpinner, PulsingDots, ShimmerText };
365
+ export default ThinkingIndicator;