opencode-account-manager 0.6.0 → 0.6.2

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.
@@ -60,17 +60,17 @@ export function AccountRow({ account, isSelected, isChecked, showCheckbox }: Acc
60
60
  : "";
61
61
 
62
62
  const cursor = isSelected ? ">" : " ";
63
- const emailColor = status === "disabled" ? "gray" : (isSelected ? "white" : "cyan");
63
+ const emailColor = status === "disabled" ? "gray" : "white";
64
64
 
65
65
  return (
66
66
  <Box flexDirection="column">
67
67
  <Box flexDirection="row" paddingX={1}>
68
68
  <Box width={2}>
69
- <Text color={isSelected ? "yellow" : "white"}>{cursor}</Text>
69
+ <Text color={isSelected ? "white" : "gray"}>{cursor}</Text>
70
70
  </Box>
71
71
  {showCheckbox && (
72
72
  <Box width={4}>
73
- <Text color={isChecked ? "green" : "gray"}>{checkbox}</Text>
73
+ <Text color={isChecked ? "white" : "gray"}>{checkbox}</Text>
74
74
  </Box>
75
75
  )}
76
76
  <Box width={28}>
@@ -90,7 +90,7 @@ export function AccountRow({ account, isSelected, isChecked, showCheckbox }: Acc
90
90
  </Box>
91
91
  {status === "limited" && limitDetails.length > 0 && (
92
92
  <Box paddingLeft={showCheckbox ? 8 : 4}>
93
- <Text color="gray">
93
+ <Text dimColor>
94
94
  └─ {limitDetails.join(" | ")}
95
95
  </Text>
96
96
  </Box>
@@ -75,13 +75,13 @@ export function ActionPalette({ actions, onSelect, onClose }: ActionPaletteProps
75
75
  <Box
76
76
  flexDirection="column"
77
77
  borderStyle="round"
78
- borderColor="cyan"
78
+ borderColor="gray"
79
79
  paddingX={1}
80
80
  paddingY={0}
81
81
  >
82
82
  {/* Search input */}
83
83
  <Box marginBottom={1}>
84
- <Text color="cyan" bold>{">"} </Text>
84
+ <Text bold>{">"} </Text>
85
85
  <Text>{search || " "}</Text>
86
86
  <Text color="gray">_</Text>
87
87
  </Box>
@@ -95,10 +95,7 @@ export function ActionPalette({ actions, onSelect, onClose }: ActionPaletteProps
95
95
  const isSelected = index === selectedIndex;
96
96
  return (
97
97
  <Box key={action.id}>
98
- <Text
99
- backgroundColor={isSelected ? "cyan" : undefined}
100
- color={isSelected ? "black" : "white"}
101
- >
98
+ <Text inverse={isSelected} dimColor={!isSelected}>
102
99
  {isSelected ? "▸ " : " "}
103
100
  {action.label}
104
101
  </Text>
@@ -7,33 +7,90 @@ interface DashboardViewProps {
7
7
  selectedIndex: number;
8
8
  }
9
9
 
10
- interface RateLimitInfo {
11
- model: string;
10
+ interface ModelConfig {
11
+ key: string;
12
12
  displayName: string;
13
- resetTime: number;
13
+ shortName: string;
14
+ }
15
+
16
+ // Model definitions with display names
17
+ const MODEL_CONFIGS: ModelConfig[] = [
18
+ { key: "claude", displayName: "Claude", shortName: "Claude" },
19
+ { key: "gemini-cli:gemini-3-pro-preview", displayName: "G3 Pro", shortName: "G3 Pro" },
20
+ { key: "gemini-cli:gemini-3-flash-preview", displayName: "G3 Flash", shortName: "G3 Fl" },
21
+ { key: "gemini-cli:imagen-3", displayName: "G3 Image", shortName: "G3 Img" },
22
+ { key: "gemini-cli:gemini-2.5-pro", displayName: "G2.5 Pro", shortName: "G2.5" },
23
+ { key: "gemini-cli:gemini-2.0-flash", displayName: "G2 Flash", shortName: "G2 Fl" },
24
+ { key: "gemini", displayName: "Gemini", shortName: "Gem" },
25
+ ];
26
+
27
+ const DEFAULT_MODEL_KEYS = [
28
+ "claude",
29
+ "gemini-cli:gemini-3-pro-preview",
30
+ "gemini-cli:gemini-3-flash-preview",
31
+ "gemini-cli:imagen-3",
32
+ ];
33
+
34
+ const MODEL_CONFIG_MAP = new Map(MODEL_CONFIGS.map((model) => [model.key, model]));
35
+
36
+ interface ModelStatus {
37
+ available: boolean;
14
38
  timeRemaining: string;
39
+ percentage: number;
40
+ resetTime: number;
15
41
  }
16
42
 
17
43
  function formatTimeRemaining(resetTime: number): string {
18
44
  const now = Date.now();
19
- if (resetTime <= now) return "";
45
+ if (resetTime <= now) return "0h 0m";
20
46
 
21
47
  const remaining = resetTime - now;
22
48
  const hours = Math.floor(remaining / 3600000);
23
49
  const minutes = Math.floor((remaining % 3600000) / 60000);
24
50
 
25
- if (hours > 24) {
51
+ if (hours >= 24) {
26
52
  const days = Math.floor(hours / 24);
27
53
  return `${days}d ${hours % 24}h`;
28
54
  }
29
- if (hours > 0) {
30
- return `${hours}h ${minutes}m`;
55
+ return `${hours}h ${minutes}m`;
56
+ }
57
+
58
+ function calculatePercentage(resetTime: number): number {
59
+ const now = Date.now();
60
+ if (resetTime <= now) return 100;
61
+
62
+ const remaining = resetTime - now;
63
+ const maxTime = 24 * 3600000;
64
+ const clamped = Math.min(remaining, maxTime);
65
+ const elapsed = maxTime - clamped;
66
+ return Math.max(0, Math.min(100, Math.round((elapsed / maxTime) * 100)));
67
+ }
68
+
69
+ function getModelStatus(account: Account, modelKey: string): ModelStatus {
70
+ const now = Date.now();
71
+ const rateLimits = account.rateLimitResetTimes || {};
72
+
73
+ // Check if model is rate limited
74
+ const resetTime = rateLimits[modelKey] || 0;
75
+
76
+ if (resetTime <= now) {
77
+ return {
78
+ available: true,
79
+ timeRemaining: "0h 0m",
80
+ percentage: 100,
81
+ resetTime: 0,
82
+ };
31
83
  }
32
- return `${minutes}m`;
84
+
85
+ return {
86
+ available: false,
87
+ timeRemaining: formatTimeRemaining(resetTime),
88
+ percentage: calculatePercentage(resetTime),
89
+ resetTime,
90
+ };
33
91
  }
34
92
 
35
93
  function getModelDisplayName(model: string): string {
36
- // Shorten long model names for display
37
94
  if (model.includes(":")) {
38
95
  const parts = model.split(":");
39
96
  return parts[1] || parts[0];
@@ -41,36 +98,85 @@ function getModelDisplayName(model: string): string {
41
98
  return model;
42
99
  }
43
100
 
44
- function getAccountRateLimits(account: Account): RateLimitInfo[] {
45
- const now = Date.now();
46
- const rateLimits = account.rateLimitResetTimes || {};
47
-
48
- return Object.entries(rateLimits)
49
- .filter(([_, time]) => time > now)
50
- .map(([model, resetTime]) => ({
51
- model,
52
- displayName: getModelDisplayName(model),
53
- resetTime,
54
- timeRemaining: formatTimeRemaining(resetTime),
55
- }))
56
- .sort((a, b) => a.resetTime - b.resetTime);
101
+ function getModelShortName(model: string): string {
102
+ const name = getModelDisplayName(model);
103
+ if (name.length <= 7) return name;
104
+ return name.slice(0, 6) + "…";
57
105
  }
58
106
 
59
- function getAccountStatus(account: Account): {
60
- status: string;
61
- statusColor: string;
62
- indicator: string;
63
- } {
64
- if (account.enabled === false) {
65
- return { status: "disabled", statusColor: "gray", indicator: "○" };
66
- }
67
-
68
- const limits = getAccountRateLimits(account);
69
- if (limits.length === 0) {
70
- return { status: "available", statusColor: "white", indicator: "●" };
71
- }
72
-
73
- return { status: "limited", statusColor: "gray", indicator: "◐" };
107
+ function getDisplayModels(accounts: Account[]): ModelConfig[] {
108
+ const activeKeys = new Set<string>(DEFAULT_MODEL_KEYS);
109
+
110
+ accounts.forEach((acc) => {
111
+ if (acc.rateLimitResetTimes) {
112
+ Object.keys(acc.rateLimitResetTimes).forEach((key) => {
113
+ activeKeys.add(key);
114
+ });
115
+ }
116
+ });
117
+
118
+ const models: ModelConfig[] = [];
119
+
120
+ DEFAULT_MODEL_KEYS.forEach((key) => {
121
+ const config = MODEL_CONFIG_MAP.get(key);
122
+ if (config) models.push(config);
123
+ });
124
+
125
+ const extraKeys = Array.from(activeKeys).filter((key) => !DEFAULT_MODEL_KEYS.includes(key));
126
+ extraKeys.sort().forEach((key) => {
127
+ const config = MODEL_CONFIG_MAP.get(key);
128
+ if (config) {
129
+ models.push(config);
130
+ } else {
131
+ models.push({
132
+ key,
133
+ displayName: getModelDisplayName(key),
134
+ shortName: getModelShortName(key),
135
+ });
136
+ }
137
+ });
138
+
139
+ return models;
140
+ }
141
+
142
+ function formatLastUsed(timestamp?: number): string {
143
+ if (!timestamp) return "--";
144
+ const date = new Date(timestamp);
145
+ const month = String(date.getMonth() + 1).padStart(2, "0");
146
+ const day = String(date.getDate()).padStart(2, "0");
147
+ const hours = String(date.getHours()).padStart(2, "0");
148
+ const minutes = String(date.getMinutes()).padStart(2, "0");
149
+ return `${month}/${day} ${hours}:${minutes}`;
150
+ }
151
+
152
+ // Progress bar component using text
153
+ function ProgressBar({ percentage, width = 6 }: { percentage: number; width?: number }) {
154
+ const filled = Math.round((percentage / 100) * width);
155
+ const empty = width - filled;
156
+
157
+ return (
158
+ <Text>
159
+ {"█".repeat(filled)}
160
+ <Text dimColor>{"░".repeat(empty)}</Text>
161
+ </Text>
162
+ );
163
+ }
164
+
165
+ // Model cell component
166
+ function ModelCell({ status, width = 20 }: { status: ModelStatus; width?: number }) {
167
+ return (
168
+ <Box width={width}>
169
+ <Box width={8}>
170
+ <ProgressBar percentage={status.percentage} width={6} />
171
+ </Box>
172
+ <Box width={7}>
173
+ <Text dimColor>{status.timeRemaining.padStart(6)}</Text>
174
+ </Box>
175
+ <Box width={5}>
176
+ <Text>{`${status.percentage}%`.padStart(4)}</Text>
177
+ </Box>
178
+ </Box>
179
+ );
74
180
  }
75
181
 
76
182
  export function DashboardView({ accounts, selectedIndex }: DashboardViewProps) {
@@ -83,78 +189,88 @@ export function DashboardView({ accounts, selectedIndex }: DashboardViewProps) {
83
189
  );
84
190
  }
85
191
 
192
+ const displayModels = getDisplayModels(accounts);
193
+ const modelColumnWidth = 20;
194
+ const emailWidth = 28;
195
+ const lastUsedWidth = 14;
196
+ const totalWidth = 3 + emailWidth + displayModels.length * modelColumnWidth + lastUsedWidth;
197
+
86
198
  return (
87
199
  <Box flexDirection="column">
88
200
  {/* Header */}
89
- <Box paddingX={1} marginBottom={1}>
201
+ <Box paddingX={1}>
90
202
  <Box width={3}>
91
203
  <Text dimColor>{" "}</Text>
92
204
  </Box>
93
- <Box width={30}>
205
+ <Box width={emailWidth}>
94
206
  <Text dimColor bold>EMAIL</Text>
95
207
  </Box>
96
- <Box width={10}>
97
- <Text dimColor bold>STATUS</Text>
98
- </Box>
99
- <Box flexGrow={1}>
100
- <Text dimColor bold>RATE LIMITS</Text>
208
+ {displayModels.map(model => (
209
+ <Box key={model.key} width={modelColumnWidth}>
210
+ <Text dimColor bold>{model.shortName}</Text>
211
+ </Box>
212
+ ))}
213
+ <Box width={lastUsedWidth}>
214
+ <Text dimColor bold>LAST USED</Text>
101
215
  </Box>
102
216
  </Box>
103
217
 
104
- {/* Accounts */}
218
+ {/* Separator */}
219
+ <Box paddingX={1}>
220
+ <Text dimColor>{"─".repeat(totalWidth)}</Text>
221
+ </Box>
222
+
223
+ {/* Account rows */}
105
224
  {accounts.map((account, index) => {
106
225
  const isSelected = index === selectedIndex;
107
- const { status, statusColor, indicator } = getAccountStatus(account);
108
- const limits = getAccountRateLimits(account);
226
+ const isDisabled = account.enabled === false;
109
227
 
110
228
  // Truncate email
111
- const email = account.email.length > 28
112
- ? account.email.slice(0, 25) + "..."
229
+ const email = account.email.length > emailWidth - 3
230
+ ? account.email.slice(0, emailWidth - 6) + "..."
113
231
  : account.email;
114
232
 
115
233
  return (
116
234
  <Box key={account.email} paddingX={1}>
117
235
  {/* Selection cursor */}
118
236
  <Box width={3}>
119
- <Text color={isSelected ? "cyan" : undefined} bold={isSelected}>
237
+ <Text bold={isSelected}>
120
238
  {isSelected ? "› " : " "}
121
239
  </Text>
122
240
  </Box>
123
241
 
124
242
  {/* Email */}
125
- <Box width={30}>
243
+ <Box width={emailWidth}>
126
244
  <Text
127
- color={isSelected ? "cyan" : (account.enabled === false ? "gray" : "white")}
128
245
  bold={isSelected}
129
- dimColor={account.enabled === false}
246
+ dimColor={isDisabled}
130
247
  >
131
248
  {email}
132
249
  </Text>
133
250
  </Box>
134
251
 
135
- {/* Status indicator */}
136
- <Box width={10}>
137
- <Text color={statusColor}>
138
- {indicator} {status}
139
- </Text>
140
- </Box>
141
-
142
- {/* Rate limits - show all models */}
143
- <Box flexGrow={1}>
144
- {limits.length === 0 ? (
145
- <Text dimColor>—</Text>
146
- ) : (
147
- <Text>
148
- {limits.map((limit, i) => (
149
- <Text key={limit.model}>
150
- <Text dimColor>{limit.displayName}</Text>
151
- <Text color="gray">:</Text>
152
- <Text color="white">{limit.timeRemaining}</Text>
153
- {i < limits.length - 1 ? <Text dimColor> │ </Text> : null}
154
- </Text>
155
- ))}
156
- </Text>
157
- )}
252
+ {/* Model columns */}
253
+ {displayModels.map(model => {
254
+ const status = getModelStatus(account, model.key);
255
+
256
+ if (isDisabled) {
257
+ return (
258
+ <Box key={model.key} width={modelColumnWidth}>
259
+ <Text dimColor>── disabled ──</Text>
260
+ </Box>
261
+ );
262
+ }
263
+
264
+ return (
265
+ <ModelCell
266
+ key={model.key}
267
+ status={status}
268
+ width={modelColumnWidth}
269
+ />
270
+ );
271
+ })}
272
+ <Box width={lastUsedWidth}>
273
+ <Text dimColor>{formatLastUsed(account.lastUsed)}</Text>
158
274
  </Box>
159
275
  </Box>
160
276
  );
@@ -162,19 +278,43 @@ export function DashboardView({ accounts, selectedIndex }: DashboardViewProps) {
162
278
 
163
279
  {/* Summary */}
164
280
  <Box paddingX={1} marginTop={1}>
165
- <Text dimColor>
166
- ─────────────────────────────────────────────────────────────────
167
- </Text>
281
+ <Text dimColor>{"─".repeat(totalWidth)}</Text>
168
282
  </Box>
283
+
169
284
  <Box paddingX={1}>
170
285
  <Text dimColor>
171
- {accounts.length} accounts • {accounts.filter(a => a.enabled !== false && getAccountRateLimits(a).length === 0).length} available • {accounts.filter(a => getAccountRateLimits(a).length > 0).length} limited
286
+ {accounts.length} accounts
287
+ </Text>
288
+ <Text dimColor> │ </Text>
289
+ <Text>
290
+ {accounts.filter(a => {
291
+ if (a.enabled === false) return false;
292
+ const now = Date.now();
293
+ const limits = a.rateLimitResetTimes || {};
294
+ return !Object.values(limits).some(t => t > now);
295
+ }).length}
296
+ </Text>
297
+ <Text dimColor> available</Text>
298
+ <Text dimColor> │ </Text>
299
+ <Text dimColor>
300
+ {accounts.filter(a => {
301
+ if (a.enabled === false) return false;
302
+ const now = Date.now();
303
+ const limits = a.rateLimitResetTimes || {};
304
+ return Object.values(limits).some(t => t > now);
305
+ }).length} limited
306
+ </Text>
307
+ <Text dimColor> │ </Text>
308
+ <Text dimColor>
309
+ {accounts.filter(a => a.enabled === false).length} disabled
172
310
  </Text>
173
311
  </Box>
174
312
 
175
313
  {/* Legend */}
176
314
  <Box paddingX={1} marginTop={1}>
177
- <Text dimColor>● available ◐ limited ○ disabled</Text>
315
+ <Text dimColor>████ 100%</Text>
316
+ <Text dimColor> ░░░░ limited </Text>
317
+ <Text dimColor>── disabled</Text>
178
318
  </Box>
179
319
  </Box>
180
320
  );
@@ -37,18 +37,18 @@ export function McpServerList({ servers }: McpServerListProps) {
37
37
  {servers.map((server) => (
38
38
  <Box key={server.id}>
39
39
  <Box width={20}>
40
- <Text color="cyan">{truncate(server.id, 18)}</Text>
40
+ <Text>{truncate(server.id, 18)}</Text>
41
41
  </Box>
42
42
  <Box width={10}>
43
43
  {server.enabled ? (
44
- <Text color="green">enabled</Text>
44
+ <Text>enabled</Text>
45
45
  ) : (
46
- <Text color="red">disabled</Text>
46
+ <Text dimColor>disabled</Text>
47
47
  )}
48
48
  </Box>
49
49
  <Box width={8}>
50
50
  {server.hasEnvVars ? (
51
- <Text color="yellow">{server.envVarCount}</Text>
51
+ <Text>{server.envVarCount}</Text>
52
52
  ) : (
53
53
  <Text dimColor>-</Text>
54
54
  )}
@@ -81,14 +81,14 @@ export function MenuBar({ onSelect, selectMode = false, selectedCount = 0 }: Men
81
81
  <Box flexDirection="column">
82
82
  {selectMode && (
83
83
  <Box marginBottom={1} paddingX={1}>
84
- <Text color="yellow" bold>
84
+ <Text dimColor bold>
85
85
  SELECT MODE - {selectedCount} selected | ↑↓ navigate | SPACE toggle | ←→ switch section
86
86
  </Text>
87
87
  </Box>
88
88
  )}
89
89
  <Box
90
90
  borderStyle="single"
91
- borderColor={selectMode ? "yellow" : "gray"}
91
+ borderColor={selectMode ? "white" : "gray"}
92
92
  paddingX={1}
93
93
  justifyContent="space-between"
94
94
  >
@@ -37,15 +37,13 @@ export function ProviderList({ providers }: ProviderListProps) {
37
37
  {providers.map((provider) => (
38
38
  <Box key={provider.id}>
39
39
  <Box width={20}>
40
- <Text color="cyan">{truncate(provider.name || provider.id, 18)}</Text>
40
+ <Text>{truncate(provider.name || provider.id, 18)}</Text>
41
41
  </Box>
42
42
  <Box width={10}>
43
- <Text color="yellow">{provider.modelCount}</Text>
43
+ <Text>{provider.modelCount}</Text>
44
44
  </Box>
45
45
  <Box width={10}>
46
- <Text color={provider.type === "builtin" ? "green" : "magenta"}>
47
- {provider.type}
48
- </Text>
46
+ <Text dimColor>{provider.type}</Text>
49
47
  </Box>
50
48
  <Box>
51
49
  <Text dimColor>{provider.baseURL || "-"}</Text>
@@ -22,14 +22,14 @@ export function SectionBox({
22
22
  marginBottom={1}
23
23
  >
24
24
  <Box paddingX={1}>
25
- <Text bold color="white">{title}</Text>
26
- {collapsed && <Text dimColor> (collapsed)</Text>}
25
+ <Text bold>{title}</Text>
26
+ {collapsed ? <Text dimColor> (collapsed)</Text> : null}
27
27
  </Box>
28
- {!collapsed && (
28
+ {!collapsed ? (
29
29
  <Box flexDirection="column" paddingY={0}>
30
30
  {children}
31
31
  </Box>
32
- )}
32
+ ) : null}
33
33
  </Box>
34
34
  );
35
35
  }
@@ -1,33 +1,33 @@
1
- import React from "react";
2
- import { Box, Text } from "ink";
3
-
4
- interface StatCardProps {
5
- label: string;
6
- value: string | number;
7
- color?: string;
8
- }
9
-
10
- export function StatCard({ label, value, color = "white" }: StatCardProps) {
11
- return (
12
- <Box flexDirection="column" marginRight={2}>
13
- <Text dimColor>{label}</Text>
14
- <Text bold color={color}>
15
- {value}
16
- </Text>
17
- </Box>
18
- );
19
- }
20
-
21
- interface StatsRowProps {
22
- stats: StatCardProps[];
23
- }
24
-
25
- export function StatsRow({ stats }: StatsRowProps) {
26
- return (
27
- <Box flexDirection="row" marginY={1}>
28
- {stats.map((stat, index) => (
29
- <StatCard key={index} {...stat} />
30
- ))}
31
- </Box>
32
- );
33
- }
1
+ import React from "react";
2
+ import { Box, Text } from "ink";
3
+
4
+ interface StatCardProps {
5
+ label: string;
6
+ value: string | number;
7
+ color?: string;
8
+ }
9
+
10
+ export function StatCard({ label, value, color = "white" }: StatCardProps) {
11
+ return (
12
+ <Box flexDirection="column" marginRight={2}>
13
+ <Text dimColor>{label}</Text>
14
+ <Text bold color={color}>
15
+ {value}
16
+ </Text>
17
+ </Box>
18
+ );
19
+ }
20
+
21
+ interface StatsRowProps {
22
+ stats: StatCardProps[];
23
+ }
24
+
25
+ export function StatsRow({ stats }: StatsRowProps) {
26
+ return (
27
+ <Box flexDirection="row" marginY={1}>
28
+ {stats.map((stat, index) => (
29
+ <StatCard key={index} {...stat} />
30
+ ))}
31
+ </Box>
32
+ );
33
+ }
@@ -1,33 +1,33 @@
1
- import React from "react";
2
- import { Text } from "ink";
3
-
4
- interface StatusBadgeProps {
5
- status: "available" | "limited" | "disabled" | "error";
6
- label?: string;
7
- }
8
-
9
- const STATUS_COLORS: Record<string, string> = {
10
- available: "green",
11
- limited: "yellow",
12
- disabled: "gray",
13
- error: "red",
14
- };
15
-
16
- const STATUS_ICONS: Record<string, string> = {
17
- available: "●",
18
- limited: "",
19
- disabled: "x",
20
- error: "!",
21
- };
22
-
23
- export function StatusBadge({ status, label }: StatusBadgeProps) {
24
- const color = STATUS_COLORS[status] || "gray";
25
- const icon = STATUS_ICONS[status] || "?";
26
- const text = label || status.toUpperCase();
27
-
28
- return (
29
- <Text color={color}>
30
- {icon} {text}
31
- </Text>
32
- );
33
- }
1
+ import React from "react";
2
+ import { Text } from "ink";
3
+
4
+ interface StatusBadgeProps {
5
+ status: "available" | "limited" | "disabled" | "error";
6
+ label?: string;
7
+ }
8
+
9
+ const STATUS_COLORS: Record<string, string> = {
10
+ available: "white",
11
+ limited: "gray",
12
+ disabled: "gray",
13
+ error: "gray",
14
+ };
15
+
16
+ const STATUS_ICONS: Record<string, string> = {
17
+ available: "●",
18
+ limited: "",
19
+ disabled: "",
20
+ error: "!",
21
+ };
22
+
23
+ export function StatusBadge({ status, label }: StatusBadgeProps) {
24
+ const color = STATUS_COLORS[status] || "gray";
25
+ const icon = STATUS_ICONS[status] || "?";
26
+ const text = label || status.toUpperCase();
27
+
28
+ return (
29
+ <Text color={color}>
30
+ {icon} {text}
31
+ </Text>
32
+ );
33
+ }