opencode-account-manager 0.6.0 → 0.6.1
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/DashboardView.d.ts.map +1 -1
- package/dist/tui/components/DashboardView.js +127 -60
- package/dist/tui/components/DashboardView.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/DashboardView.tsx +171 -85
- package/src/tui/components/SectionBox.tsx +4 -4
- package/src/tui/components/StatsRow.tsx +33 -33
- package/src/tui/components/StatusBadge.tsx +33 -33
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardView.d.ts","sourceRoot":"","sources":["../../../src/tui/components/DashboardView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"DashboardView.d.ts","sourceRoot":"","sources":["../../../src/tui/components/DashboardView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAgID,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,EAAE,kBAAkB,qBAmI5E"}
|
|
@@ -6,52 +6,98 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.DashboardView = DashboardView;
|
|
7
7
|
const react_1 = __importDefault(require("react"));
|
|
8
8
|
const ink_1 = require("ink");
|
|
9
|
+
// Model definitions with display names
|
|
10
|
+
const MODEL_CONFIGS = [
|
|
11
|
+
{ key: "claude", displayName: "Claude", shortName: "Claude" },
|
|
12
|
+
{ key: "gemini", displayName: "Gemini Pro", shortName: "G Pro" },
|
|
13
|
+
{ key: "gemini-cli:gemini-3-flash-preview", displayName: "Gemini Flash", shortName: "G Flash" },
|
|
14
|
+
{ key: "gemini-cli:gemini-3-pro-preview", displayName: "Gemini Pro", shortName: "G Pro" },
|
|
15
|
+
{ key: "gemini-cli:gemini-2.5-pro", displayName: "G 2.5 Pro", shortName: "G2.5P" },
|
|
16
|
+
{ key: "gemini-cli:imagen-3", displayName: "Imagen 3", shortName: "Img3" },
|
|
17
|
+
];
|
|
9
18
|
function formatTimeRemaining(resetTime) {
|
|
10
19
|
const now = Date.now();
|
|
11
20
|
if (resetTime <= now)
|
|
12
|
-
return "";
|
|
21
|
+
return "0h 0m";
|
|
13
22
|
const remaining = resetTime - now;
|
|
14
23
|
const hours = Math.floor(remaining / 3600000);
|
|
15
24
|
const minutes = Math.floor((remaining % 3600000) / 60000);
|
|
16
|
-
if (hours
|
|
25
|
+
if (hours >= 24) {
|
|
17
26
|
const days = Math.floor(hours / 24);
|
|
18
27
|
return `${days}d ${hours % 24}h`;
|
|
19
28
|
}
|
|
20
|
-
|
|
21
|
-
return `${hours}h ${minutes}m`;
|
|
22
|
-
}
|
|
23
|
-
return `${minutes}m`;
|
|
29
|
+
return `${hours}h ${minutes}m`;
|
|
24
30
|
}
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
function calculatePercentage(resetTime) {
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
if (resetTime <= now)
|
|
34
|
+
return 100;
|
|
35
|
+
const remaining = resetTime - now;
|
|
36
|
+
const maxTime = 5 * 3600000; // 5 hours as max reference
|
|
37
|
+
const elapsed = maxTime - remaining;
|
|
38
|
+
const pct = Math.max(0, Math.min(100, Math.round((elapsed / maxTime) * 100)));
|
|
39
|
+
return pct;
|
|
32
40
|
}
|
|
33
|
-
function
|
|
41
|
+
function getModelStatus(account, modelKey) {
|
|
34
42
|
const now = Date.now();
|
|
35
43
|
const rateLimits = account.rateLimitResetTimes || {};
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
// Check if model is rate limited
|
|
45
|
+
const resetTime = rateLimits[modelKey] || 0;
|
|
46
|
+
if (resetTime <= now) {
|
|
47
|
+
return {
|
|
48
|
+
available: true,
|
|
49
|
+
timeRemaining: "0h 0m",
|
|
50
|
+
percentage: 100,
|
|
51
|
+
resetTime: 0,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
available: false,
|
|
42
56
|
timeRemaining: formatTimeRemaining(resetTime),
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
percentage: calculatePercentage(resetTime),
|
|
58
|
+
resetTime,
|
|
59
|
+
};
|
|
45
60
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
61
|
+
function getActiveModels(accounts) {
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
const activeKeys = new Set();
|
|
64
|
+
// Collect all model keys that have rate limit data
|
|
65
|
+
accounts.forEach(acc => {
|
|
66
|
+
if (acc.rateLimitResetTimes) {
|
|
67
|
+
Object.keys(acc.rateLimitResetTimes).forEach(key => {
|
|
68
|
+
activeKeys.add(key);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// Return models that are in our config OR have rate limit data
|
|
73
|
+
const models = MODEL_CONFIGS.filter(m => activeKeys.has(m.key));
|
|
74
|
+
// If no models found, show default ones
|
|
75
|
+
if (models.length === 0) {
|
|
76
|
+
return MODEL_CONFIGS.slice(0, 2); // Claude and Gemini
|
|
53
77
|
}
|
|
54
|
-
return
|
|
78
|
+
return models;
|
|
79
|
+
}
|
|
80
|
+
// Progress bar component using text
|
|
81
|
+
function ProgressBar({ percentage, width = 8 }) {
|
|
82
|
+
const filled = Math.round((percentage / 100) * width);
|
|
83
|
+
const empty = width - filled;
|
|
84
|
+
// Color based on percentage
|
|
85
|
+
const color = percentage === 100 ? "white" : percentage >= 50 ? "gray" : "gray";
|
|
86
|
+
return (react_1.default.createElement(ink_1.Text, { color: color },
|
|
87
|
+
"█".repeat(filled),
|
|
88
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true }, "░".repeat(empty))));
|
|
89
|
+
}
|
|
90
|
+
// Model cell component
|
|
91
|
+
function ModelCell({ status, width = 18 }) {
|
|
92
|
+
return (react_1.default.createElement(ink_1.Box, { width: width },
|
|
93
|
+
react_1.default.createElement(ink_1.Box, { width: 8 },
|
|
94
|
+
react_1.default.createElement(ProgressBar, { percentage: status.percentage, width: 6 })),
|
|
95
|
+
react_1.default.createElement(ink_1.Box, { width: 6 },
|
|
96
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true }, status.timeRemaining.padStart(5))),
|
|
97
|
+
react_1.default.createElement(ink_1.Box, { width: 4 },
|
|
98
|
+
react_1.default.createElement(ink_1.Text, { color: status.percentage === 100 ? "white" : "gray" },
|
|
99
|
+
String(status.percentage).padStart(3),
|
|
100
|
+
"%"))));
|
|
55
101
|
}
|
|
56
102
|
function DashboardView({ accounts, selectedIndex }) {
|
|
57
103
|
if (accounts.length === 0) {
|
|
@@ -59,51 +105,72 @@ function DashboardView({ accounts, selectedIndex }) {
|
|
|
59
105
|
react_1.default.createElement(ink_1.Text, { dimColor: true }, "No accounts configured."),
|
|
60
106
|
react_1.default.createElement(ink_1.Text, { dimColor: true }, "Press P to open actions and import accounts.")));
|
|
61
107
|
}
|
|
108
|
+
const activeModels = getActiveModels(accounts);
|
|
109
|
+
const modelColumnWidth = 18;
|
|
110
|
+
const emailWidth = 28;
|
|
62
111
|
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column" },
|
|
63
|
-
react_1.default.createElement(ink_1.Box, { paddingX: 1
|
|
112
|
+
react_1.default.createElement(ink_1.Box, { paddingX: 1 },
|
|
64
113
|
react_1.default.createElement(ink_1.Box, { width: 3 },
|
|
65
114
|
react_1.default.createElement(ink_1.Text, { dimColor: true }, " ")),
|
|
66
|
-
react_1.default.createElement(ink_1.Box, { width:
|
|
115
|
+
react_1.default.createElement(ink_1.Box, { width: emailWidth },
|
|
67
116
|
react_1.default.createElement(ink_1.Text, { dimColor: true, bold: true }, "EMAIL")),
|
|
68
|
-
react_1.default.createElement(ink_1.Box, { width:
|
|
69
|
-
react_1.default.createElement(ink_1.Text, { dimColor: true, bold: true },
|
|
70
|
-
|
|
71
|
-
|
|
117
|
+
activeModels.map(model => (react_1.default.createElement(ink_1.Box, { key: model.key, width: modelColumnWidth },
|
|
118
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true, bold: true }, model.shortName))))),
|
|
119
|
+
react_1.default.createElement(ink_1.Box, { paddingX: 1 },
|
|
120
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true }, "─".repeat(3 + emailWidth + activeModels.length * modelColumnWidth))),
|
|
72
121
|
accounts.map((account, index) => {
|
|
73
122
|
const isSelected = index === selectedIndex;
|
|
74
|
-
const
|
|
75
|
-
const limits = getAccountRateLimits(account);
|
|
123
|
+
const isDisabled = account.enabled === false;
|
|
76
124
|
// Truncate email
|
|
77
|
-
const email = account.email.length >
|
|
78
|
-
? account.email.slice(0,
|
|
125
|
+
const email = account.email.length > emailWidth - 3
|
|
126
|
+
? account.email.slice(0, emailWidth - 6) + "..."
|
|
79
127
|
: account.email;
|
|
80
128
|
return (react_1.default.createElement(ink_1.Box, { key: account.email, paddingX: 1 },
|
|
81
129
|
react_1.default.createElement(ink_1.Box, { width: 3 },
|
|
82
|
-
react_1.default.createElement(ink_1.Text, {
|
|
83
|
-
react_1.default.createElement(ink_1.Box, { width:
|
|
84
|
-
react_1.default.createElement(ink_1.Text, {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
react_1.default.createElement(
|
|
92
|
-
|
|
93
|
-
react_1.default.createElement(ink_1.Text, { color: "white" }, limit.timeRemaining),
|
|
94
|
-
i < limits.length - 1 ? react_1.default.createElement(ink_1.Text, { dimColor: true }, " \u2502 ") : null))))))));
|
|
130
|
+
react_1.default.createElement(ink_1.Text, { bold: isSelected }, isSelected ? "› " : " ")),
|
|
131
|
+
react_1.default.createElement(ink_1.Box, { width: emailWidth },
|
|
132
|
+
react_1.default.createElement(ink_1.Text, { bold: isSelected, dimColor: isDisabled }, email)),
|
|
133
|
+
activeModels.map(model => {
|
|
134
|
+
const status = getModelStatus(account, model.key);
|
|
135
|
+
if (isDisabled) {
|
|
136
|
+
return (react_1.default.createElement(ink_1.Box, { key: model.key, width: modelColumnWidth },
|
|
137
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true }, "\u2500\u2500 disabled \u2500\u2500")));
|
|
138
|
+
}
|
|
139
|
+
return (react_1.default.createElement(ModelCell, { key: model.key, status: status, width: modelColumnWidth }));
|
|
140
|
+
})));
|
|
95
141
|
}),
|
|
96
142
|
react_1.default.createElement(ink_1.Box, { paddingX: 1, marginTop: 1 },
|
|
97
|
-
react_1.default.createElement(ink_1.Text, { dimColor: true }, "
|
|
143
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true }, "─".repeat(3 + emailWidth + activeModels.length * modelColumnWidth))),
|
|
98
144
|
react_1.default.createElement(ink_1.Box, { paddingX: 1 },
|
|
99
145
|
react_1.default.createElement(ink_1.Text, { dimColor: true },
|
|
100
146
|
accounts.length,
|
|
101
|
-
" accounts
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
147
|
+
" accounts"),
|
|
148
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true }, " \u2502 "),
|
|
149
|
+
react_1.default.createElement(ink_1.Text, null, accounts.filter(a => {
|
|
150
|
+
if (a.enabled === false)
|
|
151
|
+
return false;
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
const limits = a.rateLimitResetTimes || {};
|
|
154
|
+
return !Object.values(limits).some(t => t > now);
|
|
155
|
+
}).length),
|
|
156
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true }, " available"),
|
|
157
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true }, " \u2502 "),
|
|
158
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true },
|
|
159
|
+
accounts.filter(a => {
|
|
160
|
+
if (a.enabled === false)
|
|
161
|
+
return false;
|
|
162
|
+
const now = Date.now();
|
|
163
|
+
const limits = a.rateLimitResetTimes || {};
|
|
164
|
+
return Object.values(limits).some(t => t > now);
|
|
165
|
+
}).length,
|
|
166
|
+
" limited"),
|
|
167
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true }, " \u2502 "),
|
|
168
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true },
|
|
169
|
+
accounts.filter(a => a.enabled === false).length,
|
|
170
|
+
" disabled")),
|
|
106
171
|
react_1.default.createElement(ink_1.Box, { paddingX: 1, marginTop: 1 },
|
|
107
|
-
react_1.default.createElement(ink_1.Text, { dimColor: true }, "\
|
|
172
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true }, "\u2588\u2588\u2588\u2588 100%"),
|
|
173
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true }, " \u2591\u2591\u2591\u2591 limited "),
|
|
174
|
+
react_1.default.createElement(ink_1.Text, { dimColor: true }, "\u2500\u2500 disabled"))));
|
|
108
175
|
}
|
|
109
176
|
//# sourceMappingURL=DashboardView.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardView.js","sourceRoot":"","sources":["../../../src/tui/components/DashboardView.tsx"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"DashboardView.js","sourceRoot":"","sources":["../../../src/tui/components/DashboardView.tsx"],"names":[],"mappings":"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"}
|
|
@@ -9,8 +9,8 @@ const ink_1 = require("ink");
|
|
|
9
9
|
function SectionBox({ title, children, borderColor = "gray", collapsed = false }) {
|
|
10
10
|
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", borderStyle: "round", borderColor: borderColor, marginBottom: 1 },
|
|
11
11
|
react_1.default.createElement(ink_1.Box, { paddingX: 1 },
|
|
12
|
-
react_1.default.createElement(ink_1.Text, { bold: true
|
|
13
|
-
collapsed
|
|
14
|
-
!collapsed
|
|
12
|
+
react_1.default.createElement(ink_1.Text, { bold: true }, title),
|
|
13
|
+
collapsed ? react_1.default.createElement(ink_1.Text, { dimColor: true }, " (collapsed)") : null),
|
|
14
|
+
!collapsed ? (react_1.default.createElement(ink_1.Box, { flexDirection: "column", paddingY: 0 }, children)) : null));
|
|
15
15
|
}
|
|
16
16
|
//# sourceMappingURL=SectionBox.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SectionBox.js","sourceRoot":"","sources":["../../../src/tui/components/SectionBox.tsx"],"names":[],"mappings":";;;;;AAUA,gCAwBC;AAlCD,kDAA0B;AAC1B,6BAAgC;AAShC,SAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,WAAW,GAAG,MAAM,EACpB,SAAS,GAAG,KAAK,EACD;IAChB,OAAO,CACL,8BAAC,SAAG,IACF,aAAa,EAAC,QAAQ,EACtB,WAAW,EAAC,OAAO,EACnB,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,CAAC;QAEf,8BAAC,SAAG,IAAC,QAAQ,EAAE,CAAC;YACd,8BAAC,UAAI,IAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"SectionBox.js","sourceRoot":"","sources":["../../../src/tui/components/SectionBox.tsx"],"names":[],"mappings":";;;;;AAUA,gCAwBC;AAlCD,kDAA0B;AAC1B,6BAAgC;AAShC,SAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,WAAW,GAAG,MAAM,EACpB,SAAS,GAAG,KAAK,EACD;IAChB,OAAO,CACL,8BAAC,SAAG,IACF,aAAa,EAAC,QAAQ,EACtB,WAAW,EAAC,OAAO,EACnB,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,CAAC;QAEf,8BAAC,SAAG,IAAC,QAAQ,EAAE,CAAC;YACd,8BAAC,UAAI,IAAC,IAAI,UAAE,KAAK,CAAQ;YACxB,SAAS,CAAC,CAAC,CAAC,8BAAC,UAAI,IAAC,QAAQ,yBAAoB,CAAC,CAAC,CAAC,IAAI,CAClD;QACL,CAAC,SAAS,CAAC,CAAC,CAAC,CACZ,8BAAC,SAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,QAAQ,EAAE,CAAC,IACpC,QAAQ,CACL,CACP,CAAC,CAAC,CAAC,IAAI,CACJ,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -7,15 +7,15 @@ exports.StatusBadge = StatusBadge;
|
|
|
7
7
|
const react_1 = __importDefault(require("react"));
|
|
8
8
|
const ink_1 = require("ink");
|
|
9
9
|
const STATUS_COLORS = {
|
|
10
|
-
available: "
|
|
11
|
-
limited: "
|
|
10
|
+
available: "white",
|
|
11
|
+
limited: "gray",
|
|
12
12
|
disabled: "gray",
|
|
13
|
-
error: "
|
|
13
|
+
error: "gray",
|
|
14
14
|
};
|
|
15
15
|
const STATUS_ICONS = {
|
|
16
16
|
available: "●",
|
|
17
|
-
limited: "
|
|
18
|
-
disabled: "
|
|
17
|
+
limited: "◐",
|
|
18
|
+
disabled: "○",
|
|
19
19
|
error: "!",
|
|
20
20
|
};
|
|
21
21
|
function StatusBadge({ status, label }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusBadge.js","sourceRoot":"","sources":["../../../src/tui/components/StatusBadge.tsx"],"names":[],"mappings":";;;;;AAsBA,kCAUC;AAhCD,kDAA0B;AAC1B,6BAA2B;AAO3B,MAAM,aAAa,GAA2B;IAC5C,SAAS,EAAE,OAAO;IAClB,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"StatusBadge.js","sourceRoot":"","sources":["../../../src/tui/components/StatusBadge.tsx"],"names":[],"mappings":";;;;;AAsBA,kCAUC;AAhCD,kDAA0B;AAC1B,6BAA2B;AAO3B,MAAM,aAAa,GAA2B;IAC5C,SAAS,EAAE,OAAO;IAClB,OAAO,EAAE,MAAM;IACf,QAAQ,EAAE,MAAM;IAChB,KAAK,EAAE,MAAM;CACd,CAAC;AAEF,MAAM,YAAY,GAA2B;IAC3C,SAAS,EAAE,GAAG;IACd,OAAO,EAAE,GAAG;IACZ,QAAQ,EAAE,GAAG;IACb,KAAK,EAAE,GAAG;CACX,CAAC;AAEF,SAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,KAAK,EAAoB;IAC7D,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC;IAC9C,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC;IACzC,MAAM,IAAI,GAAG,KAAK,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;IAE3C,OAAO,CACL,8BAAC,UAAI,IAAC,KAAK,EAAE,KAAK;QACf,IAAI;;QAAG,IAAI,CACP,CACR,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -7,70 +7,130 @@ interface DashboardViewProps {
|
|
|
7
7
|
selectedIndex: number;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
displayName:
|
|
13
|
-
|
|
10
|
+
// Model definitions with display names
|
|
11
|
+
const MODEL_CONFIGS = [
|
|
12
|
+
{ key: "claude", displayName: "Claude", shortName: "Claude" },
|
|
13
|
+
{ key: "gemini", displayName: "Gemini Pro", shortName: "G Pro" },
|
|
14
|
+
{ key: "gemini-cli:gemini-3-flash-preview", displayName: "Gemini Flash", shortName: "G Flash" },
|
|
15
|
+
{ key: "gemini-cli:gemini-3-pro-preview", displayName: "Gemini Pro", shortName: "G Pro" },
|
|
16
|
+
{ key: "gemini-cli:gemini-2.5-pro", displayName: "G 2.5 Pro", shortName: "G2.5P" },
|
|
17
|
+
{ key: "gemini-cli:imagen-3", displayName: "Imagen 3", shortName: "Img3" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
interface ModelStatus {
|
|
21
|
+
available: boolean;
|
|
14
22
|
timeRemaining: string;
|
|
23
|
+
percentage: number;
|
|
24
|
+
resetTime: number;
|
|
15
25
|
}
|
|
16
26
|
|
|
17
27
|
function formatTimeRemaining(resetTime: number): string {
|
|
18
28
|
const now = Date.now();
|
|
19
|
-
if (resetTime <= now) return "";
|
|
29
|
+
if (resetTime <= now) return "0h 0m";
|
|
20
30
|
|
|
21
31
|
const remaining = resetTime - now;
|
|
22
32
|
const hours = Math.floor(remaining / 3600000);
|
|
23
33
|
const minutes = Math.floor((remaining % 3600000) / 60000);
|
|
24
34
|
|
|
25
|
-
if (hours
|
|
35
|
+
if (hours >= 24) {
|
|
26
36
|
const days = Math.floor(hours / 24);
|
|
27
37
|
return `${days}d ${hours % 24}h`;
|
|
28
38
|
}
|
|
29
|
-
|
|
30
|
-
return `${hours}h ${minutes}m`;
|
|
31
|
-
}
|
|
32
|
-
return `${minutes}m`;
|
|
39
|
+
return `${hours}h ${minutes}m`;
|
|
33
40
|
}
|
|
34
41
|
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
function calculatePercentage(resetTime: number): number {
|
|
43
|
+
const now = Date.now();
|
|
44
|
+
if (resetTime <= now) return 100;
|
|
45
|
+
|
|
46
|
+
const remaining = resetTime - now;
|
|
47
|
+
const maxTime = 5 * 3600000; // 5 hours as max reference
|
|
48
|
+
const elapsed = maxTime - remaining;
|
|
49
|
+
const pct = Math.max(0, Math.min(100, Math.round((elapsed / maxTime) * 100)));
|
|
50
|
+
return pct;
|
|
42
51
|
}
|
|
43
52
|
|
|
44
|
-
function
|
|
53
|
+
function getModelStatus(account: Account, modelKey: string): ModelStatus {
|
|
45
54
|
const now = Date.now();
|
|
46
55
|
const rateLimits = account.rateLimitResetTimes || {};
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
timeRemaining:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
// Check if model is rate limited
|
|
58
|
+
const resetTime = rateLimits[modelKey] || 0;
|
|
59
|
+
|
|
60
|
+
if (resetTime <= now) {
|
|
61
|
+
return {
|
|
62
|
+
available: true,
|
|
63
|
+
timeRemaining: "0h 0m",
|
|
64
|
+
percentage: 100,
|
|
65
|
+
resetTime: 0,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
available: false,
|
|
71
|
+
timeRemaining: formatTimeRemaining(resetTime),
|
|
72
|
+
percentage: calculatePercentage(resetTime),
|
|
73
|
+
resetTime,
|
|
74
|
+
};
|
|
57
75
|
}
|
|
58
76
|
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
indicator: string;
|
|
63
|
-
} {
|
|
64
|
-
if (account.enabled === false) {
|
|
65
|
-
return { status: "disabled", statusColor: "gray", indicator: "○" };
|
|
66
|
-
}
|
|
77
|
+
function getActiveModels(accounts: Account[]): typeof MODEL_CONFIGS {
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const activeKeys = new Set<string>();
|
|
67
80
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
// Collect all model keys that have rate limit data
|
|
82
|
+
accounts.forEach(acc => {
|
|
83
|
+
if (acc.rateLimitResetTimes) {
|
|
84
|
+
Object.keys(acc.rateLimitResetTimes).forEach(key => {
|
|
85
|
+
activeKeys.add(key);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Return models that are in our config OR have rate limit data
|
|
91
|
+
const models = MODEL_CONFIGS.filter(m => activeKeys.has(m.key));
|
|
92
|
+
|
|
93
|
+
// If no models found, show default ones
|
|
94
|
+
if (models.length === 0) {
|
|
95
|
+
return MODEL_CONFIGS.slice(0, 2); // Claude and Gemini
|
|
71
96
|
}
|
|
72
97
|
|
|
73
|
-
return
|
|
98
|
+
return models;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Progress bar component using text
|
|
102
|
+
function ProgressBar({ percentage, width = 8 }: { percentage: number; width?: number }) {
|
|
103
|
+
const filled = Math.round((percentage / 100) * width);
|
|
104
|
+
const empty = width - filled;
|
|
105
|
+
|
|
106
|
+
// Color based on percentage
|
|
107
|
+
const color = percentage === 100 ? "white" : percentage >= 50 ? "gray" : "gray";
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<Text color={color}>
|
|
111
|
+
{"█".repeat(filled)}
|
|
112
|
+
<Text dimColor>{"░".repeat(empty)}</Text>
|
|
113
|
+
</Text>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Model cell component
|
|
118
|
+
function ModelCell({ status, width = 18 }: { status: ModelStatus; width?: number }) {
|
|
119
|
+
return (
|
|
120
|
+
<Box width={width}>
|
|
121
|
+
<Box width={8}>
|
|
122
|
+
<ProgressBar percentage={status.percentage} width={6} />
|
|
123
|
+
</Box>
|
|
124
|
+
<Box width={6}>
|
|
125
|
+
<Text dimColor>{status.timeRemaining.padStart(5)}</Text>
|
|
126
|
+
</Box>
|
|
127
|
+
<Box width={4}>
|
|
128
|
+
<Text color={status.percentage === 100 ? "white" : "gray"}>
|
|
129
|
+
{String(status.percentage).padStart(3)}%
|
|
130
|
+
</Text>
|
|
131
|
+
</Box>
|
|
132
|
+
</Box>
|
|
133
|
+
);
|
|
74
134
|
}
|
|
75
135
|
|
|
76
136
|
export function DashboardView({ accounts, selectedIndex }: DashboardViewProps) {
|
|
@@ -83,98 +143,124 @@ export function DashboardView({ accounts, selectedIndex }: DashboardViewProps) {
|
|
|
83
143
|
);
|
|
84
144
|
}
|
|
85
145
|
|
|
146
|
+
const activeModels = getActiveModels(accounts);
|
|
147
|
+
const modelColumnWidth = 18;
|
|
148
|
+
const emailWidth = 28;
|
|
149
|
+
|
|
86
150
|
return (
|
|
87
151
|
<Box flexDirection="column">
|
|
88
152
|
{/* Header */}
|
|
89
|
-
<Box paddingX={1}
|
|
153
|
+
<Box paddingX={1}>
|
|
90
154
|
<Box width={3}>
|
|
91
155
|
<Text dimColor>{" "}</Text>
|
|
92
156
|
</Box>
|
|
93
|
-
<Box width={
|
|
157
|
+
<Box width={emailWidth}>
|
|
94
158
|
<Text dimColor bold>EMAIL</Text>
|
|
95
159
|
</Box>
|
|
96
|
-
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
160
|
+
{activeModels.map(model => (
|
|
161
|
+
<Box key={model.key} width={modelColumnWidth}>
|
|
162
|
+
<Text dimColor bold>{model.shortName}</Text>
|
|
163
|
+
</Box>
|
|
164
|
+
))}
|
|
165
|
+
</Box>
|
|
166
|
+
|
|
167
|
+
{/* Separator */}
|
|
168
|
+
<Box paddingX={1}>
|
|
169
|
+
<Text dimColor>{"─".repeat(3 + emailWidth + activeModels.length * modelColumnWidth)}</Text>
|
|
102
170
|
</Box>
|
|
103
171
|
|
|
104
|
-
{/*
|
|
172
|
+
{/* Account rows */}
|
|
105
173
|
{accounts.map((account, index) => {
|
|
106
174
|
const isSelected = index === selectedIndex;
|
|
107
|
-
const
|
|
108
|
-
const limits = getAccountRateLimits(account);
|
|
175
|
+
const isDisabled = account.enabled === false;
|
|
109
176
|
|
|
110
177
|
// Truncate email
|
|
111
|
-
const email = account.email.length >
|
|
112
|
-
? account.email.slice(0,
|
|
178
|
+
const email = account.email.length > emailWidth - 3
|
|
179
|
+
? account.email.slice(0, emailWidth - 6) + "..."
|
|
113
180
|
: account.email;
|
|
114
181
|
|
|
115
182
|
return (
|
|
116
183
|
<Box key={account.email} paddingX={1}>
|
|
117
184
|
{/* Selection cursor */}
|
|
118
185
|
<Box width={3}>
|
|
119
|
-
<Text
|
|
186
|
+
<Text bold={isSelected}>
|
|
120
187
|
{isSelected ? "› " : " "}
|
|
121
188
|
</Text>
|
|
122
189
|
</Box>
|
|
123
190
|
|
|
124
191
|
{/* Email */}
|
|
125
|
-
<Box width={
|
|
192
|
+
<Box width={emailWidth}>
|
|
126
193
|
<Text
|
|
127
|
-
color={isSelected ? "cyan" : (account.enabled === false ? "gray" : "white")}
|
|
128
194
|
bold={isSelected}
|
|
129
|
-
dimColor={
|
|
195
|
+
dimColor={isDisabled}
|
|
130
196
|
>
|
|
131
197
|
{email}
|
|
132
198
|
</Text>
|
|
133
199
|
</Box>
|
|
134
200
|
|
|
135
|
-
{/*
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
))}
|
|
156
|
-
</Text>
|
|
157
|
-
)}
|
|
158
|
-
</Box>
|
|
201
|
+
{/* Model columns */}
|
|
202
|
+
{activeModels.map(model => {
|
|
203
|
+
const status = getModelStatus(account, model.key);
|
|
204
|
+
|
|
205
|
+
if (isDisabled) {
|
|
206
|
+
return (
|
|
207
|
+
<Box key={model.key} width={modelColumnWidth}>
|
|
208
|
+
<Text dimColor>── disabled ──</Text>
|
|
209
|
+
</Box>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<ModelCell
|
|
215
|
+
key={model.key}
|
|
216
|
+
status={status}
|
|
217
|
+
width={modelColumnWidth}
|
|
218
|
+
/>
|
|
219
|
+
);
|
|
220
|
+
})}
|
|
159
221
|
</Box>
|
|
160
222
|
);
|
|
161
223
|
})}
|
|
162
224
|
|
|
163
225
|
{/* Summary */}
|
|
164
226
|
<Box paddingX={1} marginTop={1}>
|
|
165
|
-
<Text dimColor>
|
|
166
|
-
─────────────────────────────────────────────────────────────────
|
|
167
|
-
</Text>
|
|
227
|
+
<Text dimColor>{"─".repeat(3 + emailWidth + activeModels.length * modelColumnWidth)}</Text>
|
|
168
228
|
</Box>
|
|
229
|
+
|
|
169
230
|
<Box paddingX={1}>
|
|
170
231
|
<Text dimColor>
|
|
171
|
-
{accounts.length} accounts
|
|
232
|
+
{accounts.length} accounts
|
|
233
|
+
</Text>
|
|
234
|
+
<Text dimColor> │ </Text>
|
|
235
|
+
<Text>
|
|
236
|
+
{accounts.filter(a => {
|
|
237
|
+
if (a.enabled === false) return false;
|
|
238
|
+
const now = Date.now();
|
|
239
|
+
const limits = a.rateLimitResetTimes || {};
|
|
240
|
+
return !Object.values(limits).some(t => t > now);
|
|
241
|
+
}).length}
|
|
242
|
+
</Text>
|
|
243
|
+
<Text dimColor> available</Text>
|
|
244
|
+
<Text dimColor> │ </Text>
|
|
245
|
+
<Text dimColor>
|
|
246
|
+
{accounts.filter(a => {
|
|
247
|
+
if (a.enabled === false) return false;
|
|
248
|
+
const now = Date.now();
|
|
249
|
+
const limits = a.rateLimitResetTimes || {};
|
|
250
|
+
return Object.values(limits).some(t => t > now);
|
|
251
|
+
}).length} limited
|
|
252
|
+
</Text>
|
|
253
|
+
<Text dimColor> │ </Text>
|
|
254
|
+
<Text dimColor>
|
|
255
|
+
{accounts.filter(a => a.enabled === false).length} disabled
|
|
172
256
|
</Text>
|
|
173
257
|
</Box>
|
|
174
258
|
|
|
175
259
|
{/* Legend */}
|
|
176
260
|
<Box paddingX={1} marginTop={1}>
|
|
177
|
-
<Text dimColor
|
|
261
|
+
<Text dimColor>████ 100%</Text>
|
|
262
|
+
<Text dimColor> ░░░░ limited </Text>
|
|
263
|
+
<Text dimColor>── disabled</Text>
|
|
178
264
|
</Box>
|
|
179
265
|
</Box>
|
|
180
266
|
);
|
|
@@ -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
|
+
}
|