corex-cli 1.0.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/install.sh ADDED
@@ -0,0 +1,26 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo "---------------------------------------"
5
+ echo " Installing COREX AI Terminal Chat"
6
+ echo "---------------------------------------"
7
+
8
+ if ! command -v node &> /dev/null; then
9
+ echo "ERROR: Node.js is required. Install from https://nodejs.org"
10
+ exit 1
11
+ fi
12
+
13
+ NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
14
+ if [ "$NODE_VERSION" -lt 18 ]; then
15
+ echo "ERROR: Node.js 18 or higher is required. Current: $(node -v)"
16
+ exit 1
17
+ fi
18
+
19
+ echo "OK Node.js $(node -v) detected"
20
+ npm install -g corex-ai
21
+
22
+ echo ""
23
+ echo "---------------------------------------"
24
+ echo " COREX installed successfully."
25
+ echo " Type 'corex' to launch."
26
+ echo "---------------------------------------"
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "corex-cli",
3
+ "version": "1.0.0",
4
+ "description": "COREX CLI — powerful AI terminal",
5
+ "type": "module",
6
+ "bin": {
7
+ "corex": "./bin/corex.js"
8
+ },
9
+ "scripts": {
10
+ "dev": "node --experimental-strip-types --enable-source-maps -e \"process.stdin.setRawMode = () => {}; require('tsx').tsc();\" 2>/dev/null || tsx --inspect=0 src/index.ts",
11
+ "start": "node bin/corex.js",
12
+ "build": "tsup src/index.ts --out-dir dist --format esm --external @anthropic-ai/sdk --external @google/generative-ai --external openai"
13
+ },
14
+ "dependencies": {
15
+ "@anthropic-ai/sdk": "latest",
16
+ "@google/generative-ai": "latest",
17
+ "conf": "^12.0.0",
18
+ "dotenv": "^16.3.1",
19
+ "glob": "^10.0.0",
20
+ "gradient-string": "^2.0.2",
21
+ "ink": "^4.4.1",
22
+ "ink-text-input": "^5.0.1",
23
+ "openai": "latest",
24
+ "pdf-parse": "^1.1.1",
25
+ "react": "^18.2.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^20.10.0",
29
+ "@types/react": "^18.2.0",
30
+ "tsx": "^4.6.0",
31
+ "tsup": "^8.0.0",
32
+ "typescript": "^5.3.0"
33
+ }
34
+ }
package/src/app.tsx ADDED
@@ -0,0 +1,217 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { Box, Text, useApp, useInput } from 'ink';
3
+ import { CorexConfig, Message } from './types.js';
4
+ import { addMessage } from './lib/history.js';
5
+ import ChatHistory from './components/ChatHistory.js';
6
+ import InputBar from './components/InputBar.js';
7
+ import TopBar from './components/TopBar.js';
8
+ import StatusArea from './components/StatusArea.js';
9
+ import BootScreen from './components/BootScreen.js';
10
+ import ApiKeyScreen from './components/ApiKeyScreen.js';
11
+ import { theme } from './themes/themes.js';
12
+ import { detectProvider } from './core/providers/index.js';
13
+ import { chatAnthropic, chatOpenAI, chatGemini, ProviderConfig } from './core/providers/index.js';
14
+ import { NetworkError, parseApiError } from './core/network/request.js';
15
+
16
+ interface AppProps {
17
+ config: CorexConfig;
18
+ }
19
+
20
+ type Screen = 'boot' | 'apiKey' | 'chat';
21
+
22
+ const App: React.FC<AppProps> = ({ config: initialConfig }: AppProps) => {
23
+ const { exit } = useApp();
24
+ const [screen, setScreen] = useState<Screen>('boot');
25
+ const [config, setConfig] = useState<CorexConfig>(initialConfig);
26
+ const [messages, setMessages] = useState<Message[]>([]);
27
+ const [inputValue, setInputValue] = useState('');
28
+ const [isThinking, setIsThinking] = useState(false);
29
+ const [streamingText, setStreamingText] = useState('');
30
+ const [status, setStatus] = useState<'idle' | 'typing' | 'thinking'>('idle');
31
+ const [providerName, setProviderName] = useState<string>('Not Connected');
32
+
33
+ useInput((input, key) => {
34
+ if (screen !== 'chat') return;
35
+
36
+ if (key.ctrl && input === 'c') {
37
+ process.stdout.write('\x1b[?25h');
38
+ exit();
39
+ process.exit(0);
40
+ }
41
+
42
+ if (key.ctrl && input === 'l') {
43
+ setMessages([]);
44
+ }
45
+
46
+ if (key.ctrl && input === 'k') {
47
+ setMessages([]);
48
+ setInputValue('');
49
+ }
50
+
51
+ if (key.tab) {
52
+ // Future command mode placeholder
53
+ }
54
+ });
55
+
56
+ const handleBootComplete = useCallback(() => {
57
+ setScreen('apiKey');
58
+ }, []);
59
+
60
+ const handleApiKeySubmit = useCallback(async (apiKey: string) => {
61
+ try {
62
+ const provider = await detectProvider(apiKey);
63
+ setProviderName(provider);
64
+ setConfig({ ...config, apiKey, provider: provider as any });
65
+ setScreen('chat');
66
+ } catch (error: any) {
67
+ setMessages([{
68
+ role: 'assistant',
69
+ content: `Error: ${error.message}`
70
+ }]);
71
+ setScreen('chat');
72
+ }
73
+ }, [config]);
74
+
75
+ const addSystemMessage = useCallback((text: string) => {
76
+ setMessages((prev: Message[]) => [...prev, { role: 'assistant', content: text }]);
77
+ }, []);
78
+
79
+ const handleSubmit = useCallback(
80
+ async (value: string) => {
81
+ const trimmed = value.trim();
82
+ if (!trimmed || isThinking) return;
83
+
84
+ setInputValue('');
85
+ setStatus('typing');
86
+
87
+ const userMsg: Message = { role: 'user', content: trimmed };
88
+ setMessages((prev: Message[]) => [...prev, userMsg]);
89
+ addMessage('user', trimmed);
90
+
91
+ setStatus('thinking');
92
+ setIsThinking(true);
93
+ setStreamingText('');
94
+
95
+ try {
96
+ const providerConfig: ProviderConfig = {
97
+ apiKey: config.apiKey,
98
+ model: config.model,
99
+ systemPrompt: config.systemPrompt,
100
+ temperature: config.temperature,
101
+ maxTokens: config.maxTokens,
102
+ };
103
+
104
+ let response: { content: string; usage?: any };
105
+
106
+ switch (config.provider) {
107
+ case 'anthropic':
108
+ response = await chatAnthropic(
109
+ messages,
110
+ trimmed,
111
+ providerConfig,
112
+ (token) => setStreamingText(prev => prev + token)
113
+ );
114
+ break;
115
+ case 'gemini':
116
+ response = await chatGemini(
117
+ messages,
118
+ trimmed,
119
+ providerConfig,
120
+ (token) => setStreamingText(prev => prev + token)
121
+ );
122
+ break;
123
+ case 'openai':
124
+ case 'openrouter':
125
+ case 'deepseek':
126
+ const baseURL = config.provider === 'openrouter'
127
+ ? 'https://openrouter.ai/api/v1'
128
+ : config.provider === 'deepseek'
129
+ ? 'https://api.deepseek.com'
130
+ : 'https://api.openai.com/v1';
131
+ response = await chatOpenAI(
132
+ messages,
133
+ trimmed,
134
+ providerConfig,
135
+ (token) => setStreamingText(prev => prev + token),
136
+ baseURL
137
+ );
138
+ break;
139
+ default:
140
+ throw new Error(`Unknown provider: ${config.provider}`);
141
+ }
142
+
143
+ const assistantMsg: Message = {
144
+ role: 'assistant',
145
+ content: response.content
146
+ };
147
+ setMessages((prev: Message[]) => [...prev, assistantMsg]);
148
+ addMessage('assistant', response.content);
149
+ setStreamingText('');
150
+
151
+ } catch (error: any) {
152
+ let errorMessage: string;
153
+
154
+ if (error instanceof NetworkError) {
155
+ errorMessage = error.message;
156
+ } else if (error.status === 401) {
157
+ errorMessage = 'Invalid API key. Run /config to update.';
158
+ } else if (error.status === 429) {
159
+ errorMessage = 'Rate limit exceeded. Please wait and try again.';
160
+ } else if (error.status >= 500) {
161
+ errorMessage = 'Provider service unavailable.';
162
+ } else {
163
+ errorMessage = parseApiError(error, 'An unexpected error occurred.');
164
+ }
165
+
166
+ addSystemMessage(errorMessage);
167
+ setStreamingText('');
168
+ } finally {
169
+ setIsThinking(false);
170
+ setStatus('idle');
171
+ }
172
+ },
173
+ [config, messages, isThinking, addMessage, addSystemMessage]
174
+ );
175
+
176
+ const handleInputChange = useCallback(
177
+ (value: string) => {
178
+ setInputValue(value);
179
+ if (value.length > 0 && status === 'idle') {
180
+ setStatus('typing');
181
+ } else if (value.length === 0) {
182
+ setStatus('idle');
183
+ }
184
+ },
185
+ [status]
186
+ );
187
+
188
+ if (screen === 'boot') {
189
+ return <BootScreen onComplete={handleBootComplete} />;
190
+ }
191
+
192
+ if (screen === 'apiKey') {
193
+ return <ApiKeyScreen onSubmit={handleApiKeySubmit} />;
194
+ }
195
+
196
+ return (
197
+ <Box flexDirection="column" height="100%">
198
+ <TopBar provider={providerName} />
199
+ <Box flexDirection="row" justifyContent="flex-end" paddingX={1}>
200
+ <StatusArea status={status} />
201
+ </Box>
202
+ <ChatHistory
203
+ messages={messages}
204
+ isThinking={isThinking}
205
+ streamingText={streamingText}
206
+ />
207
+ <InputBar
208
+ value={inputValue}
209
+ onChange={handleInputChange}
210
+ onSubmit={handleSubmit}
211
+ isDisabled={isThinking}
212
+ />
213
+ </Box>
214
+ );
215
+ };
216
+
217
+ export default App;
@@ -0,0 +1,65 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import TextInput from 'ink-text-input';
4
+ import { useInput } from 'ink';
5
+ import { theme } from '../themes/themes.js';
6
+
7
+ interface ApiKeyScreenProps {
8
+ onSubmit: (apiKey: string) => void;
9
+ }
10
+
11
+ const ApiKeyScreen: React.FC<ApiKeyScreenProps> = ({ onSubmit }: ApiKeyScreenProps) => {
12
+ const [apiKey, setApiKey] = useState('');
13
+ const [showKey, setShowKey] = useState(false);
14
+
15
+ useInput((input, key) => {
16
+ if (key.ctrl && input === 'v') {
17
+ setShowKey((prev) => !prev);
18
+ }
19
+ });
20
+
21
+ const handleSubmit = useCallback(() => {
22
+ if (apiKey.trim()) {
23
+ onSubmit(apiKey.trim());
24
+ }
25
+ }, [apiKey, onSubmit]);
26
+
27
+ const displayValue = showKey ? apiKey : apiKey.replace(/./g, '•');
28
+
29
+ return (
30
+ <Box
31
+ flexDirection="column"
32
+ justifyContent="center"
33
+ alignItems="center"
34
+ height="100%"
35
+ >
36
+ <Box flexDirection="column" alignItems="center">
37
+ <Text bold color={theme.primary}>COREX</Text>
38
+ </Box>
39
+ <Box marginTop={1} flexDirection="column" alignItems="center">
40
+ <Text color={theme.textDim}>AI GATEWAY</Text>
41
+ </Box>
42
+ <Box marginTop={6} flexDirection="column" alignItems="center">
43
+ <Text color={theme.textDim}>Enter an API key</Text>
44
+ </Box>
45
+ <Box marginTop={2} flexDirection="row" alignItems="center">
46
+ <Text color={theme.primary}>{'>'} </Text>
47
+ <TextInput
48
+ value={displayValue}
49
+ onChange={setApiKey}
50
+ onSubmit={handleSubmit}
51
+ placeholder=""
52
+ mask={!showKey ? '•' : undefined}
53
+ focus={true}
54
+ />
55
+ </Box>
56
+ <Box marginTop={3} flexDirection="column" alignItems="center">
57
+ <Text color={theme.textDim} dimColor>
58
+ Ctrl+V to toggle visibility • Enter to continue
59
+ </Text>
60
+ </Box>
61
+ </Box>
62
+ );
63
+ };
64
+
65
+ export default ApiKeyScreen;
@@ -0,0 +1,62 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { theme } from '../themes/themes.js';
4
+
5
+ interface BootScreenProps {
6
+ onComplete: () => void;
7
+ }
8
+
9
+ const LOGO = `
10
+ ██████╗██╗ ██╗ ██████╗ ██████╗ ██████╗ ████████╗███████╗ ██████╗ ██╗ ██╗ ██████╗
11
+ ██╔════╝██║ ██║ ██╔══██╗██╔═══██╗██╔══██╗╚══██╔══╝██╔════╝██╔═══██╗██║ ██║██╔═══██╗
12
+ ██║ ██║ ██║ ██████╔╝██║ ██║██████╔╝ ██║ █████╗ ██║ ██║██║ ██║██║ ██║
13
+ ██║ ██║ ██║ ██╔═══╝ ██║ ██║██╔══██╗ ██║ ██╔══╝ ██║ ██║██║ ██║██║ ██║
14
+ ╚██████╗███████╗██║ ██║ ╚██████╔╝██║ ██║ ██║ ██║ ╚██████╔╝███████╗██║╚██████╔╝
15
+ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═════╝
16
+ `;
17
+
18
+ const BootScreen: React.FC<BootScreenProps> = ({ onComplete }: BootScreenProps) => {
19
+ const [fadeIn, setFadeIn] = useState(0);
20
+
21
+ useEffect(() => {
22
+ const fadeInterval = setInterval(() => {
23
+ setFadeIn((prev) => {
24
+ if (prev >= 1) {
25
+ clearInterval(fadeInterval);
26
+ setTimeout(onComplete, 1500);
27
+ return 1;
28
+ }
29
+ return prev + 0.1;
30
+ });
31
+ }, 150);
32
+
33
+ return () => clearInterval(fadeInterval);
34
+ }, [onComplete]);
35
+
36
+ const opacity = Math.floor(fadeIn * 255)
37
+ .toString(16)
38
+ .padStart(2, '0');
39
+
40
+ return (
41
+ <Box
42
+ flexDirection="column"
43
+ justifyContent="center"
44
+ alignItems="center"
45
+ height="100%"
46
+ >
47
+ <Box flexDirection="column" alignItems="center">
48
+ <Text color={`#${opacity}93C5FD` as any}>{LOGO}</Text>
49
+ </Box>
50
+ <Box marginTop={1} flexDirection="column" alignItems="center">
51
+ <Text bold color={`#${opacity}3B82F6` as any}>
52
+ AI GATEWAY
53
+ </Text>
54
+ </Box>
55
+ <Box marginTop={3} flexDirection="column" alignItems="center">
56
+ <Text color={`#${opacity}6B7280` as any}>Universal AI Provider Gateway</Text>
57
+ </Box>
58
+ </Box>
59
+ );
60
+ };
61
+
62
+ export default BootScreen;
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { Message } from '../types.js';
4
+ import { theme } from '../themes/themes.js';
5
+ import ThinkingDots from './ThinkingDots.js';
6
+
7
+ interface ChatHistoryProps {
8
+ messages: Message[];
9
+ isThinking: boolean;
10
+ streamingText: string;
11
+ }
12
+
13
+ const ChatHistory: React.FC<ChatHistoryProps> = ({
14
+ messages,
15
+ isThinking,
16
+ streamingText,
17
+ }: ChatHistoryProps) => {
18
+ return (
19
+ <Box flexDirection="column" flexGrow={1} paddingX={1}>
20
+ {messages.map((msg: Message, i: number) => (
21
+ <Box key={i} flexDirection="column" marginBottom={1}>
22
+ {msg.role === 'user' ? (
23
+ <Text color={theme.textDim}>{`> ${msg.content}`}</Text>
24
+ ) : (
25
+ <Text color={theme.highlight}>{msg.content}</Text>
26
+ )}
27
+ </Box>
28
+ ))}
29
+
30
+ {streamingText && (
31
+ <Box flexDirection="column" marginBottom={1}>
32
+ <Text color={theme.highlight}>{streamingText}</Text>
33
+ </Box>
34
+ )}
35
+
36
+ {isThinking && !streamingText && (
37
+ <Box marginBottom={1}>
38
+ <ThinkingDots />
39
+ </Box>
40
+ )}
41
+ </Box>
42
+ );
43
+ };
44
+
45
+ export default ChatHistory;
@@ -0,0 +1,60 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import gradient from 'gradient-string';
6
+ import { Theme } from '../types.js';
7
+
8
+ interface HeaderProps {
9
+ theme: Theme;
10
+ }
11
+
12
+ const Header: React.FC<HeaderProps> = ({ theme }) => {
13
+ const [logoLines, setLogoLines] = useState<string[]>([]);
14
+
15
+ useEffect(() => {
16
+ try {
17
+ // Try multiple paths for logo.txt
18
+ const possiblePaths = [
19
+ path.join(__dirname, '..', 'assets', 'logo.txt'),
20
+ path.join(__dirname, '..', '..', 'assets', 'logo.txt'),
21
+ path.join(process.cwd(), 'assets', 'logo.txt'),
22
+ ];
23
+
24
+ let logoText = '';
25
+ for (const p of possiblePaths) {
26
+ if (fs.existsSync(p)) {
27
+ logoText = fs.readFileSync(p, 'utf-8');
28
+ break;
29
+ }
30
+ }
31
+
32
+ if (!logoText) {
33
+ // Fallback inline logo
34
+ logoText = `
35
+ ██████╗ ██████╗ ██████╗ ███████╗██╗ ██╗
36
+ ██╔════╝██╔═══██╗██╔══██╗██╔════╝╚██╗██╔╝
37
+ ██║ ██║ ██║██████╔╝█████╗ ╚███╔╝
38
+ ██║ ██║ ██║██╔══██╗██╔══╝ ██╔██╗
39
+ ╚██████╗╚██████╔╝██║ ██║███████╗██╔╝ ██╗
40
+ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝`;
41
+ }
42
+
43
+ const grad = gradient(theme.headerGradient);
44
+ const lines = logoText.split('\n').map((line) => grad(line));
45
+ setLogoLines(lines);
46
+ } catch {
47
+ setLogoLines([' COREX']);
48
+ }
49
+ }, [theme]);
50
+
51
+ return (
52
+ <Box flexDirection="column" marginBottom={1}>
53
+ {logoLines.map((line, i) => (
54
+ <Text key={i}>{line}</Text>
55
+ ))}
56
+ </Box>
57
+ );
58
+ };
59
+
60
+ export default Header;
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import TextInput from 'ink-text-input';
4
+ import { theme } from '../themes/themes.js';
5
+
6
+ interface InputBarProps {
7
+ value: string;
8
+ onChange: (value: string) => void;
9
+ onSubmit: (value: string) => void;
10
+ isDisabled: boolean;
11
+ }
12
+
13
+ const InputBar: React.FC<InputBarProps> = ({
14
+ value,
15
+ onChange,
16
+ onSubmit,
17
+ isDisabled,
18
+ }: InputBarProps) => {
19
+ const separator = '─'.repeat(process.stdout.columns || 80);
20
+
21
+ return (
22
+ <Box flexDirection="column">
23
+ <Box>
24
+ <Text color={theme.border}>{separator}</Text>
25
+ </Box>
26
+ <Box paddingX={1}>
27
+ <Text bold color={theme.primary}>
28
+ {'> '}
29
+ </Text>
30
+ <TextInput
31
+ value={value}
32
+ onChange={onChange}
33
+ onSubmit={onSubmit}
34
+ placeholder={isDisabled ? '' : ''}
35
+ focus={!isDisabled}
36
+ showCursor={true}
37
+ />
38
+ </Box>
39
+ </Box>
40
+ );
41
+ };
42
+
43
+ export default InputBar;
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import { Text } from 'ink';
3
+ import { theme } from '../themes/themes.js';
4
+
5
+ interface StatusAreaProps {
6
+ status: 'idle' | 'typing' | 'thinking';
7
+ }
8
+
9
+ const StatusArea: React.FC<StatusAreaProps> = ({ status }: StatusAreaProps) => {
10
+ const statusText = {
11
+ idle: 'Idle',
12
+ typing: 'Typing...',
13
+ thinking: 'Thinking...',
14
+ };
15
+
16
+ return (
17
+ <Text color={theme.textDim} dimColor>
18
+ {statusText[status]}
19
+ </Text>
20
+ );
21
+ };
22
+
23
+ export default StatusArea;
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { Theme, ThemeName } from '../types.js';
4
+
5
+ interface StatusBarProps {
6
+ model: string;
7
+ totalTokens: number;
8
+ themeName: ThemeName;
9
+ theme: Theme;
10
+ }
11
+
12
+ const StatusBar: React.FC<StatusBarProps> = ({
13
+ model,
14
+ totalTokens,
15
+ themeName,
16
+ theme,
17
+ }) => {
18
+ return (
19
+ <Box>
20
+ <Text color={theme.statusBar}>
21
+ {model} │ tokens: {totalTokens} │ theme: {themeName}
22
+ </Text>
23
+ </Box>
24
+ );
25
+ };
26
+
27
+ export default StatusBar;
@@ -0,0 +1,22 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Text } from 'ink';
3
+ import { theme } from '../themes/themes.js';
4
+
5
+ const ThinkingDots: React.FC = () => {
6
+ const [dots, setDots] = useState('');
7
+
8
+ useEffect(() => {
9
+ const interval = setInterval(() => {
10
+ setDots((prev: string) => (prev.length < 3 ? prev + '.' : ''));
11
+ }, 400);
12
+ return () => clearInterval(interval);
13
+ }, []);
14
+
15
+ return (
16
+ <Text color={theme.textDim} dimColor>
17
+ Thinking{dots}
18
+ </Text>
19
+ );
20
+ };
21
+
22
+ export default ThinkingDots;
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { theme } from '../themes/themes.js';
4
+
5
+ interface TopBarProps {
6
+ provider?: string;
7
+ }
8
+
9
+ const TopBar: React.FC<TopBarProps> = ({ provider = 'Not Connected' }: TopBarProps) => {
10
+ return (
11
+ <Box
12
+ flexDirection="row"
13
+ justifyContent="space-between"
14
+ paddingX={1}
15
+ >
16
+ <Box flexDirection="row">
17
+ <Text bold color={theme.primary}>
18
+ COREX
19
+ </Text>
20
+ <Text color={theme.textDim}> | </Text>
21
+ <Text color={theme.textPrimary}>AI Gateway</Text>
22
+ </Box>
23
+ <Box flexDirection="row">
24
+ <Text color={theme.textDim}>Provider: </Text>
25
+ <Text color={theme.highlight}>{provider}</Text>
26
+ </Box>
27
+ </Box>
28
+ );
29
+ };
30
+
31
+ export default TopBar;