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.
- package/README.md +193 -0
- package/bin/otherwise.js +5 -0
- package/frontend/404.html +84 -0
- package/frontend/assets/OpenDyslexic3-Bold-CDyRs55Y.ttf +0 -0
- package/frontend/assets/OpenDyslexic3-Regular-CIBXa4WE.ttf +0 -0
- package/frontend/assets/__vite-browser-external-BIHI7g3E.js +1 -0
- package/frontend/assets/conversational-worker-CeKiciGk.js +2929 -0
- package/frontend/assets/dictation-worker-D0aYfq8b.js +29 -0
- package/frontend/assets/gemini-color-CgSQmmva.png +0 -0
- package/frontend/assets/index-BLux5ps4.js +21 -0
- package/frontend/assets/index-Blh8_TEM.js +5272 -0
- package/frontend/assets/index-BpQ1PuKu.js +18 -0
- package/frontend/assets/index-Df737c8w.css +1 -0
- package/frontend/assets/index-xaYHL6wb.js +113 -0
- package/frontend/assets/ort-wasm-simd-threaded.asyncify-BynIiDiv.wasm +0 -0
- package/frontend/assets/ort-wasm-simd-threaded.jsep-B0T3yYHD.wasm +0 -0
- package/frontend/assets/transformers-tULNc5V3.js +31 -0
- package/frontend/assets/tts-worker-DPJWqT7N.js +2899 -0
- package/frontend/assets/voice-mode-worker-GzvIE_uh.js +2927 -0
- package/frontend/assets/worker-2d5ABSLU.js +31 -0
- package/frontend/banner.png +0 -0
- package/frontend/favicon.svg +3 -0
- package/frontend/google55e5ec47ee14a5f8.html +1 -0
- package/frontend/index.html +234 -0
- package/frontend/manifest.json +17 -0
- package/frontend/pdf.worker.min.mjs +21 -0
- package/frontend/robots.txt +5 -0
- package/frontend/sitemap.xml +27 -0
- package/package.json +81 -0
- package/src/agent/index.js +1066 -0
- package/src/agent/location.js +51 -0
- package/src/agent/prompt.js +548 -0
- package/src/agent/tools.js +4372 -0
- package/src/browser/detect.js +68 -0
- package/src/browser/session.js +1109 -0
- package/src/config.js +137 -0
- package/src/email/client.js +503 -0
- package/src/index.js +557 -0
- package/src/inference/anthropic.js +113 -0
- package/src/inference/google.js +373 -0
- package/src/inference/index.js +81 -0
- package/src/inference/ollama.js +383 -0
- package/src/inference/openai.js +140 -0
- package/src/inference/openrouter.js +378 -0
- package/src/inference/xai.js +200 -0
- package/src/logBridge.js +9 -0
- package/src/models.js +146 -0
- package/src/remote/client.js +225 -0
- package/src/scheduler/cron.js +243 -0
- package/src/server.js +3876 -0
- package/src/storage/db.js +1135 -0
- package/src/storage/supabase.js +364 -0
- package/src/tunnel/cloudflare.js +241 -0
- package/src/ui/components/App.jsx +687 -0
- package/src/ui/components/BrowserSelect.jsx +111 -0
- package/src/ui/components/FilePicker.jsx +472 -0
- package/src/ui/components/Header.jsx +444 -0
- package/src/ui/components/HelpPanel.jsx +173 -0
- package/src/ui/components/HistoryPanel.jsx +158 -0
- package/src/ui/components/MessageList.jsx +235 -0
- package/src/ui/components/ModelSelector.jsx +304 -0
- package/src/ui/components/PromptInput.jsx +515 -0
- package/src/ui/components/StreamingResponse.jsx +134 -0
- package/src/ui/components/ThinkingIndicator.jsx +365 -0
- package/src/ui/components/ToolExecution.jsx +714 -0
- package/src/ui/components/index.js +82 -0
- package/src/ui/context/TerminalContext.jsx +150 -0
- package/src/ui/context/index.js +13 -0
- package/src/ui/hooks/index.js +16 -0
- package/src/ui/hooks/useChatState.js +675 -0
- package/src/ui/hooks/useCommands.js +280 -0
- package/src/ui/hooks/useFileAttachments.js +216 -0
- package/src/ui/hooks/useKeyboardShortcuts.js +173 -0
- package/src/ui/hooks/useNotifications.js +185 -0
- package/src/ui/hooks/useTerminalSize.js +151 -0
- package/src/ui/hooks/useWebSocket.js +273 -0
- package/src/ui/index.js +94 -0
- package/src/ui/ink-runner.js +22 -0
- package/src/ui/utils/formatters.js +424 -0
- package/src/ui/utils/index.js +6 -0
- 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;
|