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.
- package/dist/tui/components/AccountList.js +4 -4
- package/dist/tui/components/AccountList.js.map +1 -1
- package/dist/tui/components/ActionPalette.d.ts.map +1 -1
- package/dist/tui/components/ActionPalette.js +3 -3
- package/dist/tui/components/ActionPalette.js.map +1 -1
- package/dist/tui/components/DashboardView.d.ts.map +1 -1
- package/dist/tui/components/DashboardView.js +169 -57
- package/dist/tui/components/DashboardView.js.map +1 -1
- package/dist/tui/components/McpServerList.js +3 -3
- package/dist/tui/components/McpServerList.js.map +1 -1
- package/dist/tui/components/Menu.js +2 -2
- package/dist/tui/components/Menu.js.map +1 -1
- package/dist/tui/components/ProviderList.d.ts.map +1 -1
- package/dist/tui/components/ProviderList.js +3 -3
- package/dist/tui/components/ProviderList.js.map +1 -1
- package/dist/tui/components/SectionBox.js +3 -3
- package/dist/tui/components/SectionBox.js.map +1 -1
- package/dist/tui/components/StatusBadge.js +5 -5
- package/dist/tui/components/StatusBadge.js.map +1 -1
- package/package.json +1 -1
- package/src/tui/components/AccountList.tsx +4 -4
- package/src/tui/components/ActionPalette.tsx +3 -6
- package/src/tui/components/DashboardView.tsx +221 -81
- package/src/tui/components/McpServerList.tsx +4 -4
- package/src/tui/components/Menu.tsx +2 -2
- package/src/tui/components/ProviderList.tsx +3 -5
- package/src/tui/components/SectionBox.tsx +4 -4
- package/src/tui/components/StatsRow.tsx +33 -33
- package/src/tui/components/StatusBadge.tsx +33 -33
|
@@ -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" :
|
|
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 ? "
|
|
69
|
+
<Text color={isSelected ? "white" : "gray"}>{cursor}</Text>
|
|
70
70
|
</Box>
|
|
71
71
|
{showCheckbox && (
|
|
72
72
|
<Box width={4}>
|
|
73
|
-
<Text color={isChecked ? "
|
|
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
|
|
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="
|
|
78
|
+
borderColor="gray"
|
|
79
79
|
paddingX={1}
|
|
80
80
|
paddingY={0}
|
|
81
81
|
>
|
|
82
82
|
{/* Search input */}
|
|
83
83
|
<Box marginBottom={1}>
|
|
84
|
-
<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
|
|
11
|
-
|
|
10
|
+
interface ModelConfig {
|
|
11
|
+
key: string;
|
|
12
12
|
displayName: string;
|
|
13
|
-
|
|
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
|
|
51
|
+
if (hours >= 24) {
|
|
26
52
|
const days = Math.floor(hours / 24);
|
|
27
53
|
return `${days}d ${hours % 24}h`;
|
|
28
54
|
}
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
|
45
|
-
const
|
|
46
|
-
|
|
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
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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}
|
|
201
|
+
<Box paddingX={1}>
|
|
90
202
|
<Box width={3}>
|
|
91
203
|
<Text dimColor>{" "}</Text>
|
|
92
204
|
</Box>
|
|
93
|
-
<Box width={
|
|
205
|
+
<Box width={emailWidth}>
|
|
94
206
|
<Text dimColor bold>EMAIL</Text>
|
|
95
207
|
</Box>
|
|
96
|
-
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
{/*
|
|
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
|
|
108
|
-
const limits = getAccountRateLimits(account);
|
|
226
|
+
const isDisabled = account.enabled === false;
|
|
109
227
|
|
|
110
228
|
// Truncate email
|
|
111
|
-
const email = account.email.length >
|
|
112
|
-
? account.email.slice(0,
|
|
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
|
|
237
|
+
<Text bold={isSelected}>
|
|
120
238
|
{isSelected ? "› " : " "}
|
|
121
239
|
</Text>
|
|
122
240
|
</Box>
|
|
123
241
|
|
|
124
242
|
{/* Email */}
|
|
125
|
-
<Box width={
|
|
243
|
+
<Box width={emailWidth}>
|
|
126
244
|
<Text
|
|
127
|
-
color={isSelected ? "cyan" : (account.enabled === false ? "gray" : "white")}
|
|
128
245
|
bold={isSelected}
|
|
129
|
-
dimColor={
|
|
246
|
+
dimColor={isDisabled}
|
|
130
247
|
>
|
|
131
248
|
{email}
|
|
132
249
|
</Text>
|
|
133
250
|
</Box>
|
|
134
251
|
|
|
135
|
-
{/*
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
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
|
|
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
|
|
40
|
+
<Text>{truncate(server.id, 18)}</Text>
|
|
41
41
|
</Box>
|
|
42
42
|
<Box width={10}>
|
|
43
43
|
{server.enabled ? (
|
|
44
|
-
<Text
|
|
44
|
+
<Text>enabled</Text>
|
|
45
45
|
) : (
|
|
46
|
-
<Text
|
|
46
|
+
<Text dimColor>disabled</Text>
|
|
47
47
|
)}
|
|
48
48
|
</Box>
|
|
49
49
|
<Box width={8}>
|
|
50
50
|
{server.hasEnvVars ? (
|
|
51
|
-
<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
|
|
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 ? "
|
|
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
|
|
40
|
+
<Text>{truncate(provider.name || provider.id, 18)}</Text>
|
|
41
41
|
</Box>
|
|
42
42
|
<Box width={10}>
|
|
43
|
-
<Text
|
|
43
|
+
<Text>{provider.modelCount}</Text>
|
|
44
44
|
</Box>
|
|
45
45
|
<Box width={10}>
|
|
46
|
-
<Text
|
|
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
|
|
26
|
-
{collapsed
|
|
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: "
|
|
11
|
-
limited: "
|
|
12
|
-
disabled: "gray",
|
|
13
|
-
error: "
|
|
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
|
-
}
|
|
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
|
+
}
|