opencode-account-manager 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -216
- package/README_VI.md +235 -216
- package/dist/cli.js +83 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/config-store.d.ts +12 -0
- package/dist/core/config-store.d.ts.map +1 -1
- package/dist/core/config-store.js +98 -0
- package/dist/core/config-store.js.map +1 -1
- package/dist/core/health-log.d.ts +9 -0
- package/dist/core/health-log.d.ts.map +1 -0
- package/dist/core/health-log.js +154 -0
- package/dist/core/health-log.js.map +1 -0
- package/dist/core/health-oauth.d.ts +5 -0
- package/dist/core/health-oauth.d.ts.map +1 -0
- package/dist/core/health-oauth.js +147 -0
- package/dist/core/health-oauth.js.map +1 -0
- package/dist/core/health-orchestrator.d.ts +32 -0
- package/dist/core/health-orchestrator.d.ts.map +1 -0
- package/dist/core/health-orchestrator.js +148 -0
- package/dist/core/health-orchestrator.js.map +1 -0
- package/dist/core/health-utils.d.ts +15 -0
- package/dist/core/health-utils.d.ts.map +1 -0
- package/dist/core/health-utils.js +60 -0
- package/dist/core/health-utils.js.map +1 -0
- package/dist/core/paths.d.ts +1 -0
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +4 -0
- package/dist/core/paths.js.map +1 -1
- package/dist/core/types.d.ts +26 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/tui/Dashboard.d.ts.map +1 -1
- package/dist/tui/Dashboard.js +72 -5
- package/dist/tui/Dashboard.js.map +1 -1
- package/dist/tui/components/AccountList.d.ts +5 -3
- package/dist/tui/components/AccountList.d.ts.map +1 -1
- package/dist/tui/components/AccountList.js +9 -3
- package/dist/tui/components/AccountList.js.map +1 -1
- package/dist/tui/components/Box.js +2 -2
- package/dist/tui/components/Box.js.map +1 -1
- package/dist/tui/components/DashboardView.d.ts +3 -2
- package/dist/tui/components/DashboardView.d.ts.map +1 -1
- package/dist/tui/components/DashboardView.js +50 -4
- package/dist/tui/components/DashboardView.js.map +1 -1
- package/dist/tui/components/ExportModal.js +4 -4
- package/dist/tui/components/ExportModal.js.map +1 -1
- package/dist/tui/components/FileBrowser.js +1 -1
- package/dist/tui/components/HealthBadge.d.ts +9 -0
- package/dist/tui/components/HealthBadge.d.ts.map +1 -0
- package/dist/tui/components/HealthBadge.js +56 -0
- package/dist/tui/components/HealthBadge.js.map +1 -0
- package/dist/tui/components/ImportModal.js +4 -4
- package/dist/tui/components/ImportModal.js.map +1 -1
- package/dist/tui/components/PasswordInput.js +2 -2
- package/dist/tui/components/PasswordInput.js.map +1 -1
- package/dist/tui/components/StatusBadge.d.ts +2 -1
- package/dist/tui/components/StatusBadge.d.ts.map +1 -1
- package/dist/tui/components/StatusBadge.js +30 -2
- package/dist/tui/components/StatusBadge.js.map +1 -1
- package/dist/tui/components/index.d.ts +1 -0
- package/dist/tui/components/index.d.ts.map +1 -1
- package/dist/tui/components/index.js +3 -1
- package/dist/tui/components/index.js.map +1 -1
- package/docs/BLUEPRINT.md +476 -476
- package/docs/ROADMAP.md +125 -107
- package/package.json +36 -36
- package/src/cli.ts +139 -38
- package/src/core/config-store.ts +278 -171
- package/src/core/crypto.ts +162 -162
- package/src/core/health-log.ts +173 -0
- package/src/core/health-oauth.ts +190 -0
- package/src/core/health-orchestrator.ts +224 -0
- package/src/core/importers/amExport.ts +177 -177
- package/src/core/opencode-config.ts +217 -217
- package/src/core/paths.ts +10 -6
- package/src/core/types.ts +193 -147
- package/src/tui/Dashboard.tsx +557 -478
- package/src/tui/components/AccountList.tsx +122 -104
- package/src/tui/components/ActionPalette.tsx +117 -117
- package/src/tui/components/Box.tsx +2 -2
- package/src/tui/components/DashboardView.tsx +285 -230
- package/src/tui/components/ExportModal.tsx +255 -255
- package/src/tui/components/FileBrowser.tsx +393 -393
- package/src/tui/components/Header.tsx +26 -26
- package/src/tui/components/HealthBadge.tsx +64 -0
- package/src/tui/components/ImportModal.tsx +334 -334
- package/src/tui/components/McpServerList.tsx +67 -67
- package/src/tui/components/Menu.tsx +61 -61
- package/src/tui/components/PasswordInput.tsx +159 -159
- package/src/tui/components/ProviderList.tsx +59 -59
- package/src/tui/components/SectionBox.tsx +35 -35
- package/src/tui/components/StatsRow.tsx +33 -33
- package/src/tui/components/StatusBadge.tsx +36 -3
- package/src/tui/components/index.ts +15 -14
- package/test-minimal.js +26 -26
- package/test-with-accounts.js +58 -58
package/src/tui/Dashboard.tsx
CHANGED
|
@@ -1,89 +1,92 @@
|
|
|
1
|
-
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { Box, Text, useApp, useInput } from "ink";
|
|
3
|
-
import {
|
|
4
|
-
Header,
|
|
5
|
-
StatsRow,
|
|
6
|
-
AccountList,
|
|
7
|
-
ProviderList,
|
|
8
|
-
McpServerList,
|
|
9
|
-
SectionBox,
|
|
10
|
-
ExportModal,
|
|
11
|
-
ImportModal,
|
|
12
|
-
ActionPalette,
|
|
13
|
-
PaletteAction,
|
|
14
|
-
DashboardView,
|
|
15
|
-
} from "./components";
|
|
16
|
-
import {
|
|
17
|
-
readPluginAccountsFile,
|
|
18
|
-
createEmptyPluginAccountsFile,
|
|
19
|
-
summarizeAccounts,
|
|
20
|
-
mergeAccounts,
|
|
21
|
-
writePluginAccountsFile,
|
|
22
|
-
} from "../core/accounts";
|
|
23
|
-
import { getPluginAccountsPath, getAmFolderPath } from "../core/paths";
|
|
24
|
-
import { Account, PluginAccountsFile } from "../core/types";
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
3
|
+
import {
|
|
4
|
+
Header,
|
|
5
|
+
StatsRow,
|
|
6
|
+
AccountList,
|
|
7
|
+
ProviderList,
|
|
8
|
+
McpServerList,
|
|
9
|
+
SectionBox,
|
|
10
|
+
ExportModal,
|
|
11
|
+
ImportModal,
|
|
12
|
+
ActionPalette,
|
|
13
|
+
PaletteAction,
|
|
14
|
+
DashboardView,
|
|
15
|
+
} from "./components";
|
|
16
|
+
import {
|
|
17
|
+
readPluginAccountsFile,
|
|
18
|
+
createEmptyPluginAccountsFile,
|
|
19
|
+
summarizeAccounts,
|
|
20
|
+
mergeAccounts,
|
|
21
|
+
writePluginAccountsFile,
|
|
22
|
+
} from "../core/accounts";
|
|
23
|
+
import { getPluginAccountsPath, getAmFolderPath } from "../core/paths";
|
|
24
|
+
import { Account, AccountHealthResult, PluginAccountsFile } from "../core/types";
|
|
25
25
|
import { importFromAmFolder } from "../core/importers/amJson";
|
|
26
26
|
import {
|
|
27
27
|
parseOpencodeInfo,
|
|
28
28
|
getConfigSummary,
|
|
29
29
|
OpencodeInfo,
|
|
30
30
|
} from "../core/opencode-config";
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
31
|
+
import { checkAccountsHealth } from "../core/health-orchestrator";
|
|
32
|
+
import { getHealthCache, normalizeHealthKey } from "../core/config-store";
|
|
33
|
+
|
|
34
|
+
interface DashboardProps {
|
|
35
|
+
pluginPath?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type ModalType = "none" | "export" | "import" | "export-selected" | "palette";
|
|
39
|
+
type MainTab = "dashboard" | "settings";
|
|
40
|
+
|
|
41
|
+
function safeReadPluginFile(pluginPath: string): PluginAccountsFile {
|
|
42
|
+
try {
|
|
43
|
+
return readPluginAccountsFile(pluginPath);
|
|
44
|
+
} catch {
|
|
45
|
+
return createEmptyPluginAccountsFile();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function Dashboard({ pluginPath }: DashboardProps) {
|
|
50
|
+
const { exit } = useApp();
|
|
51
|
+
const resolvedPath = getPluginAccountsPath(pluginPath);
|
|
52
|
+
|
|
53
|
+
// OpenCode config state
|
|
54
|
+
const [opencodeInfo, setOpencodeInfo] = useState<OpencodeInfo | null>(null);
|
|
55
|
+
|
|
56
|
+
// Plugin accounts state
|
|
55
57
|
const [accounts, setAccounts] = useState<Account[]>([]);
|
|
56
58
|
const [summary, setSummary] = useState({ total: 0, available: 0, limited: 0 });
|
|
57
59
|
const [message, setMessage] = useState<string | null>(null);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const [
|
|
68
|
-
const [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const [
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
60
|
+
const [healthResults, setHealthResults] = useState<Record<string, AccountHealthResult>>({});
|
|
61
|
+
|
|
62
|
+
// Main tab state
|
|
63
|
+
const [activeTab, setActiveTab] = useState<MainTab>("dashboard");
|
|
64
|
+
|
|
65
|
+
// Dashboard tab state
|
|
66
|
+
const [dashboardIndex, setDashboardIndex] = useState(0);
|
|
67
|
+
|
|
68
|
+
// Settings tab state
|
|
69
|
+
const [expandedSection, setExpandedSection] = useState<"providers" | "accounts" | "mcp">("accounts");
|
|
70
|
+
const [settingsNavIndex, setSettingsNavIndex] = useState(0);
|
|
71
|
+
const [checkedEmails, setCheckedEmails] = useState<Set<string>>(new Set());
|
|
72
|
+
|
|
73
|
+
// Modal state
|
|
74
|
+
const [activeModal, setActiveModal] = useState<ModalType>("none");
|
|
75
|
+
|
|
76
|
+
// Loading state
|
|
77
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
78
|
+
const [loadingStep, setLoadingStep] = useState("");
|
|
79
|
+
|
|
80
|
+
const showMessage = (msg: string, duration = 3000) => {
|
|
81
|
+
setMessage(msg);
|
|
82
|
+
setTimeout(() => setMessage(null), duration);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const loadOpencodeConfig = () => {
|
|
86
|
+
const info = parseOpencodeInfo();
|
|
87
|
+
setOpencodeInfo(info);
|
|
88
|
+
};
|
|
89
|
+
|
|
87
90
|
const loadAccounts = () => {
|
|
88
91
|
const file = safeReadPluginFile(resolvedPath);
|
|
89
92
|
setAccounts(file.accounts);
|
|
@@ -91,6 +94,10 @@ export function Dashboard({ pluginPath }: DashboardProps) {
|
|
|
91
94
|
setCheckedEmails(new Set());
|
|
92
95
|
};
|
|
93
96
|
|
|
97
|
+
const loadHealthCache = () => {
|
|
98
|
+
setHealthResults(getHealthCache());
|
|
99
|
+
};
|
|
100
|
+
|
|
94
101
|
const refresh = async () => {
|
|
95
102
|
setIsLoading(true);
|
|
96
103
|
|
|
@@ -101,6 +108,7 @@ export function Dashboard({ pluginPath }: DashboardProps) {
|
|
|
101
108
|
setLoadingStep("Loading accounts...");
|
|
102
109
|
await new Promise(r => setTimeout(r, 100));
|
|
103
110
|
loadAccounts();
|
|
111
|
+
loadHealthCache();
|
|
104
112
|
|
|
105
113
|
setLoadingStep("Done!");
|
|
106
114
|
await new Promise(r => setTimeout(r, 300));
|
|
@@ -110,406 +118,474 @@ export function Dashboard({ pluginPath }: DashboardProps) {
|
|
|
110
118
|
showMessage("Refreshed", 2000);
|
|
111
119
|
};
|
|
112
120
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
const runHealthCheck = async (emails?: string[]) => {
|
|
122
|
+
if (accounts.length === 0) {
|
|
123
|
+
showMessage("No accounts to check", 2000);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
117
126
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
setIsLoading(true);
|
|
128
|
+
setLoadingStep("Starting health check...");
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const result = await checkAccountsHealth(accounts, {
|
|
132
|
+
emails,
|
|
133
|
+
includeLogs: true,
|
|
134
|
+
force: false,
|
|
135
|
+
onProgress: (current, total, msg) => {
|
|
136
|
+
setLoadingStep(`[${current}/${total}] ${msg}`);
|
|
137
|
+
},
|
|
138
|
+
});
|
|
124
139
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
setHealthResults((prev) => {
|
|
141
|
+
const next = { ...prev };
|
|
142
|
+
for (const item of result.items) {
|
|
143
|
+
next[normalizeHealthKey(item.email)] = item.result;
|
|
144
|
+
}
|
|
145
|
+
return next;
|
|
128
146
|
});
|
|
129
|
-
}
|
|
130
147
|
|
|
131
|
-
|
|
132
|
-
|
|
148
|
+
showMessage(
|
|
149
|
+
`Health check: ${result.counts.checked} checked, ${result.counts.cached} cached, ${result.counts.skipped} skipped`,
|
|
150
|
+
5000
|
|
151
|
+
);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
showMessage("Health check failed", 3000);
|
|
154
|
+
} finally {
|
|
155
|
+
setIsLoading(false);
|
|
156
|
+
setLoadingStep("");
|
|
157
|
+
}
|
|
133
158
|
};
|
|
134
159
|
|
|
135
|
-
const
|
|
136
|
-
|
|
160
|
+
const handleCheckHealthSelected = () => {
|
|
161
|
+
if (checkedEmails.size === 0) {
|
|
162
|
+
showMessage("No accounts selected", 2000);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
runHealthCheck(Array.from(checkedEmails));
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
loadOpencodeConfig();
|
|
170
|
+
loadAccounts();
|
|
171
|
+
loadHealthCache();
|
|
172
|
+
}, []);
|
|
173
|
+
|
|
174
|
+
// Build settings navigation items
|
|
175
|
+
const buildSettingsNavItems = () => {
|
|
176
|
+
const items: Array<{ type: "section" | "account"; id?: string; index?: number; email?: string }> = [
|
|
177
|
+
{ type: "section", id: "providers" },
|
|
178
|
+
{ type: "section", id: "accounts" },
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
if (expandedSection === "accounts") {
|
|
182
|
+
accounts.forEach((acc, index) => {
|
|
183
|
+
items.push({ type: "account", index, email: acc.email });
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
items.push({ type: "section", id: "mcp" });
|
|
188
|
+
return items;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const settingsNavItems = buildSettingsNavItems();
|
|
192
|
+
|
|
137
193
|
// Palette actions
|
|
138
194
|
const paletteActions: PaletteAction[] = [
|
|
139
195
|
{ id: "refresh", label: "Refresh", shortcut: "R" },
|
|
196
|
+
{ id: "check-health-all", label: "Check Account Health (All)", shortcut: "H" },
|
|
140
197
|
{ id: "export", label: "Export All Accounts", shortcut: "E" },
|
|
141
198
|
{ id: "import", label: "Import from File", shortcut: "I" },
|
|
142
199
|
{ id: "import-am", label: "Import from Antigravity Manager", shortcut: "A" },
|
|
143
200
|
...(checkedEmails.size > 0 ? [
|
|
201
|
+
{ id: "check-health-selected", label: `Check Health Selected (${checkedEmails.size})`, shortcut: "Shift+H" },
|
|
144
202
|
{ id: "export-selected", label: `Export Selected (${checkedEmails.size})`, shortcut: "X" },
|
|
145
203
|
{ id: "enable-selected", label: `Enable Selected (${checkedEmails.size})` },
|
|
146
204
|
{ id: "disable-selected", label: `Disable Selected (${checkedEmails.size})` },
|
|
147
205
|
{ id: "delete-selected", label: `Delete Selected (${checkedEmails.size})`, shortcut: "Del" },
|
|
148
|
-
{ id: "clear-selection", label: "Clear Selection", shortcut: "N" },
|
|
149
|
-
] : []),
|
|
150
|
-
{ id: "select-all", label: "Select All Accounts", shortcut: "Ctrl+A" },
|
|
151
|
-
{ id: "quit", label: "Quit", shortcut: "Q" },
|
|
152
|
-
];
|
|
153
|
-
|
|
154
|
-
// Keyboard navigation
|
|
155
|
-
useInput((input, key) => {
|
|
156
|
-
if (activeModal === "palette") return;
|
|
157
|
-
if (activeModal !== "none") return;
|
|
158
|
-
|
|
159
|
-
// Tab to switch between main tabs
|
|
160
|
-
if (key.tab) {
|
|
161
|
-
setActiveTab(prev => prev === "dashboard" ? "settings" : "dashboard");
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Open palette with P
|
|
166
|
-
if (input === "p" || input === "P") {
|
|
167
|
-
setActiveModal("palette");
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Quick shortcuts
|
|
172
|
-
if (input === "q" || input === "Q") {
|
|
173
|
-
exit();
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
206
|
+
{ id: "clear-selection", label: "Clear Selection", shortcut: "N" },
|
|
207
|
+
] : []),
|
|
208
|
+
{ id: "select-all", label: "Select All Accounts", shortcut: "Ctrl+A" },
|
|
209
|
+
{ id: "quit", label: "Quit", shortcut: "Q" },
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
// Keyboard navigation
|
|
213
|
+
useInput((input, key) => {
|
|
214
|
+
if (activeModal === "palette") return;
|
|
215
|
+
if (activeModal !== "none") return;
|
|
216
|
+
|
|
217
|
+
// Tab to switch between main tabs
|
|
218
|
+
if (key.tab) {
|
|
219
|
+
setActiveTab(prev => prev === "dashboard" ? "settings" : "dashboard");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Open palette with P
|
|
224
|
+
if (input === "p" || input === "P") {
|
|
225
|
+
setActiveModal("palette");
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Quick shortcuts
|
|
230
|
+
if (input === "q" || input === "Q") {
|
|
231
|
+
exit();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
176
234
|
if (input === "r" || input === "R") {
|
|
177
235
|
refresh();
|
|
178
236
|
return;
|
|
179
237
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (activeTab === "dashboard") {
|
|
183
|
-
// Dashboard tab navigation
|
|
184
|
-
if (key.upArrow) {
|
|
185
|
-
setDashboardIndex(prev => Math.max(0, prev - 1));
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
if (key.downArrow) {
|
|
189
|
-
setDashboardIndex(prev => Math.min(accounts.length - 1, prev + 1));
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
// Space/Enter to toggle selection
|
|
193
|
-
if (input === " " || key.return) {
|
|
194
|
-
const email = accounts[dashboardIndex]?.email;
|
|
195
|
-
if (email) {
|
|
196
|
-
setCheckedEmails(prev => {
|
|
197
|
-
const next = new Set(prev);
|
|
198
|
-
if (next.has(email)) {
|
|
199
|
-
next.delete(email);
|
|
200
|
-
} else {
|
|
201
|
-
next.add(email);
|
|
202
|
-
}
|
|
203
|
-
return next;
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
} else {
|
|
209
|
-
// Settings tab navigation
|
|
210
|
-
if (key.upArrow) {
|
|
211
|
-
setSettingsNavIndex(prev => Math.max(0, prev - 1));
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
if (key.downArrow) {
|
|
215
|
-
setSettingsNavIndex(prev => Math.min(settingsNavItems.length - 1, prev + 1));
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
if (key.return) {
|
|
219
|
-
const currentItem = settingsNavItems[settingsNavIndex];
|
|
220
|
-
if (currentItem?.type === "section" && currentItem.id) {
|
|
221
|
-
setExpandedSection(currentItem.id as "providers" | "accounts" | "mcp");
|
|
222
|
-
} else if (currentItem?.type === "account" && currentItem.email) {
|
|
223
|
-
const email = currentItem.email;
|
|
224
|
-
setCheckedEmails(prev => {
|
|
225
|
-
const next = new Set(prev);
|
|
226
|
-
if (next.has(email)) {
|
|
227
|
-
next.delete(email);
|
|
228
|
-
} else {
|
|
229
|
-
next.add(email);
|
|
230
|
-
}
|
|
231
|
-
return next;
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
if (input === " ") {
|
|
237
|
-
const currentItem = settingsNavItems[settingsNavIndex];
|
|
238
|
-
if (currentItem?.type === "account" && currentItem.email) {
|
|
239
|
-
const email = currentItem.email;
|
|
240
|
-
setCheckedEmails(prev => {
|
|
241
|
-
const next = new Set(prev);
|
|
242
|
-
if (next.has(email)) {
|
|
243
|
-
next.delete(email);
|
|
244
|
-
} else {
|
|
245
|
-
next.add(email);
|
|
246
|
-
}
|
|
247
|
-
return next;
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Escape to clear selection
|
|
255
|
-
if (key.escape) {
|
|
256
|
-
if (checkedEmails.size > 0) {
|
|
257
|
-
setCheckedEmails(new Set());
|
|
258
|
-
showMessage("Selection cleared", 1500);
|
|
259
|
-
}
|
|
238
|
+
if (input === "h" || input === "H") {
|
|
239
|
+
runHealthCheck();
|
|
260
240
|
return;
|
|
261
241
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
242
|
+
|
|
243
|
+
// Tab-specific navigation
|
|
244
|
+
if (activeTab === "dashboard") {
|
|
245
|
+
// Dashboard tab navigation
|
|
246
|
+
if (key.upArrow) {
|
|
247
|
+
setDashboardIndex(prev => Math.max(0, prev - 1));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (key.downArrow) {
|
|
251
|
+
setDashboardIndex(prev => Math.min(accounts.length - 1, prev + 1));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// Space/Enter to toggle selection
|
|
255
|
+
if (input === " " || key.return) {
|
|
256
|
+
const email = accounts[dashboardIndex]?.email;
|
|
257
|
+
if (email) {
|
|
258
|
+
setCheckedEmails(prev => {
|
|
259
|
+
const next = new Set(prev);
|
|
260
|
+
if (next.has(email)) {
|
|
261
|
+
next.delete(email);
|
|
262
|
+
} else {
|
|
263
|
+
next.add(email);
|
|
264
|
+
}
|
|
265
|
+
return next;
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
// Settings tab navigation
|
|
272
|
+
if (key.upArrow) {
|
|
273
|
+
setSettingsNavIndex(prev => Math.max(0, prev - 1));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (key.downArrow) {
|
|
277
|
+
setSettingsNavIndex(prev => Math.min(settingsNavItems.length - 1, prev + 1));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (key.return) {
|
|
281
|
+
const currentItem = settingsNavItems[settingsNavIndex];
|
|
282
|
+
if (currentItem?.type === "section" && currentItem.id) {
|
|
283
|
+
setExpandedSection(currentItem.id as "providers" | "accounts" | "mcp");
|
|
284
|
+
} else if (currentItem?.type === "account" && currentItem.email) {
|
|
285
|
+
const email = currentItem.email;
|
|
286
|
+
setCheckedEmails(prev => {
|
|
287
|
+
const next = new Set(prev);
|
|
288
|
+
if (next.has(email)) {
|
|
289
|
+
next.delete(email);
|
|
290
|
+
} else {
|
|
291
|
+
next.add(email);
|
|
292
|
+
}
|
|
293
|
+
return next;
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (input === " ") {
|
|
299
|
+
const currentItem = settingsNavItems[settingsNavIndex];
|
|
300
|
+
if (currentItem?.type === "account" && currentItem.email) {
|
|
301
|
+
const email = currentItem.email;
|
|
302
|
+
setCheckedEmails(prev => {
|
|
303
|
+
const next = new Set(prev);
|
|
304
|
+
if (next.has(email)) {
|
|
305
|
+
next.delete(email);
|
|
306
|
+
} else {
|
|
307
|
+
next.add(email);
|
|
308
|
+
}
|
|
309
|
+
return next;
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Escape to clear selection
|
|
317
|
+
if (key.escape) {
|
|
318
|
+
if (checkedEmails.size > 0) {
|
|
319
|
+
setCheckedEmails(new Set());
|
|
320
|
+
showMessage("Selection cleared", 1500);
|
|
321
|
+
}
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Handle palette action
|
|
327
|
+
const handlePaletteAction = (actionId: string) => {
|
|
328
|
+
setActiveModal("none");
|
|
329
|
+
|
|
268
330
|
switch (actionId) {
|
|
269
331
|
case "refresh":
|
|
270
332
|
refresh();
|
|
271
333
|
break;
|
|
272
|
-
case "
|
|
273
|
-
|
|
334
|
+
case "check-health-all":
|
|
335
|
+
runHealthCheck();
|
|
274
336
|
break;
|
|
275
|
-
case "
|
|
276
|
-
|
|
337
|
+
case "check-health-selected":
|
|
338
|
+
handleCheckHealthSelected();
|
|
277
339
|
break;
|
|
278
|
-
case "
|
|
279
|
-
|
|
280
|
-
break;
|
|
281
|
-
case "export-selected":
|
|
282
|
-
if (checkedEmails.size > 0) {
|
|
283
|
-
setActiveModal("export-selected");
|
|
284
|
-
} else {
|
|
285
|
-
showMessage("No accounts selected", 2000);
|
|
286
|
-
}
|
|
287
|
-
break;
|
|
288
|
-
case "enable-selected":
|
|
289
|
-
handleEnableSelected();
|
|
290
|
-
break;
|
|
291
|
-
case "disable-selected":
|
|
292
|
-
handleDisableSelected();
|
|
293
|
-
break;
|
|
294
|
-
case "delete-selected":
|
|
295
|
-
handleDeleteSelected();
|
|
296
|
-
break;
|
|
297
|
-
case "select-all":
|
|
298
|
-
setCheckedEmails(new Set(accounts.map(a => a.email)));
|
|
299
|
-
showMessage(`Selected ${accounts.length} accounts`, 2000);
|
|
300
|
-
break;
|
|
301
|
-
case "clear-selection":
|
|
302
|
-
setCheckedEmails(new Set());
|
|
303
|
-
showMessage("Selection cleared", 1500);
|
|
304
|
-
break;
|
|
305
|
-
case "quit":
|
|
306
|
-
exit();
|
|
340
|
+
case "export":
|
|
341
|
+
setActiveModal("export");
|
|
307
342
|
break;
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
showMessage(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
return
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
<
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
343
|
+
case "import":
|
|
344
|
+
setActiveModal("import");
|
|
345
|
+
break;
|
|
346
|
+
case "import-am":
|
|
347
|
+
handleImportAM();
|
|
348
|
+
break;
|
|
349
|
+
case "export-selected":
|
|
350
|
+
if (checkedEmails.size > 0) {
|
|
351
|
+
setActiveModal("export-selected");
|
|
352
|
+
} else {
|
|
353
|
+
showMessage("No accounts selected", 2000);
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
356
|
+
case "enable-selected":
|
|
357
|
+
handleEnableSelected();
|
|
358
|
+
break;
|
|
359
|
+
case "disable-selected":
|
|
360
|
+
handleDisableSelected();
|
|
361
|
+
break;
|
|
362
|
+
case "delete-selected":
|
|
363
|
+
handleDeleteSelected();
|
|
364
|
+
break;
|
|
365
|
+
case "select-all":
|
|
366
|
+
setCheckedEmails(new Set(accounts.map(a => a.email)));
|
|
367
|
+
showMessage(`Selected ${accounts.length} accounts`, 2000);
|
|
368
|
+
break;
|
|
369
|
+
case "clear-selection":
|
|
370
|
+
setCheckedEmails(new Set());
|
|
371
|
+
showMessage("Selection cleared", 1500);
|
|
372
|
+
break;
|
|
373
|
+
case "quit":
|
|
374
|
+
exit();
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// Export completion handler
|
|
380
|
+
const handleExportComplete = (filePath: string) => {
|
|
381
|
+
setActiveModal("none");
|
|
382
|
+
showMessage(`Exported to ${filePath}`, 4000);
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// Import completion handler
|
|
386
|
+
const handleImportComplete = (importedAccounts: Account[], newCount: number, overwrittenCount: number) => {
|
|
387
|
+
const file = safeReadPluginFile(resolvedPath);
|
|
388
|
+
const merged = mergeAccounts(file, importedAccounts, "merge");
|
|
389
|
+
writePluginAccountsFile(pluginPath, merged);
|
|
390
|
+
|
|
391
|
+
setActiveModal("none");
|
|
392
|
+
loadAccounts();
|
|
393
|
+
showMessage(`Imported: ${newCount} new, ${overwrittenCount} updated`, 4000);
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const handleImportAM = () => {
|
|
397
|
+
const amPath = getAmFolderPath();
|
|
398
|
+
const result = importFromAmFolder(amPath);
|
|
399
|
+
|
|
400
|
+
if (result.errors.length > 0) {
|
|
401
|
+
showMessage(`Error: ${result.errors[0]}`, 5000);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (result.accounts.length === 0) {
|
|
406
|
+
showMessage(`No accounts found in AM (${result.skipped.length} skipped)`, 4000);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const existingFile = safeReadPluginFile(resolvedPath);
|
|
411
|
+
const merged = mergeAccounts(existingFile, result.accounts, "merge");
|
|
412
|
+
writePluginAccountsFile(pluginPath, merged);
|
|
413
|
+
|
|
414
|
+
const added = merged.accounts.length - existingFile.accounts.length;
|
|
415
|
+
showMessage(
|
|
416
|
+
`Imported from AM: ${result.accounts.length} found, ${added} new`,
|
|
417
|
+
5000
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
loadAccounts();
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const handleEnableSelected = () => {
|
|
424
|
+
if (checkedEmails.size === 0) {
|
|
425
|
+
showMessage("No accounts selected", 2000);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const file = safeReadPluginFile(resolvedPath);
|
|
430
|
+
let count = 0;
|
|
431
|
+
|
|
432
|
+
file.accounts = file.accounts.map(acc => {
|
|
433
|
+
if (checkedEmails.has(acc.email)) {
|
|
434
|
+
count++;
|
|
435
|
+
return { ...acc, enabled: true };
|
|
436
|
+
}
|
|
437
|
+
return acc;
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
writePluginAccountsFile(pluginPath, file);
|
|
441
|
+
showMessage(`Enabled ${count} accounts`, 3000);
|
|
442
|
+
loadAccounts();
|
|
443
|
+
setCheckedEmails(new Set());
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const handleDisableSelected = () => {
|
|
447
|
+
if (checkedEmails.size === 0) {
|
|
448
|
+
showMessage("No accounts selected", 2000);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const file = safeReadPluginFile(resolvedPath);
|
|
453
|
+
let count = 0;
|
|
454
|
+
|
|
455
|
+
file.accounts = file.accounts.map(acc => {
|
|
456
|
+
if (checkedEmails.has(acc.email)) {
|
|
457
|
+
count++;
|
|
458
|
+
return { ...acc, enabled: false };
|
|
459
|
+
}
|
|
460
|
+
return acc;
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
writePluginAccountsFile(pluginPath, file);
|
|
464
|
+
showMessage(`Disabled ${count} accounts`, 3000);
|
|
465
|
+
loadAccounts();
|
|
466
|
+
setCheckedEmails(new Set());
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const handleDeleteSelected = () => {
|
|
470
|
+
if (checkedEmails.size === 0) {
|
|
471
|
+
showMessage("No accounts selected", 2000);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const file = safeReadPluginFile(resolvedPath);
|
|
476
|
+
const beforeCount = file.accounts.length;
|
|
477
|
+
|
|
478
|
+
file.accounts = file.accounts.filter(acc => !checkedEmails.has(acc.email));
|
|
479
|
+
const deletedCount = beforeCount - file.accounts.length;
|
|
480
|
+
|
|
481
|
+
writePluginAccountsFile(pluginPath, file);
|
|
482
|
+
showMessage(`Deleted ${deletedCount} accounts`, 3000);
|
|
483
|
+
loadAccounts();
|
|
484
|
+
setCheckedEmails(new Set());
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// Calculate stats
|
|
488
|
+
const configSummary = opencodeInfo ? getConfigSummary(opencodeInfo) : null;
|
|
489
|
+
|
|
490
|
+
// Get accounts to export
|
|
491
|
+
const getAccountsForExport = (): Account[] => {
|
|
492
|
+
if (activeModal === "export-selected") {
|
|
493
|
+
return accounts.filter(acc => checkedEmails.has(acc.email));
|
|
494
|
+
}
|
|
495
|
+
return accounts;
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
// Settings nav helpers
|
|
499
|
+
const currentSettingsItem = settingsNavItems[settingsNavIndex];
|
|
500
|
+
const isOnSection = (id: string) => currentSettingsItem?.type === "section" && currentSettingsItem.id === id;
|
|
501
|
+
|
|
502
|
+
// Render modals
|
|
503
|
+
if (activeModal === "export" || activeModal === "export-selected") {
|
|
504
|
+
return (
|
|
505
|
+
<Box flexDirection="column" padding={1}>
|
|
506
|
+
<ExportModal
|
|
507
|
+
accounts={getAccountsForExport()}
|
|
508
|
+
onComplete={handleExportComplete}
|
|
509
|
+
onCancel={() => setActiveModal("none")}
|
|
510
|
+
/>
|
|
511
|
+
</Box>
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (activeModal === "import") {
|
|
516
|
+
return (
|
|
517
|
+
<Box flexDirection="column" padding={1}>
|
|
518
|
+
<ImportModal
|
|
519
|
+
existingAccounts={accounts}
|
|
520
|
+
onComplete={handleImportComplete}
|
|
521
|
+
onCancel={() => setActiveModal("none")}
|
|
522
|
+
/>
|
|
523
|
+
</Box>
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return (
|
|
528
|
+
<Box flexDirection="column" padding={1}>
|
|
529
|
+
<Header title="OpenCode Account Manager" subtitle={activeTab === "dashboard" ? "Dashboard" : "Settings"} />
|
|
530
|
+
|
|
531
|
+
{/* Tab bar */}
|
|
532
|
+
<Box marginBottom={1}>
|
|
533
|
+
<Text
|
|
534
|
+
inverse={activeTab === "dashboard"}
|
|
535
|
+
bold={activeTab === "dashboard"}
|
|
536
|
+
>
|
|
537
|
+
{" DASHBOARD "}
|
|
538
|
+
</Text>
|
|
539
|
+
<Text> </Text>
|
|
540
|
+
<Text
|
|
541
|
+
inverse={activeTab === "settings"}
|
|
542
|
+
bold={activeTab === "settings"}
|
|
543
|
+
>
|
|
544
|
+
{" SETTINGS "}
|
|
545
|
+
</Text>
|
|
546
|
+
<Text dimColor> [Tab]</Text>
|
|
547
|
+
</Box>
|
|
548
|
+
|
|
481
549
|
{/* Global Stats */}
|
|
482
550
|
<StatsRow
|
|
483
551
|
stats={[
|
|
484
552
|
{ label: "Accounts", value: summary.total, color: "white" },
|
|
485
553
|
{ label: "Available", value: summary.available, color: "white" },
|
|
486
554
|
{ label: "Limited", value: summary.limited, color: "gray" },
|
|
555
|
+
{
|
|
556
|
+
label: "Need Verify",
|
|
557
|
+
value: Object.values(healthResults).filter(r => r.status === "verification_required").length,
|
|
558
|
+
color: "yellow"
|
|
559
|
+
},
|
|
487
560
|
{ label: "Providers", value: configSummary?.providers || 0, color: "white" },
|
|
488
561
|
{ label: "MCP", value: configSummary?.mcpEnabled || 0, color: "white" },
|
|
489
562
|
]}
|
|
490
563
|
/>
|
|
491
564
|
|
|
565
|
+
|
|
492
566
|
{/* Help bar */}
|
|
493
567
|
<Box marginY={1}>
|
|
494
568
|
<Text dimColor>↑↓ navigate • Space select • </Text>
|
|
495
569
|
<Text bold>R</Text>
|
|
496
570
|
<Text dimColor> refresh • </Text>
|
|
571
|
+
<Text bold>H</Text>
|
|
572
|
+
<Text dimColor> health • </Text>
|
|
497
573
|
<Text bold>P</Text>
|
|
498
574
|
<Text dimColor> actions • </Text>
|
|
499
575
|
<Text bold>Q</Text>
|
|
500
576
|
<Text dimColor> quit</Text>
|
|
501
|
-
{checkedEmails.size > 0 ? (
|
|
502
|
-
<Text> • {checkedEmails.size} selected</Text>
|
|
503
|
-
) : null}
|
|
504
|
-
</Box>
|
|
505
|
-
|
|
506
|
-
{/* Loading indicator */}
|
|
507
|
-
{isLoading && loadingStep ? (
|
|
508
|
-
<Box marginBottom={1} paddingX={1}>
|
|
509
|
-
<Text dimColor>⟳ {loadingStep}</Text>
|
|
510
|
-
</Box>
|
|
511
|
-
) : null}
|
|
512
|
-
|
|
577
|
+
{checkedEmails.size > 0 ? (
|
|
578
|
+
<Text> • {checkedEmails.size} selected</Text>
|
|
579
|
+
) : null}
|
|
580
|
+
</Box>
|
|
581
|
+
|
|
582
|
+
{/* Loading indicator */}
|
|
583
|
+
{isLoading && loadingStep ? (
|
|
584
|
+
<Box marginBottom={1} paddingX={1}>
|
|
585
|
+
<Text dimColor>⟳ {loadingStep}</Text>
|
|
586
|
+
</Box>
|
|
587
|
+
) : null}
|
|
588
|
+
|
|
513
589
|
{/* Tab content */}
|
|
514
590
|
{activeTab === "dashboard" ? (
|
|
515
591
|
// Dashboard Tab - Rate limits view
|
|
@@ -517,64 +593,67 @@ export function Dashboard({ pluginPath }: DashboardProps) {
|
|
|
517
593
|
<DashboardView
|
|
518
594
|
accounts={accounts}
|
|
519
595
|
selectedIndex={dashboardIndex}
|
|
596
|
+
healthResults={healthResults}
|
|
520
597
|
/>
|
|
521
598
|
</Box>
|
|
522
599
|
) : (
|
|
523
|
-
// Settings Tab - Original sections view
|
|
524
|
-
<>
|
|
525
|
-
<SectionBox
|
|
526
|
-
title="PROVIDERS"
|
|
527
|
-
borderColor={isOnSection("providers") ? "cyan" : (expandedSection === "providers" ? "white" : "gray")}
|
|
528
|
-
collapsed={expandedSection !== "providers"}
|
|
529
|
-
>
|
|
530
|
-
{opencodeInfo && <ProviderList providers={opencodeInfo.providers} />}
|
|
531
|
-
</SectionBox>
|
|
532
600
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
601
|
+
// Settings Tab - Original sections view
|
|
602
|
+
<>
|
|
603
|
+
<SectionBox
|
|
604
|
+
title="PROVIDERS"
|
|
605
|
+
borderColor={isOnSection("providers") ? "white" : (expandedSection === "providers" ? "gray" : "gray")}
|
|
606
|
+
collapsed={expandedSection !== "providers"}
|
|
607
|
+
>
|
|
608
|
+
{opencodeInfo && <ProviderList providers={opencodeInfo.providers} />}
|
|
609
|
+
</SectionBox>
|
|
610
|
+
|
|
611
|
+
<SectionBox
|
|
612
|
+
title={`ACCOUNTS (${opencodeInfo?.plugins[0]?.name || "antigravity-auth"})`}
|
|
613
|
+
borderColor={isOnSection("accounts") || (currentSettingsItem?.type === "account") ? "white" : "gray"}
|
|
614
|
+
collapsed={expandedSection !== "accounts"}
|
|
615
|
+
>
|
|
538
616
|
<AccountList
|
|
539
617
|
accounts={accounts}
|
|
540
618
|
selectedIndex={currentSettingsItem?.type === "account" ? (currentSettingsItem.index ?? -1) : -1}
|
|
541
619
|
checkedEmails={checkedEmails}
|
|
542
620
|
showCheckbox={true}
|
|
621
|
+
healthResults={healthResults}
|
|
543
622
|
/>
|
|
544
|
-
</SectionBox>
|
|
545
|
-
|
|
546
|
-
<SectionBox
|
|
547
|
-
title="MCP SERVERS"
|
|
548
|
-
borderColor={isOnSection("mcp") ? "
|
|
549
|
-
collapsed={expandedSection !== "mcp"}
|
|
550
|
-
>
|
|
551
|
-
{opencodeInfo && <McpServerList servers={opencodeInfo.mcpServers} />}
|
|
552
|
-
</SectionBox>
|
|
553
|
-
</>
|
|
554
|
-
)}
|
|
555
|
-
|
|
556
|
-
{/* Config path */}
|
|
557
|
-
<Box marginTop={1}>
|
|
558
|
-
<Text dimColor>Config: {opencodeInfo?.configPath || "N/A"}</Text>
|
|
559
|
-
</Box>
|
|
560
|
-
|
|
561
|
-
{/* Message */}
|
|
562
|
-
{message ? (
|
|
563
|
-
<Box marginTop={1}>
|
|
564
|
-
<Text dimColor>→ {message}</Text>
|
|
565
|
-
</Box>
|
|
566
|
-
) : null}
|
|
567
|
-
|
|
568
|
-
{/* Action Palette overlay */}
|
|
569
|
-
{activeModal === "palette" && (
|
|
570
|
-
<Box position="absolute" marginTop={3} marginLeft={10}>
|
|
571
|
-
<ActionPalette
|
|
572
|
-
actions={paletteActions}
|
|
573
|
-
onSelect={handlePaletteAction}
|
|
574
|
-
onClose={() => setActiveModal("none")}
|
|
575
|
-
/>
|
|
576
|
-
</Box>
|
|
577
|
-
)}
|
|
578
|
-
</Box>
|
|
579
|
-
);
|
|
580
|
-
}
|
|
623
|
+
</SectionBox>
|
|
624
|
+
|
|
625
|
+
<SectionBox
|
|
626
|
+
title="MCP SERVERS"
|
|
627
|
+
borderColor={isOnSection("mcp") ? "white" : "gray"}
|
|
628
|
+
collapsed={expandedSection !== "mcp"}
|
|
629
|
+
>
|
|
630
|
+
{opencodeInfo && <McpServerList servers={opencodeInfo.mcpServers} />}
|
|
631
|
+
</SectionBox>
|
|
632
|
+
</>
|
|
633
|
+
)}
|
|
634
|
+
|
|
635
|
+
{/* Config path */}
|
|
636
|
+
<Box marginTop={1}>
|
|
637
|
+
<Text dimColor>Config: {opencodeInfo?.configPath || "N/A"}</Text>
|
|
638
|
+
</Box>
|
|
639
|
+
|
|
640
|
+
{/* Message */}
|
|
641
|
+
{message ? (
|
|
642
|
+
<Box marginTop={1}>
|
|
643
|
+
<Text dimColor>→ {message}</Text>
|
|
644
|
+
</Box>
|
|
645
|
+
) : null}
|
|
646
|
+
|
|
647
|
+
{/* Action Palette overlay */}
|
|
648
|
+
{activeModal === "palette" && (
|
|
649
|
+
<Box position="absolute" marginTop={3} marginLeft={10}>
|
|
650
|
+
<ActionPalette
|
|
651
|
+
actions={paletteActions}
|
|
652
|
+
onSelect={handlePaletteAction}
|
|
653
|
+
onClose={() => setActiveModal("none")}
|
|
654
|
+
/>
|
|
655
|
+
</Box>
|
|
656
|
+
)}
|
|
657
|
+
</Box>
|
|
658
|
+
);
|
|
659
|
+
}
|