@tokentop/ttop 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/package.json +8 -5
- package/src/agents/aggregator.ts +39 -19
- package/src/agents/costing.ts +11 -9
- package/src/agents/index.ts +7 -9
- package/src/agents/types.ts +2 -2
- package/src/cli.ts +28 -23
- package/src/config/schema.test.ts +42 -40
- package/src/config/schema.ts +26 -26
- package/src/demo/simulator.ts +161 -122
- package/src/plugins/agents/index.ts +2 -2
- package/src/plugins/auth-sources.ts +42 -38
- package/src/plugins/lifecycle.ts +32 -32
- package/src/plugins/loader.test.ts +49 -49
- package/src/plugins/loader.ts +67 -63
- package/src/plugins/notification-bus.ts +21 -16
- package/src/plugins/notifications/index.ts +2 -2
- package/src/plugins/notifications/terminal-bell.ts +29 -23
- package/src/plugins/notifications/visual-flash.ts +21 -19
- package/src/plugins/npm-installer.ts +23 -25
- package/src/plugins/plugin-context-factory.ts +10 -10
- package/src/plugins/plugin-host.ts +12 -5
- package/src/plugins/providers/anthropic.ts +64 -57
- package/src/plugins/providers/antigravity.ts +81 -69
- package/src/plugins/providers/codex.ts +42 -42
- package/src/plugins/providers/gemini.ts +106 -87
- package/src/plugins/providers/github-copilot.ts +49 -45
- package/src/plugins/providers/index.ts +10 -12
- package/src/plugins/providers/minimax.ts +50 -44
- package/src/plugins/providers/openai-api.ts +82 -62
- package/src/plugins/providers/opencode-zen.ts +46 -39
- package/src/plugins/providers/perplexity.ts +55 -46
- package/src/plugins/providers/zai.ts +66 -57
- package/src/plugins/registry.ts +47 -43
- package/src/plugins/sandbox-guard.test.ts +45 -48
- package/src/plugins/sandbox-guard.ts +20 -25
- package/src/plugins/sandbox.ts +26 -48
- package/src/plugins/themes/catppuccin-latte.ts +29 -29
- package/src/plugins/themes/catppuccin-mocha.ts +29 -29
- package/src/plugins/themes/claude-code.ts +29 -29
- package/src/plugins/themes/dracula.ts +29 -29
- package/src/plugins/themes/github-light.ts +29 -29
- package/src/plugins/themes/gruvbox-dark.ts +29 -29
- package/src/plugins/themes/gruvbox-light.ts +29 -29
- package/src/plugins/themes/index.ts +15 -15
- package/src/plugins/themes/kanagawa.ts +29 -29
- package/src/plugins/themes/nord.ts +29 -29
- package/src/plugins/themes/one-dark.ts +29 -29
- package/src/plugins/themes/opencode.ts +31 -31
- package/src/plugins/themes/rose-pine-dawn.ts +29 -29
- package/src/plugins/themes/rose-pine.ts +29 -29
- package/src/plugins/themes/solarized-light.ts +29 -29
- package/src/plugins/themes/tokyo-night.ts +29 -29
- package/src/plugins/types/agent.ts +5 -5
- package/src/plugins/types/base.ts +35 -24
- package/src/plugins/types/index.ts +9 -9
- package/src/plugins/types/notification.ts +12 -12
- package/src/plugins/types/provider.ts +12 -8
- package/src/plugins/types/theme.ts +31 -23
- package/src/plugins/update-checker.ts +6 -6
- package/src/pricing/estimator.test.ts +39 -44
- package/src/pricing/estimator.ts +7 -13
- package/src/pricing/fallback.test.ts +30 -34
- package/src/pricing/fallback.ts +54 -27
- package/src/pricing/index.ts +18 -12
- package/src/pricing/models-dev.ts +6 -6
- package/src/storage/database.ts +56 -46
- package/src/storage/db.ts +12 -12
- package/src/storage/index.ts +44 -45
- package/src/storage/migrations/index.ts +2 -2
- package/src/storage/paths.ts +13 -13
- package/src/storage/repos/agentSessions.ts +33 -23
- package/src/storage/repos/providerSnapshots.ts +5 -5
- package/src/storage/repos/usageEvents.ts +40 -28
- package/src/storage/types.ts +5 -5
- package/src/tui/App.tsx +180 -137
- package/src/tui/components/CommandPalette.tsx +46 -34
- package/src/tui/components/DebugConsole.tsx +39 -33
- package/src/tui/components/DebugPanel.tsx +295 -177
- package/src/tui/components/FooterHints.tsx +2 -2
- package/src/tui/components/GhostProviderCard.tsx +37 -35
- package/src/tui/components/Header.tsx +68 -35
- package/src/tui/components/HelpOverlay.tsx +99 -29
- package/src/tui/components/InlineGauge.tsx +10 -7
- package/src/tui/components/InlineSparkline.tsx +17 -11
- package/src/tui/components/KpiStrip.tsx +128 -98
- package/src/tui/components/LimitGauge.tsx +96 -59
- package/src/tui/components/ModalBackdrop.tsx +4 -4
- package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
- package/src/tui/components/ProviderCard.tsx +232 -226
- package/src/tui/components/ProviderDetailPanel.tsx +133 -58
- package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
- package/src/tui/components/ProvidersList.tsx +215 -126
- package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
- package/src/tui/components/SessionsTable.tsx +313 -166
- package/src/tui/components/SettingsModal.tsx +231 -214
- package/src/tui/components/SidebarBreakdown.tsx +30 -16
- package/src/tui/components/Skeleton.tsx +24 -17
- package/src/tui/components/SmartSidebar.tsx +360 -258
- package/src/tui/components/Sparkline.tsx +64 -52
- package/src/tui/components/Spinner.tsx +3 -3
- package/src/tui/components/StatusBar.tsx +27 -27
- package/src/tui/components/ThemePicker.tsx +67 -66
- package/src/tui/components/Toast.tsx +9 -6
- package/src/tui/components/UsageGauge.tsx +10 -13
- package/src/tui/components/index.ts +5 -5
- package/src/tui/contexts/AgentSessionContext.tsx +193 -144
- package/src/tui/contexts/ConfigContext.tsx +15 -13
- package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
- package/src/tui/contexts/DemoModeContext.tsx +22 -15
- package/src/tui/contexts/DrawerContext.tsx +11 -9
- package/src/tui/contexts/InputContext.tsx +2 -2
- package/src/tui/contexts/LogContext.tsx +62 -47
- package/src/tui/contexts/PluginContext.tsx +232 -183
- package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
- package/src/tui/contexts/StorageContext.tsx +151 -117
- package/src/tui/contexts/ThemeContext.tsx +21 -16
- package/src/tui/contexts/TimeWindowContext.tsx +72 -61
- package/src/tui/contexts/ToastContext.tsx +10 -7
- package/src/tui/contexts/index.ts +9 -5
- package/src/tui/createApp.tsx +8 -12
- package/src/tui/debug/captureFrame.ts +25 -28
- package/src/tui/debug/snapshot.tsx +179 -162
- package/src/tui/driver/assertions.ts +30 -22
- package/src/tui/driver/cli.ts +231 -221
- package/src/tui/driver/coverage.ts +27 -44
- package/src/tui/driver/demo-snapshot.ts +33 -33
- package/src/tui/driver/diff.ts +26 -32
- package/src/tui/driver/driver.ts +43 -48
- package/src/tui/driver/index.ts +36 -40
- package/src/tui/driver/recorder.ts +37 -48
- package/src/tui/driver/test-workflow.ts +51 -51
- package/src/tui/driver/test.ts +25 -25
- package/src/tui/hooks/index.ts +24 -20
- package/src/tui/hooks/useAnimatedValue.ts +13 -18
- package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
- package/src/tui/hooks/useDashboardState.ts +26 -24
- package/src/tui/hooks/useEmaActivity.ts +82 -74
- package/src/tui/hooks/useEntranceAnimation.ts +11 -11
- package/src/tui/hooks/useExitAnimation.ts +3 -3
- package/src/tui/hooks/usePulse.ts +4 -4
- package/src/tui/hooks/useSafeRenderer.ts +5 -5
- package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
- package/src/tui/hooks/useValueFlash.ts +10 -14
- package/src/tui/index.tsx +29 -6
- package/src/tui/shutdown.ts +19 -0
- package/src/tui/utils/providerColor.ts +1 -1
- package/src/tui/views/Dashboard.tsx +127 -102
- package/src/tui/views/HistoricalTrendsView.tsx +579 -250
- package/src/tui/views/ProjectsView.tsx +466 -237
- package/src/tui/views/RealTimeDashboard.tsx +150 -111
- package/src/tui/views/SettingsView.tsx +222 -191
- package/src/tui/views/index.ts +2 -2
- package/src/types/opentui-test-utils.d.ts +7 -7
- package/src/utils/clipboard.ts +29 -29
- package/src/utils/currency.test.ts +53 -53
- package/src/utils/currency.ts +35 -32
- package/src/version.ts +1 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useKeyboard, useTerminalDimensions } from
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { useInputFocus } from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { ModalBackdrop, Z_INDEX } from
|
|
10
|
-
|
|
11
|
-
type DebugTab =
|
|
12
|
-
type PluginSort =
|
|
1
|
+
import type { ScrollBoxRenderable } from "@opentui/core";
|
|
2
|
+
import { useKeyboard, useTerminalDimensions } from "@opentui/react";
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { pluginRegistry } from "@/plugins/registry.ts";
|
|
5
|
+
import { checkAllPluginUpdates, type PluginUpdateInfo } from "@/plugins/update-checker.ts";
|
|
6
|
+
import { useInputFocus } from "../contexts/InputContext.tsx";
|
|
7
|
+
import { type LogEntry, type LogLevel, useLogs } from "../contexts/LogContext.tsx";
|
|
8
|
+
import { useColors } from "../contexts/ThemeContext.tsx";
|
|
9
|
+
import { ModalBackdrop, Z_INDEX } from "./ModalBackdrop.tsx";
|
|
10
|
+
|
|
11
|
+
type DebugTab = "logs" | "inspector" | "plugins";
|
|
12
|
+
type PluginSort = "name" | "type" | "official";
|
|
13
13
|
|
|
14
14
|
interface DebugInspectorData {
|
|
15
15
|
sessions: Array<{
|
|
@@ -41,21 +41,21 @@ interface DebugPanelProps {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function padRight(str: string, len: number): string {
|
|
44
|
-
return str.length >= len ? str.slice(0, len) : str +
|
|
44
|
+
return str.length >= len ? str.slice(0, len) : str + " ".repeat(len - str.length);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const LOG_LEVELS: LogLevel[] = [
|
|
47
|
+
const LOG_LEVELS: LogLevel[] = ["debug", "info", "warn", "error"];
|
|
48
48
|
const LOG_LEVEL_RANK: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
49
49
|
|
|
50
50
|
function filterByLevel(logs: LogEntry[], minLevel: LogLevel): LogEntry[] {
|
|
51
51
|
const minRank = LOG_LEVEL_RANK[minLevel];
|
|
52
52
|
if (minRank === 0) return logs;
|
|
53
|
-
return logs.filter(e => LOG_LEVEL_RANK[e.level] >= minRank);
|
|
53
|
+
return logs.filter((e) => LOG_LEVEL_RANK[e.level] >= minRank);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
function nextLevel(current: LogLevel): LogLevel {
|
|
57
57
|
const idx = LOG_LEVELS.indexOf(current);
|
|
58
|
-
return LOG_LEVELS[(idx + 1) % LOG_LEVELS.length] ??
|
|
58
|
+
return LOG_LEVELS[(idx + 1) % LOG_LEVELS.length] ?? "debug";
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
export function DebugPanel({ onClose, inspectorData }: DebugPanelProps) {
|
|
@@ -63,10 +63,10 @@ export function DebugPanel({ onClose, inspectorData }: DebugPanelProps) {
|
|
|
63
63
|
const { width: termWidth, height: termHeight } = useTerminalDimensions();
|
|
64
64
|
const { logs, clearLogs } = useLogs();
|
|
65
65
|
const { setInputFocused } = useInputFocus();
|
|
66
|
-
const [activeTab, setActiveTab] = useState<DebugTab>(
|
|
66
|
+
const [activeTab, setActiveTab] = useState<DebugTab>("logs");
|
|
67
67
|
const [follow, setFollow] = useState(true);
|
|
68
|
-
const [minLevel, setMinLevel] = useState<LogLevel>(
|
|
69
|
-
const [pluginSort, setPluginSort] = useState<PluginSort>(
|
|
68
|
+
const [minLevel, setMinLevel] = useState<LogLevel>("info");
|
|
69
|
+
const [pluginSort, setPluginSort] = useState<PluginSort>("type");
|
|
70
70
|
const scrollboxRef = useRef<ScrollBoxRenderable>(null);
|
|
71
71
|
const inspectorScrollboxRef = useRef<ScrollBoxRenderable>(null);
|
|
72
72
|
const pluginsScrollboxRef = useRef<ScrollBoxRenderable>(null);
|
|
@@ -82,7 +82,7 @@ export function DebugPanel({ onClose, inspectorData }: DebugPanelProps) {
|
|
|
82
82
|
}, [setInputFocused]);
|
|
83
83
|
|
|
84
84
|
useEffect(() => {
|
|
85
|
-
if (follow && activeTab ===
|
|
85
|
+
if (follow && activeTab === "logs") {
|
|
86
86
|
const scrollbox = scrollboxRef.current;
|
|
87
87
|
if (scrollbox) {
|
|
88
88
|
scrollbox.scrollTo(scrollbox.scrollHeight);
|
|
@@ -91,136 +91,136 @@ export function DebugPanel({ onClose, inspectorData }: DebugPanelProps) {
|
|
|
91
91
|
}, [logs.length, follow, activeTab]);
|
|
92
92
|
|
|
93
93
|
useKeyboard((key) => {
|
|
94
|
-
if (key.name ===
|
|
94
|
+
if (key.name === "escape") {
|
|
95
95
|
onClose();
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
const tabs: DebugTab[] = [
|
|
100
|
-
if (key.name ===
|
|
101
|
-
setActiveTab(prev => tabs[(tabs.indexOf(prev) + 1) % tabs.length] ??
|
|
99
|
+
const tabs: DebugTab[] = ["logs", "inspector", "plugins"];
|
|
100
|
+
if (key.name === "tab" || key.sequence === "[" || key.sequence === "]") {
|
|
101
|
+
setActiveTab((prev) => tabs[(tabs.indexOf(prev) + 1) % tabs.length] ?? "logs");
|
|
102
102
|
lastKeyRef.current = null;
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
|
-
if (key.name ===
|
|
106
|
-
setActiveTab(
|
|
105
|
+
if (key.name === "1") {
|
|
106
|
+
setActiveTab("logs");
|
|
107
107
|
lastKeyRef.current = null;
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
|
-
if (key.name ===
|
|
111
|
-
setActiveTab(
|
|
110
|
+
if (key.name === "2") {
|
|
111
|
+
setActiveTab("inspector");
|
|
112
112
|
lastKeyRef.current = null;
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
|
-
if (key.name ===
|
|
116
|
-
setActiveTab(
|
|
115
|
+
if (key.name === "3") {
|
|
116
|
+
setActiveTab("plugins");
|
|
117
117
|
lastKeyRef.current = null;
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
if (activeTab ===
|
|
122
|
-
if (key.name ===
|
|
123
|
-
setFollow(prev => !prev);
|
|
121
|
+
if (activeTab === "logs") {
|
|
122
|
+
if (key.name === "f") {
|
|
123
|
+
setFollow((prev) => !prev);
|
|
124
124
|
return;
|
|
125
125
|
}
|
|
126
|
-
if (key.name ===
|
|
127
|
-
setMinLevel(prev => nextLevel(prev));
|
|
126
|
+
if (key.name === "l") {
|
|
127
|
+
setMinLevel((prev) => nextLevel(prev));
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
|
-
if (key.name ===
|
|
130
|
+
if (key.name === "c") {
|
|
131
131
|
clearLogs();
|
|
132
132
|
return;
|
|
133
133
|
}
|
|
134
|
-
if (key.name ===
|
|
134
|
+
if (key.name === "down" || key.name === "j") {
|
|
135
135
|
const box = scrollboxRef.current;
|
|
136
136
|
if (box) box.scrollTo(Math.min(box.scrollTop + SCROLL_STEP, box.scrollHeight));
|
|
137
137
|
setFollow(false);
|
|
138
138
|
lastKeyRef.current = null;
|
|
139
139
|
return;
|
|
140
140
|
}
|
|
141
|
-
if (key.name ===
|
|
141
|
+
if (key.name === "up" || key.name === "k") {
|
|
142
142
|
const box = scrollboxRef.current;
|
|
143
143
|
if (box) box.scrollTo(Math.max(box.scrollTop - SCROLL_STEP, 0));
|
|
144
144
|
setFollow(false);
|
|
145
145
|
lastKeyRef.current = null;
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
|
-
if (key.shift && key.name ===
|
|
148
|
+
if (key.shift && key.name === "g") {
|
|
149
149
|
scrollboxRef.current?.scrollTo(scrollboxRef.current.scrollHeight);
|
|
150
150
|
lastKeyRef.current = null;
|
|
151
151
|
return;
|
|
152
152
|
}
|
|
153
|
-
if (key.name ===
|
|
154
|
-
if (lastKeyRef.current ===
|
|
153
|
+
if (key.name === "g") {
|
|
154
|
+
if (lastKeyRef.current === "g") {
|
|
155
155
|
scrollboxRef.current?.scrollTo(0);
|
|
156
156
|
lastKeyRef.current = null;
|
|
157
157
|
} else {
|
|
158
|
-
lastKeyRef.current =
|
|
158
|
+
lastKeyRef.current = "g";
|
|
159
159
|
}
|
|
160
160
|
return;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
if (activeTab ===
|
|
165
|
-
if (key.name ===
|
|
164
|
+
if (activeTab === "inspector") {
|
|
165
|
+
if (key.name === "down" || key.name === "j") {
|
|
166
166
|
const box = inspectorScrollboxRef.current;
|
|
167
167
|
if (box) box.scrollTo(Math.min(box.scrollTop + SCROLL_STEP, box.scrollHeight));
|
|
168
168
|
lastKeyRef.current = null;
|
|
169
169
|
return;
|
|
170
170
|
}
|
|
171
|
-
if (key.name ===
|
|
171
|
+
if (key.name === "up" || key.name === "k") {
|
|
172
172
|
const box = inspectorScrollboxRef.current;
|
|
173
173
|
if (box) box.scrollTo(Math.max(box.scrollTop - SCROLL_STEP, 0));
|
|
174
174
|
lastKeyRef.current = null;
|
|
175
175
|
return;
|
|
176
176
|
}
|
|
177
|
-
if (key.shift && key.name ===
|
|
177
|
+
if (key.shift && key.name === "g") {
|
|
178
178
|
inspectorScrollboxRef.current?.scrollTo(inspectorScrollboxRef.current.scrollHeight);
|
|
179
179
|
lastKeyRef.current = null;
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
|
-
if (key.name ===
|
|
183
|
-
if (lastKeyRef.current ===
|
|
182
|
+
if (key.name === "g") {
|
|
183
|
+
if (lastKeyRef.current === "g") {
|
|
184
184
|
inspectorScrollboxRef.current?.scrollTo(0);
|
|
185
185
|
lastKeyRef.current = null;
|
|
186
186
|
} else {
|
|
187
|
-
lastKeyRef.current =
|
|
187
|
+
lastKeyRef.current = "g";
|
|
188
188
|
}
|
|
189
189
|
return;
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
if (activeTab ===
|
|
194
|
-
if (key.name ===
|
|
195
|
-
setPluginSort(prev => {
|
|
196
|
-
const order: PluginSort[] = [
|
|
197
|
-
return order[(order.indexOf(prev) + 1) % order.length] ??
|
|
193
|
+
if (activeTab === "plugins") {
|
|
194
|
+
if (key.name === "s") {
|
|
195
|
+
setPluginSort((prev) => {
|
|
196
|
+
const order: PluginSort[] = ["name", "type", "official"];
|
|
197
|
+
return order[(order.indexOf(prev) + 1) % order.length] ?? "name";
|
|
198
198
|
});
|
|
199
199
|
return;
|
|
200
200
|
}
|
|
201
|
-
if (key.name ===
|
|
201
|
+
if (key.name === "down" || key.name === "j") {
|
|
202
202
|
const box = pluginsScrollboxRef.current;
|
|
203
203
|
if (box) box.scrollTo(Math.min(box.scrollTop + SCROLL_STEP, box.scrollHeight));
|
|
204
204
|
lastKeyRef.current = null;
|
|
205
205
|
return;
|
|
206
206
|
}
|
|
207
|
-
if (key.name ===
|
|
207
|
+
if (key.name === "up" || key.name === "k") {
|
|
208
208
|
const box = pluginsScrollboxRef.current;
|
|
209
209
|
if (box) box.scrollTo(Math.max(box.scrollTop - SCROLL_STEP, 0));
|
|
210
210
|
lastKeyRef.current = null;
|
|
211
211
|
return;
|
|
212
212
|
}
|
|
213
|
-
if (key.shift && key.name ===
|
|
213
|
+
if (key.shift && key.name === "g") {
|
|
214
214
|
pluginsScrollboxRef.current?.scrollTo(pluginsScrollboxRef.current.scrollHeight);
|
|
215
215
|
lastKeyRef.current = null;
|
|
216
216
|
return;
|
|
217
217
|
}
|
|
218
|
-
if (key.name ===
|
|
219
|
-
if (lastKeyRef.current ===
|
|
218
|
+
if (key.name === "g") {
|
|
219
|
+
if (lastKeyRef.current === "g") {
|
|
220
220
|
pluginsScrollboxRef.current?.scrollTo(0);
|
|
221
221
|
lastKeyRef.current = null;
|
|
222
222
|
} else {
|
|
223
|
-
lastKeyRef.current =
|
|
223
|
+
lastKeyRef.current = "g";
|
|
224
224
|
}
|
|
225
225
|
return;
|
|
226
226
|
}
|
|
@@ -237,10 +237,10 @@ export function DebugPanel({ onClose, inspectorData }: DebugPanelProps) {
|
|
|
237
237
|
};
|
|
238
238
|
|
|
239
239
|
const levelLabels: Record<LogLevel, string> = {
|
|
240
|
-
debug:
|
|
241
|
-
info:
|
|
242
|
-
warn:
|
|
243
|
-
error:
|
|
240
|
+
debug: "DBG",
|
|
241
|
+
info: "INF",
|
|
242
|
+
warn: "WRN",
|
|
243
|
+
error: "ERR",
|
|
244
244
|
};
|
|
245
245
|
|
|
246
246
|
return (
|
|
@@ -255,38 +255,38 @@ export function DebugPanel({ onClose, inspectorData }: DebugPanelProps) {
|
|
|
255
255
|
backgroundColor={colors.background}
|
|
256
256
|
overflow="hidden"
|
|
257
257
|
>
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
258
|
+
<box
|
|
259
|
+
flexDirection="row"
|
|
260
|
+
justifyContent="space-between"
|
|
261
|
+
paddingX={1}
|
|
262
|
+
backgroundColor={colors.foreground}
|
|
263
|
+
height={1}
|
|
264
|
+
flexShrink={0}
|
|
265
|
+
>
|
|
266
266
|
<box flexDirection="row" gap={2}>
|
|
267
267
|
<text
|
|
268
|
-
fg={activeTab ===
|
|
269
|
-
{...(activeTab ===
|
|
268
|
+
fg={activeTab === "logs" ? colors.background : colors.textMuted}
|
|
269
|
+
{...(activeTab === "logs" ? { bg: colors.primary } : {})}
|
|
270
270
|
>
|
|
271
|
-
{
|
|
271
|
+
{" LOGS "}
|
|
272
272
|
</text>
|
|
273
273
|
<text
|
|
274
|
-
fg={activeTab ===
|
|
275
|
-
{...(activeTab ===
|
|
274
|
+
fg={activeTab === "inspector" ? colors.background : colors.textMuted}
|
|
275
|
+
{...(activeTab === "inspector" ? { bg: colors.primary } : {})}
|
|
276
276
|
>
|
|
277
|
-
{
|
|
277
|
+
{" INSPECTOR "}
|
|
278
278
|
</text>
|
|
279
279
|
<text
|
|
280
|
-
fg={activeTab ===
|
|
281
|
-
{...(activeTab ===
|
|
280
|
+
fg={activeTab === "plugins" ? colors.background : colors.textMuted}
|
|
281
|
+
{...(activeTab === "plugins" ? { bg: colors.primary } : {})}
|
|
282
282
|
>
|
|
283
|
-
{
|
|
283
|
+
{" PLUGINS "}
|
|
284
284
|
</text>
|
|
285
285
|
</box>
|
|
286
|
-
<text fg={colors.textSubtle}>1/2/3:switch
|
|
286
|
+
<text fg={colors.textSubtle}>1/2/3:switch Esc:close</text>
|
|
287
287
|
</box>
|
|
288
288
|
|
|
289
|
-
{activeTab ===
|
|
289
|
+
{activeTab === "logs" && (
|
|
290
290
|
<LogsTab
|
|
291
291
|
logs={filterByLevel(logs, minLevel)}
|
|
292
292
|
follow={follow}
|
|
@@ -297,35 +297,32 @@ export function DebugPanel({ onClose, inspectorData }: DebugPanelProps) {
|
|
|
297
297
|
/>
|
|
298
298
|
)}
|
|
299
299
|
|
|
300
|
-
{activeTab ===
|
|
300
|
+
{activeTab === "inspector" && (
|
|
301
301
|
<InspectorTab data={inspectorData} colors={colors} scrollboxRef={inspectorScrollboxRef} />
|
|
302
302
|
)}
|
|
303
303
|
|
|
304
|
-
{activeTab ===
|
|
304
|
+
{activeTab === "plugins" && (
|
|
305
305
|
<PluginsTab colors={colors} scrollboxRef={pluginsScrollboxRef} sortBy={pluginSort} />
|
|
306
306
|
)}
|
|
307
307
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
{activeTab ===
|
|
308
|
+
<box
|
|
309
|
+
flexDirection="row"
|
|
310
|
+
paddingX={1}
|
|
311
|
+
backgroundColor={colors.foreground}
|
|
312
|
+
height={1}
|
|
313
|
+
flexShrink={0}
|
|
314
|
+
>
|
|
315
|
+
{activeTab === "logs" && (
|
|
316
316
|
<text fg={colors.textSubtle}>
|
|
317
|
-
j/k:scroll
|
|
317
|
+
j/k:scroll f:follow{follow ? "(on)" : "(off)"} l:{minLevel.toUpperCase()} c:clear
|
|
318
|
+
gg:top G:bottom
|
|
318
319
|
</text>
|
|
319
320
|
)}
|
|
320
|
-
{activeTab ===
|
|
321
|
-
<text fg={colors.textSubtle}>
|
|
322
|
-
j/k:scroll gg:top G:bottom
|
|
323
|
-
</text>
|
|
321
|
+
{activeTab === "inspector" && (
|
|
322
|
+
<text fg={colors.textSubtle}>j/k:scroll gg:top G:bottom</text>
|
|
324
323
|
)}
|
|
325
|
-
{activeTab ===
|
|
326
|
-
<text fg={colors.textSubtle}>
|
|
327
|
-
j/k:scroll s:sort({pluginSort}) gg:top G:bottom
|
|
328
|
-
</text>
|
|
324
|
+
{activeTab === "plugins" && (
|
|
325
|
+
<text fg={colors.textSubtle}>j/k:scroll s:sort({pluginSort}) gg:top G:bottom</text>
|
|
329
326
|
)}
|
|
330
327
|
</box>
|
|
331
328
|
</box>
|
|
@@ -355,13 +352,16 @@ function LogsTab({ logs, follow, scrollboxRef, levelColors, levelLabels, colors
|
|
|
355
352
|
}
|
|
356
353
|
|
|
357
354
|
const visibleLogs = frozenLogsRef.current ?? logs.slice(-200);
|
|
358
|
-
const pendingCount = frozenLogsRef.current
|
|
355
|
+
const pendingCount = frozenLogsRef.current
|
|
356
|
+
? Math.max(0, logs.length - frozenAtCountRef.current)
|
|
357
|
+
: 0;
|
|
359
358
|
|
|
360
359
|
return (
|
|
361
360
|
<box flexDirection="column" flexGrow={1} overflow="hidden">
|
|
362
361
|
<box paddingLeft={1} height={1} flexShrink={0}>
|
|
363
362
|
<text fg={colors.textMuted}>
|
|
364
|
-
{logs.length} entries
|
|
363
|
+
{logs.length} entries
|
|
364
|
+
{follow ? " [FOLLOW]" : ` [PAUSED${pendingCount > 0 ? ` +${pendingCount} new` : ""}]`}
|
|
365
365
|
</text>
|
|
366
366
|
</box>
|
|
367
367
|
<scrollbox
|
|
@@ -407,24 +407,21 @@ interface LogLineProps {
|
|
|
407
407
|
}
|
|
408
408
|
|
|
409
409
|
function LogLine({ entry, levelColors, levelLabels, colors }: LogLineProps) {
|
|
410
|
-
const time = new Date(entry.timestamp).toLocaleTimeString(
|
|
410
|
+
const time = new Date(entry.timestamp).toLocaleTimeString("en-US", {
|
|
411
411
|
hour12: false,
|
|
412
|
-
hour:
|
|
413
|
-
minute:
|
|
414
|
-
second:
|
|
412
|
+
hour: "2-digit",
|
|
413
|
+
minute: "2-digit",
|
|
414
|
+
second: "2-digit",
|
|
415
415
|
});
|
|
416
416
|
|
|
417
417
|
const levelColor = levelColors[entry.level];
|
|
418
418
|
const levelLabel = levelLabels[entry.level];
|
|
419
|
-
const source = entry.source ? `[${entry.source}]` :
|
|
420
|
-
const dataStr = entry.data ? ` ${JSON.stringify(entry.data)}` :
|
|
419
|
+
const source = entry.source ? `[${entry.source}]` : "";
|
|
420
|
+
const dataStr = entry.data ? ` ${JSON.stringify(entry.data)}` : "";
|
|
421
421
|
|
|
422
422
|
return (
|
|
423
423
|
<text height={1}>
|
|
424
|
-
<span fg={colors.textSubtle}>{time}</span>
|
|
425
|
-
{' '}
|
|
426
|
-
<span fg={levelColor}>{levelLabel}</span>
|
|
427
|
-
{' '}
|
|
424
|
+
<span fg={colors.textSubtle}>{time}</span> <span fg={levelColor}>{levelLabel}</span>{" "}
|
|
428
425
|
{source && <span fg={colors.textMuted}>{source} </span>}
|
|
429
426
|
<span fg={colors.text}>{entry.message}</span>
|
|
430
427
|
{dataStr && <span fg={colors.textSubtle}>{dataStr}</span>}
|
|
@@ -452,31 +449,49 @@ function InspectorTab({ data, colors, scrollboxRef }: InspectorTabProps) {
|
|
|
452
449
|
|
|
453
450
|
const { sessions, debugData, activity, sparkData } = data;
|
|
454
451
|
const totalTokens = sessions.reduce((sum, s) => sum + s.totals.input + s.totals.output, 0);
|
|
455
|
-
const activeSessions = sessions.filter(s => s.status ===
|
|
452
|
+
const activeSessions = sessions.filter((s) => s.status === "active");
|
|
456
453
|
const now = Date.now();
|
|
457
454
|
|
|
458
455
|
if (isCompact) {
|
|
459
456
|
return (
|
|
460
|
-
|
|
457
|
+
<box flexDirection="column" flexGrow={1} paddingX={1} overflow="hidden">
|
|
461
458
|
<box flexDirection="row" height={1} gap={2} flexShrink={0}>
|
|
462
459
|
<text fg={colors.textMuted}>rate:{(activity.instantRate || 0).toFixed(0)}/s</text>
|
|
463
460
|
<text fg={colors.textMuted}>avg:{(activity.avgRate || 0).toFixed(0)}/s</text>
|
|
464
461
|
<text fg={colors.textMuted}>tokens:{totalTokens.toLocaleString()}</text>
|
|
465
|
-
<text fg={activity.isSpike ? colors.warning : colors.textMuted}>
|
|
462
|
+
<text fg={activity.isSpike ? colors.warning : colors.textMuted}>
|
|
463
|
+
{activity.isSpike ? "SPIKE" : ""}
|
|
464
|
+
</text>
|
|
466
465
|
</box>
|
|
467
466
|
<box flexDirection="row" marginTop={1} height={1}>
|
|
468
|
-
<text width={18} fg={colors.textSubtle}>
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
<text width={10} fg={colors.textSubtle}>
|
|
467
|
+
<text width={18} fg={colors.textSubtle}>
|
|
468
|
+
{padRight("SESSION", 18)}
|
|
469
|
+
</text>
|
|
470
|
+
<text width={10} fg={colors.textSubtle}>
|
|
471
|
+
{padRight("AGENT", 10)}
|
|
472
|
+
</text>
|
|
473
|
+
<text width={8} fg={colors.textSubtle}>
|
|
474
|
+
{padRight("STATUS", 8)}
|
|
475
|
+
</text>
|
|
476
|
+
<text width={10} fg={colors.textSubtle}>
|
|
477
|
+
{padRight("TOKENS", 10)}
|
|
478
|
+
</text>
|
|
472
479
|
</box>
|
|
473
480
|
<scrollbox ref={scrollboxRef} flexGrow={1}>
|
|
474
|
-
{sessions.slice(0, 15).map(s => (
|
|
481
|
+
{sessions.slice(0, 15).map((s) => (
|
|
475
482
|
<box key={s.sessionId} flexDirection="row" height={1}>
|
|
476
|
-
<text width={18} fg={colors.text}>
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
<text width={10} fg={colors.text}>
|
|
483
|
+
<text width={18} fg={colors.text}>
|
|
484
|
+
{padRight(s.sessionId.slice(0, 17), 18)}
|
|
485
|
+
</text>
|
|
486
|
+
<text width={10} fg={colors.text}>
|
|
487
|
+
{padRight(s.agentName.slice(0, 9), 10)}
|
|
488
|
+
</text>
|
|
489
|
+
<text width={8} fg={s.status === "active" ? colors.success : colors.textMuted}>
|
|
490
|
+
{padRight(s.status.slice(0, 7), 8)}
|
|
491
|
+
</text>
|
|
492
|
+
<text width={10} fg={colors.text}>
|
|
493
|
+
{padRight((s.totals.input + s.totals.output).toLocaleString(), 10)}
|
|
494
|
+
</text>
|
|
480
495
|
</box>
|
|
481
496
|
))}
|
|
482
497
|
</scrollbox>
|
|
@@ -487,53 +502,143 @@ function InspectorTab({ data, colors, scrollboxRef }: InspectorTabProps) {
|
|
|
487
502
|
return (
|
|
488
503
|
<box flexDirection="column" flexGrow={1} padding={1} overflow="hidden">
|
|
489
504
|
<box flexDirection="row" gap={2} height={8} flexShrink={0}>
|
|
490
|
-
<box
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
505
|
+
<box
|
|
506
|
+
flexDirection="column"
|
|
507
|
+
flexGrow={1}
|
|
508
|
+
border
|
|
509
|
+
borderColor={colors.border}
|
|
510
|
+
padding={1}
|
|
511
|
+
overflow="hidden"
|
|
512
|
+
>
|
|
513
|
+
<text height={1} fg={colors.primary}>
|
|
514
|
+
{padRight("Bucket Data", 30)}
|
|
515
|
+
</text>
|
|
516
|
+
<text height={1} fg={colors.textMuted}>
|
|
517
|
+
{padRight("deltaTokens: " + debugData.lastDeltaTokens.toLocaleString(), 30)}
|
|
518
|
+
</text>
|
|
519
|
+
<text height={1} fg={colors.textMuted}>
|
|
520
|
+
{padRight("dt: " + debugData.lastDt.toFixed(3) + "s", 30)}
|
|
521
|
+
</text>
|
|
522
|
+
<text height={1} fg={colors.textMuted}>
|
|
523
|
+
{padRight("shifted: " + debugData.bucketsShifted, 30)}
|
|
524
|
+
</text>
|
|
525
|
+
<text height={1} fg={colors.textMuted}>
|
|
526
|
+
{padRight("currBucket: " + debugData.currentBucketValue.toFixed(1), 30)}
|
|
527
|
+
</text>
|
|
528
|
+
<text height={1} fg={colors.textMuted}>
|
|
529
|
+
{padRight(
|
|
530
|
+
"buckets[5]: [" +
|
|
531
|
+
sparkData
|
|
532
|
+
.slice(-5)
|
|
533
|
+
.map((v) => v.toFixed(0))
|
|
534
|
+
.join(",") +
|
|
535
|
+
"]",
|
|
536
|
+
30,
|
|
537
|
+
)}
|
|
538
|
+
</text>
|
|
497
539
|
</box>
|
|
498
540
|
|
|
499
|
-
<box
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
541
|
+
<box
|
|
542
|
+
flexDirection="column"
|
|
543
|
+
flexGrow={1}
|
|
544
|
+
border
|
|
545
|
+
borderColor={colors.border}
|
|
546
|
+
padding={1}
|
|
547
|
+
overflow="hidden"
|
|
548
|
+
>
|
|
549
|
+
<text height={1} fg={colors.primary}>
|
|
550
|
+
{padRight("Activity State", 30)}
|
|
551
|
+
</text>
|
|
552
|
+
<text height={1} fg={colors.textMuted}>
|
|
553
|
+
{padRight("instantRate: " + (activity.instantRate || 0).toFixed(1) + "/s", 30)}
|
|
554
|
+
</text>
|
|
555
|
+
<text height={1} fg={colors.textMuted}>
|
|
556
|
+
{padRight("avgRate: " + (activity.avgRate || 0).toFixed(1) + "/s", 30)}
|
|
557
|
+
</text>
|
|
558
|
+
<text height={1} fg={colors.textMuted}>
|
|
559
|
+
{padRight("isSpike: " + (activity.isSpike ? "YES" : "no"), 30)}
|
|
560
|
+
</text>
|
|
504
561
|
</box>
|
|
505
562
|
|
|
506
|
-
<box
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
563
|
+
<box
|
|
564
|
+
flexDirection="column"
|
|
565
|
+
flexGrow={1}
|
|
566
|
+
border
|
|
567
|
+
borderColor={colors.border}
|
|
568
|
+
padding={1}
|
|
569
|
+
overflow="hidden"
|
|
570
|
+
>
|
|
571
|
+
<text height={1} fg={colors.primary}>
|
|
572
|
+
{padRight("Refresh Stats", 30)}
|
|
573
|
+
</text>
|
|
574
|
+
<text height={1} fg={colors.textMuted}>
|
|
575
|
+
{padRight("count: " + String(debugData.refreshCount), 30)}
|
|
576
|
+
</text>
|
|
577
|
+
<text height={1} fg={colors.textMuted}>
|
|
578
|
+
{padRight("last: " + new Date(debugData.lastRefreshTime).toLocaleTimeString(), 30)}
|
|
579
|
+
</text>
|
|
580
|
+
<text height={1} fg={colors.textMuted}>
|
|
581
|
+
{padRight("age: " + ((now - debugData.lastRefreshTime) / 1000).toFixed(1) + "s", 30)}
|
|
582
|
+
</text>
|
|
583
|
+
<text height={1} fg={colors.textMuted}>
|
|
584
|
+
{padRight("tokens: " + totalTokens.toLocaleString(), 30)}
|
|
585
|
+
</text>
|
|
512
586
|
</box>
|
|
513
587
|
</box>
|
|
514
588
|
|
|
515
|
-
<box
|
|
589
|
+
<box
|
|
590
|
+
flexDirection="column"
|
|
591
|
+
flexGrow={1}
|
|
592
|
+
marginTop={1}
|
|
593
|
+
border
|
|
594
|
+
borderColor={colors.border}
|
|
595
|
+
padding={1}
|
|
596
|
+
overflow="hidden"
|
|
597
|
+
>
|
|
516
598
|
<text height={1} fg={colors.primary}>
|
|
517
|
-
<strong>
|
|
599
|
+
<strong>
|
|
600
|
+
Sessions ({sessions.length} total, {activeSessions.length} active)
|
|
601
|
+
</strong>
|
|
518
602
|
</text>
|
|
519
603
|
<box flexDirection="row" marginTop={1} height={1}>
|
|
520
|
-
<text width={22} fg={colors.textSubtle}>
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
<text width={12} fg={colors.textSubtle}>
|
|
524
|
-
|
|
604
|
+
<text width={22} fg={colors.textSubtle}>
|
|
605
|
+
{padRight("SESSION ID", 22)}
|
|
606
|
+
</text>
|
|
607
|
+
<text width={12} fg={colors.textSubtle}>
|
|
608
|
+
{padRight("AGENT", 12)}
|
|
609
|
+
</text>
|
|
610
|
+
<text width={10} fg={colors.textSubtle}>
|
|
611
|
+
{padRight("STATUS", 10)}
|
|
612
|
+
</text>
|
|
613
|
+
<text width={12} fg={colors.textSubtle}>
|
|
614
|
+
{padRight("TOKENS", 12)}
|
|
615
|
+
</text>
|
|
616
|
+
<text width={10} fg={colors.textSubtle}>
|
|
617
|
+
{padRight("AGE", 10)}
|
|
618
|
+
</text>
|
|
525
619
|
</box>
|
|
526
620
|
<scrollbox ref={scrollboxRef} flexGrow={1}>
|
|
527
|
-
{sessions.slice(0, 15).map(s => {
|
|
621
|
+
{sessions.slice(0, 15).map((s) => {
|
|
528
622
|
const age = now - s.lastActivityAt;
|
|
529
|
-
const ageStr =
|
|
623
|
+
const ageStr =
|
|
624
|
+
age < 60000 ? `${(age / 1000).toFixed(0)}s` : `${(age / 60000).toFixed(1)}m`;
|
|
530
625
|
return (
|
|
531
626
|
<box key={s.sessionId} flexDirection="row" height={1}>
|
|
532
|
-
<text width={22} fg={colors.text}>
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
<text width={12} fg={colors.text}>
|
|
536
|
-
|
|
627
|
+
<text width={22} fg={colors.text}>
|
|
628
|
+
{padRight(s.sessionId.slice(0, 21), 22)}
|
|
629
|
+
</text>
|
|
630
|
+
<text width={12} fg={colors.text}>
|
|
631
|
+
{padRight(s.agentName, 12)}
|
|
632
|
+
</text>
|
|
633
|
+
<text width={10} fg={s.status === "active" ? colors.success : colors.textMuted}>
|
|
634
|
+
{padRight(s.status, 10)}
|
|
635
|
+
</text>
|
|
636
|
+
<text width={12} fg={colors.text}>
|
|
637
|
+
{padRight((s.totals.input + s.totals.output).toLocaleString(), 12)}
|
|
638
|
+
</text>
|
|
639
|
+
<text width={10} fg={age < 120000 ? colors.success : colors.textMuted}>
|
|
640
|
+
{padRight(ageStr, 10)}
|
|
641
|
+
</text>
|
|
537
642
|
</box>
|
|
538
643
|
);
|
|
539
644
|
})}
|
|
@@ -552,18 +657,21 @@ interface PluginsTabProps {
|
|
|
552
657
|
const TYPE_ORDER: Record<string, number> = { provider: 0, agent: 1, theme: 2, notification: 3 };
|
|
553
658
|
|
|
554
659
|
function isOfficialPlugin(pluginId: string, pluginType: string): boolean {
|
|
555
|
-
const source = pluginRegistry.getSource(
|
|
556
|
-
|
|
660
|
+
const source = pluginRegistry.getSource(
|
|
661
|
+
pluginType as "provider" | "agent" | "theme" | "notification",
|
|
662
|
+
pluginId,
|
|
663
|
+
);
|
|
664
|
+
return source === "builtin";
|
|
557
665
|
}
|
|
558
666
|
|
|
559
667
|
function PluginsTab({ colors, scrollboxRef, sortBy }: PluginsTabProps) {
|
|
560
668
|
const allPlugins = useMemo(() => {
|
|
561
669
|
const plugins = pluginRegistry.getAllPlugins();
|
|
562
670
|
return [...plugins].sort((a, b) => {
|
|
563
|
-
if (sortBy ===
|
|
671
|
+
if (sortBy === "name") {
|
|
564
672
|
return a.name.localeCompare(b.name);
|
|
565
673
|
}
|
|
566
|
-
if (sortBy ===
|
|
674
|
+
if (sortBy === "official") {
|
|
567
675
|
const aOfficial = isOfficialPlugin(a.id, a.type) ? 0 : 1;
|
|
568
676
|
const bOfficial = isOfficialPlugin(b.id, b.type) ? 0 : 1;
|
|
569
677
|
if (aOfficial !== bOfficial) return aOfficial - bOfficial;
|
|
@@ -579,9 +687,7 @@ function PluginsTab({ colors, scrollboxRef, sortBy }: PluginsTabProps) {
|
|
|
579
687
|
const [updates, setUpdates] = useState<Map<string, PluginUpdateInfo>>(new Map());
|
|
580
688
|
|
|
581
689
|
useEffect(() => {
|
|
582
|
-
const npmPlugins = allPlugins.filter(
|
|
583
|
-
(p) => pluginRegistry.getSource(p.type, p.id) === 'npm',
|
|
584
|
-
);
|
|
690
|
+
const npmPlugins = allPlugins.filter((p) => pluginRegistry.getSource(p.type, p.id) === "npm");
|
|
585
691
|
if (npmPlugins.length === 0) return;
|
|
586
692
|
|
|
587
693
|
checkAllPluginUpdates(
|
|
@@ -605,8 +711,8 @@ function PluginsTab({ colors, scrollboxRef, sortBy }: PluginsTabProps) {
|
|
|
605
711
|
};
|
|
606
712
|
|
|
607
713
|
const summary = Object.entries(typeCounts)
|
|
608
|
-
.map(([type, count]) => `${count} ${type}${count !== 1 ?
|
|
609
|
-
.join(
|
|
714
|
+
.map(([type, count]) => `${count} ${type}${count !== 1 ? "s" : ""}`)
|
|
715
|
+
.join(", ");
|
|
610
716
|
|
|
611
717
|
const updatesAvailable = [...updates.values()].filter((u) => u.hasUpdate).length;
|
|
612
718
|
|
|
@@ -615,13 +721,21 @@ function PluginsTab({ colors, scrollboxRef, sortBy }: PluginsTabProps) {
|
|
|
615
721
|
<box paddingLeft={1} height={1} flexShrink={0}>
|
|
616
722
|
<text fg={colors.textMuted}>
|
|
617
723
|
{allPlugins.length} plugins ({summary})
|
|
618
|
-
{updatesAvailable > 0
|
|
724
|
+
{updatesAvailable > 0
|
|
725
|
+
? ` · ${updatesAvailable} update${updatesAvailable !== 1 ? "s" : ""} available`
|
|
726
|
+
: ""}
|
|
619
727
|
</text>
|
|
620
728
|
</box>
|
|
621
729
|
<box flexDirection="row" paddingX={1} height={1} flexShrink={0}>
|
|
622
|
-
<text width={22} fg={colors.textSubtle}>
|
|
623
|
-
|
|
624
|
-
|
|
730
|
+
<text width={22} fg={colors.textSubtle}>
|
|
731
|
+
{padRight("PLUGIN", 22)}
|
|
732
|
+
</text>
|
|
733
|
+
<text width={14} fg={colors.textSubtle}>
|
|
734
|
+
{padRight("TYPE", 14)}
|
|
735
|
+
</text>
|
|
736
|
+
<text width={14} fg={colors.textSubtle}>
|
|
737
|
+
{padRight("VERSION", 14)}
|
|
738
|
+
</text>
|
|
625
739
|
<text fg={colors.textSubtle}>DESCRIPTION</text>
|
|
626
740
|
</box>
|
|
627
741
|
<scrollbox ref={scrollboxRef} flexGrow={1}>
|
|
@@ -631,13 +745,13 @@ function PluginsTab({ colors, scrollboxRef, sortBy }: PluginsTabProps) {
|
|
|
631
745
|
const updateInfo = updates.get(key);
|
|
632
746
|
const official = isOfficialPlugin(plugin.id, plugin.type);
|
|
633
747
|
|
|
634
|
-
let versionSuffix =
|
|
748
|
+
let versionSuffix = "";
|
|
635
749
|
let versionColor = colors.textMuted;
|
|
636
750
|
if (updateInfo?.hasUpdate && updateInfo.latestVersion) {
|
|
637
|
-
versionSuffix =
|
|
751
|
+
versionSuffix = " ↑";
|
|
638
752
|
versionColor = colors.warning;
|
|
639
753
|
} else if (updateInfo && !updateInfo.hasUpdate && updateInfo.latestVersion) {
|
|
640
|
-
versionSuffix =
|
|
754
|
+
versionSuffix = " ✓";
|
|
641
755
|
versionColor = colors.success;
|
|
642
756
|
}
|
|
643
757
|
|
|
@@ -647,9 +761,13 @@ function PluginsTab({ colors, scrollboxRef, sortBy }: PluginsTabProps) {
|
|
|
647
761
|
{official && <span fg={colors.success}>❖ </span>}
|
|
648
762
|
<span fg={colors.text}>{padRight(plugin.name, official ? 20 : 22)}</span>
|
|
649
763
|
</text>
|
|
650
|
-
<text width={14} fg={typeColors[plugin.type] ?? colors.textMuted}>
|
|
651
|
-
|
|
652
|
-
|
|
764
|
+
<text width={14} fg={typeColors[plugin.type] ?? colors.textMuted}>
|
|
765
|
+
{padRight(plugin.type, 14)}
|
|
766
|
+
</text>
|
|
767
|
+
<text width={14} fg={versionColor}>
|
|
768
|
+
{padRight(plugin.version + versionSuffix, 14)}
|
|
769
|
+
</text>
|
|
770
|
+
<text fg={colors.textSubtle}>{plugin.meta?.description ?? ""}</text>
|
|
653
771
|
</box>
|
|
654
772
|
);
|
|
655
773
|
})}
|