@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.
Files changed (158) hide show
  1. package/README.md +2 -5
  2. package/package.json +8 -5
  3. package/src/agents/aggregator.ts +39 -19
  4. package/src/agents/costing.ts +11 -9
  5. package/src/agents/index.ts +7 -9
  6. package/src/agents/types.ts +2 -2
  7. package/src/cli.ts +28 -23
  8. package/src/config/schema.test.ts +42 -40
  9. package/src/config/schema.ts +26 -26
  10. package/src/demo/simulator.ts +161 -122
  11. package/src/plugins/agents/index.ts +2 -2
  12. package/src/plugins/auth-sources.ts +42 -38
  13. package/src/plugins/lifecycle.ts +32 -32
  14. package/src/plugins/loader.test.ts +49 -49
  15. package/src/plugins/loader.ts +67 -63
  16. package/src/plugins/notification-bus.ts +21 -16
  17. package/src/plugins/notifications/index.ts +2 -2
  18. package/src/plugins/notifications/terminal-bell.ts +29 -23
  19. package/src/plugins/notifications/visual-flash.ts +21 -19
  20. package/src/plugins/npm-installer.ts +23 -25
  21. package/src/plugins/plugin-context-factory.ts +10 -10
  22. package/src/plugins/plugin-host.ts +12 -5
  23. package/src/plugins/providers/anthropic.ts +64 -57
  24. package/src/plugins/providers/antigravity.ts +81 -69
  25. package/src/plugins/providers/codex.ts +42 -42
  26. package/src/plugins/providers/gemini.ts +106 -87
  27. package/src/plugins/providers/github-copilot.ts +49 -45
  28. package/src/plugins/providers/index.ts +10 -12
  29. package/src/plugins/providers/minimax.ts +50 -44
  30. package/src/plugins/providers/openai-api.ts +82 -62
  31. package/src/plugins/providers/opencode-zen.ts +46 -39
  32. package/src/plugins/providers/perplexity.ts +55 -46
  33. package/src/plugins/providers/zai.ts +66 -57
  34. package/src/plugins/registry.ts +47 -43
  35. package/src/plugins/sandbox-guard.test.ts +45 -48
  36. package/src/plugins/sandbox-guard.ts +20 -25
  37. package/src/plugins/sandbox.ts +26 -48
  38. package/src/plugins/themes/catppuccin-latte.ts +29 -29
  39. package/src/plugins/themes/catppuccin-mocha.ts +29 -29
  40. package/src/plugins/themes/claude-code.ts +29 -29
  41. package/src/plugins/themes/dracula.ts +29 -29
  42. package/src/plugins/themes/github-light.ts +29 -29
  43. package/src/plugins/themes/gruvbox-dark.ts +29 -29
  44. package/src/plugins/themes/gruvbox-light.ts +29 -29
  45. package/src/plugins/themes/index.ts +15 -15
  46. package/src/plugins/themes/kanagawa.ts +29 -29
  47. package/src/plugins/themes/nord.ts +29 -29
  48. package/src/plugins/themes/one-dark.ts +29 -29
  49. package/src/plugins/themes/opencode.ts +31 -31
  50. package/src/plugins/themes/rose-pine-dawn.ts +29 -29
  51. package/src/plugins/themes/rose-pine.ts +29 -29
  52. package/src/plugins/themes/solarized-light.ts +29 -29
  53. package/src/plugins/themes/tokyo-night.ts +29 -29
  54. package/src/plugins/types/agent.ts +5 -5
  55. package/src/plugins/types/base.ts +35 -24
  56. package/src/plugins/types/index.ts +9 -9
  57. package/src/plugins/types/notification.ts +12 -12
  58. package/src/plugins/types/provider.ts +12 -8
  59. package/src/plugins/types/theme.ts +31 -23
  60. package/src/plugins/update-checker.ts +6 -6
  61. package/src/pricing/estimator.test.ts +39 -44
  62. package/src/pricing/estimator.ts +7 -13
  63. package/src/pricing/fallback.test.ts +30 -34
  64. package/src/pricing/fallback.ts +54 -27
  65. package/src/pricing/index.ts +18 -12
  66. package/src/pricing/models-dev.ts +6 -6
  67. package/src/storage/database.ts +56 -46
  68. package/src/storage/db.ts +12 -12
  69. package/src/storage/index.ts +44 -45
  70. package/src/storage/migrations/index.ts +2 -2
  71. package/src/storage/paths.ts +13 -13
  72. package/src/storage/repos/agentSessions.ts +33 -23
  73. package/src/storage/repos/providerSnapshots.ts +5 -5
  74. package/src/storage/repos/usageEvents.ts +40 -28
  75. package/src/storage/types.ts +5 -5
  76. package/src/tui/App.tsx +180 -137
  77. package/src/tui/components/CommandPalette.tsx +46 -34
  78. package/src/tui/components/DebugConsole.tsx +39 -33
  79. package/src/tui/components/DebugPanel.tsx +295 -177
  80. package/src/tui/components/FooterHints.tsx +2 -2
  81. package/src/tui/components/GhostProviderCard.tsx +37 -35
  82. package/src/tui/components/Header.tsx +68 -35
  83. package/src/tui/components/HelpOverlay.tsx +99 -29
  84. package/src/tui/components/InlineGauge.tsx +10 -7
  85. package/src/tui/components/InlineSparkline.tsx +17 -11
  86. package/src/tui/components/KpiStrip.tsx +128 -98
  87. package/src/tui/components/LimitGauge.tsx +96 -59
  88. package/src/tui/components/ModalBackdrop.tsx +4 -4
  89. package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
  90. package/src/tui/components/ProviderCard.tsx +232 -226
  91. package/src/tui/components/ProviderDetailPanel.tsx +133 -58
  92. package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
  93. package/src/tui/components/ProvidersList.tsx +215 -126
  94. package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
  95. package/src/tui/components/SessionsTable.tsx +313 -166
  96. package/src/tui/components/SettingsModal.tsx +231 -214
  97. package/src/tui/components/SidebarBreakdown.tsx +30 -16
  98. package/src/tui/components/Skeleton.tsx +24 -17
  99. package/src/tui/components/SmartSidebar.tsx +360 -258
  100. package/src/tui/components/Sparkline.tsx +64 -52
  101. package/src/tui/components/Spinner.tsx +3 -3
  102. package/src/tui/components/StatusBar.tsx +27 -27
  103. package/src/tui/components/ThemePicker.tsx +67 -66
  104. package/src/tui/components/Toast.tsx +9 -6
  105. package/src/tui/components/UsageGauge.tsx +10 -13
  106. package/src/tui/components/index.ts +5 -5
  107. package/src/tui/contexts/AgentSessionContext.tsx +193 -144
  108. package/src/tui/contexts/ConfigContext.tsx +15 -13
  109. package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
  110. package/src/tui/contexts/DemoModeContext.tsx +22 -15
  111. package/src/tui/contexts/DrawerContext.tsx +11 -9
  112. package/src/tui/contexts/InputContext.tsx +2 -2
  113. package/src/tui/contexts/LogContext.tsx +62 -47
  114. package/src/tui/contexts/PluginContext.tsx +232 -183
  115. package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
  116. package/src/tui/contexts/StorageContext.tsx +151 -117
  117. package/src/tui/contexts/ThemeContext.tsx +21 -16
  118. package/src/tui/contexts/TimeWindowContext.tsx +72 -61
  119. package/src/tui/contexts/ToastContext.tsx +10 -7
  120. package/src/tui/contexts/index.ts +9 -5
  121. package/src/tui/createApp.tsx +8 -12
  122. package/src/tui/debug/captureFrame.ts +25 -28
  123. package/src/tui/debug/snapshot.tsx +179 -162
  124. package/src/tui/driver/assertions.ts +30 -22
  125. package/src/tui/driver/cli.ts +231 -221
  126. package/src/tui/driver/coverage.ts +27 -44
  127. package/src/tui/driver/demo-snapshot.ts +33 -33
  128. package/src/tui/driver/diff.ts +26 -32
  129. package/src/tui/driver/driver.ts +43 -48
  130. package/src/tui/driver/index.ts +36 -40
  131. package/src/tui/driver/recorder.ts +37 -48
  132. package/src/tui/driver/test-workflow.ts +51 -51
  133. package/src/tui/driver/test.ts +25 -25
  134. package/src/tui/hooks/index.ts +24 -20
  135. package/src/tui/hooks/useAnimatedValue.ts +13 -18
  136. package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
  137. package/src/tui/hooks/useDashboardState.ts +26 -24
  138. package/src/tui/hooks/useEmaActivity.ts +82 -74
  139. package/src/tui/hooks/useEntranceAnimation.ts +11 -11
  140. package/src/tui/hooks/useExitAnimation.ts +3 -3
  141. package/src/tui/hooks/usePulse.ts +4 -4
  142. package/src/tui/hooks/useSafeRenderer.ts +5 -5
  143. package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
  144. package/src/tui/hooks/useValueFlash.ts +10 -14
  145. package/src/tui/index.tsx +29 -6
  146. package/src/tui/shutdown.ts +19 -0
  147. package/src/tui/utils/providerColor.ts +1 -1
  148. package/src/tui/views/Dashboard.tsx +127 -102
  149. package/src/tui/views/HistoricalTrendsView.tsx +579 -250
  150. package/src/tui/views/ProjectsView.tsx +466 -237
  151. package/src/tui/views/RealTimeDashboard.tsx +150 -111
  152. package/src/tui/views/SettingsView.tsx +222 -191
  153. package/src/tui/views/index.ts +2 -2
  154. package/src/types/opentui-test-utils.d.ts +7 -7
  155. package/src/utils/clipboard.ts +29 -29
  156. package/src/utils/currency.test.ts +53 -53
  157. package/src/utils/currency.ts +35 -32
  158. package/src/version.ts +1 -1
package/src/tui/App.tsx CHANGED
@@ -1,39 +1,46 @@
1
- import { useState, useEffect, useCallback, useMemo } from 'react';
2
- import { useKeyboard } from '@opentui/react';
3
-
4
- import { captureFrameToFile, createBurstRecorder, type BurstRecorder } from './debug/captureFrame.ts';
5
- import { pluginLifecycle } from '@/plugins/lifecycle.ts';
6
- import { ThemeProvider, useColors, useTheme, resolveTheme } from './contexts/ThemeContext.tsx';
7
- import * as builtinThemeExports from '@/plugins/themes/index.ts';
8
- import type { ThemePlugin } from '@/plugins/types/theme.ts';
1
+ import { useKeyboard } from "@opentui/react";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import * as builtinThemeExports from "@/plugins/themes/index.ts";
4
+ import type { ThemePlugin } from "@/plugins/types/theme.ts";
5
+ import { resolveTheme, ThemeProvider, useColors, useTheme } from "./contexts/ThemeContext.tsx";
6
+ import {
7
+ type BurstRecorder,
8
+ captureFrameToFile,
9
+ createBurstRecorder,
10
+ } from "./debug/captureFrame.ts";
9
11
 
10
12
  const builtinThemeList = Object.values(builtinThemeExports) as ThemePlugin[];
11
- import { PluginProvider, usePlugins } from './contexts/PluginContext.tsx';
12
- import { LogProvider, useLogs } from './contexts/LogContext.tsx';
13
- import { InputProvider, useInputFocus } from './contexts/InputContext.tsx';
14
- import { AgentSessionProvider, useAgentSessions } from './contexts/AgentSessionContext.tsx';
15
- import { StorageProvider } from './contexts/StorageContext.tsx';
16
- import { TimeWindowProvider } from './contexts/TimeWindowContext.tsx';
17
- import { ConfigProvider, useConfig } from './contexts/ConfigContext.tsx';
18
- import { DrawerProvider, useDrawer } from './contexts/DrawerContext.tsx';
19
- import { SessionDetailsDrawer } from './components/SessionDetailsDrawer.tsx';
20
- import { Header } from './components/Header.tsx';
21
- import { StatusBar } from './components/StatusBar.tsx';
22
- import { Toast } from './components/Toast.tsx';
23
- import { ToastProvider, useToastContext } from './contexts/ToastContext.tsx';
24
- import { DashboardRuntimeProvider, useDashboardRuntime } from './contexts/DashboardRuntimeContext.tsx';
25
- import { RealTimeActivityProvider } from './contexts/RealTimeActivityContext.tsx';
26
- import { RealTimeDashboard } from './views/RealTimeDashboard.tsx';
27
- import { Dashboard } from './views/Dashboard.tsx';
28
- import { HistoricalTrendsView } from './views/HistoricalTrendsView.tsx';
29
- import { ProjectsView } from './views/ProjectsView.tsx';
30
- import { CommandPalette, type CommandAction } from './components/CommandPalette.tsx';
31
- import { SettingsModal } from './components/SettingsModal.tsx';
32
- import { DebugPanel } from './components/DebugPanel.tsx';
33
- import { copyToClipboard } from '@/utils/clipboard.ts';
34
- import { useSafeRenderer } from './hooks/useSafeRenderer.ts';
35
- import type { DemoPreset } from '@/demo/simulator.ts';
36
- import { DemoModeProvider, useDemoMode } from './contexts/DemoModeContext.tsx';
13
+
14
+ import type { DemoPreset } from "@/demo/simulator.ts";
15
+ import { copyToClipboard } from "@/utils/clipboard.ts";
16
+ import { type CommandAction, CommandPalette } from "./components/CommandPalette.tsx";
17
+ import { DebugPanel } from "./components/DebugPanel.tsx";
18
+ import { Header } from "./components/Header.tsx";
19
+ import { SessionDetailsDrawer } from "./components/SessionDetailsDrawer.tsx";
20
+ import { SettingsModal } from "./components/SettingsModal.tsx";
21
+ import { StatusBar } from "./components/StatusBar.tsx";
22
+ import { Toast } from "./components/Toast.tsx";
23
+ import { AgentSessionProvider, useAgentSessions } from "./contexts/AgentSessionContext.tsx";
24
+ import { ConfigProvider, useConfig } from "./contexts/ConfigContext.tsx";
25
+ import {
26
+ DashboardRuntimeProvider,
27
+ useDashboardRuntime,
28
+ } from "./contexts/DashboardRuntimeContext.tsx";
29
+ import { DemoModeProvider, useDemoMode } from "./contexts/DemoModeContext.tsx";
30
+ import { DrawerProvider, useDrawer } from "./contexts/DrawerContext.tsx";
31
+ import { InputProvider, useInputFocus } from "./contexts/InputContext.tsx";
32
+ import { LogProvider, useLogs } from "./contexts/LogContext.tsx";
33
+ import { PluginProvider, usePlugins } from "./contexts/PluginContext.tsx";
34
+ import { RealTimeActivityProvider } from "./contexts/RealTimeActivityContext.tsx";
35
+ import { StorageProvider } from "./contexts/StorageContext.tsx";
36
+ import { TimeWindowProvider } from "./contexts/TimeWindowContext.tsx";
37
+ import { ToastProvider, useToastContext } from "./contexts/ToastContext.tsx";
38
+ import { useSafeRenderer } from "./hooks/useSafeRenderer.ts";
39
+ import { triggerShutdown } from "./shutdown.ts";
40
+ import { Dashboard } from "./views/Dashboard.tsx";
41
+ import { HistoricalTrendsView } from "./views/HistoricalTrendsView.tsx";
42
+ import { ProjectsView } from "./views/ProjectsView.tsx";
43
+ import { RealTimeDashboard } from "./views/RealTimeDashboard.tsx";
37
44
 
38
45
  interface AppProps {
39
46
  debug?: boolean;
@@ -43,7 +50,7 @@ interface AppProps {
43
50
  cliPlugins?: string[];
44
51
  }
45
52
 
46
- type View = 'dashboard' | 'providers' | 'trends' | 'projects';
53
+ type View = "dashboard" | "providers" | "trends" | "projects";
47
54
 
48
55
  function AppContent() {
49
56
  const renderer = useSafeRenderer();
@@ -63,53 +70,47 @@ function AppContent() {
63
70
  const refreshInterval = config.refresh.pauseAutoRefresh ? 0 : config.refresh.intervalMs;
64
71
 
65
72
  const [lastRefresh, setLastRefresh] = useState<number | null>(null);
66
- const [activeView, setActiveView] = useState<View>('dashboard');
73
+ const [activeView, setActiveView] = useState<View>("dashboard");
67
74
  const [showCommandPalette, setShowCommandPalette] = useState(false);
68
75
  const [showSettings, setShowSettings] = useState(false);
69
76
  const [showDebugPanel, setShowDebugPanel] = useState(false);
70
77
 
71
78
  const isModalOpen = showCommandPalette || showSettings || showDebugPanel || isDrawerOpen;
72
79
 
73
- const shutdownPromiseRef = { current: null as Promise<void> | null };
74
- const gracefulShutdown = useCallback(async () => {
75
- if (shutdownPromiseRef.current) return shutdownPromiseRef.current;
76
- shutdownPromiseRef.current = (async () => {
77
- await pluginLifecycle.stopAll();
78
- await pluginLifecycle.destroyAll();
79
- renderer?.destroy();
80
- })();
81
- return shutdownPromiseRef.current;
82
- }, [renderer]);
83
-
84
- const inspectorData = useMemo(() => ({
85
- sessions: sessions.map(s => ({
86
- sessionId: s.sessionId,
87
- agentName: s.agentName,
88
- status: s.status,
89
- totals: s.totals,
90
- lastActivityAt: s.lastActivityAt,
91
- })),
92
- debugData: debugDataRef.current,
93
- activity,
94
- sparkData,
95
- }), [sessions, debugDataRef, activity, sparkData]);
80
+ const gracefulShutdown = useCallback(() => triggerShutdown(), []);
81
+
82
+ const inspectorData = useMemo(
83
+ () => ({
84
+ sessions: sessions.map((s) => ({
85
+ sessionId: s.sessionId,
86
+ agentName: s.agentName,
87
+ status: s.status,
88
+ totals: s.totals,
89
+ lastActivityAt: s.lastActivityAt,
90
+ })),
91
+ debugData: debugDataRef.current,
92
+ activity,
93
+ sparkData,
94
+ }),
95
+ [sessions, debugDataRef, activity, sparkData],
96
+ );
96
97
 
97
98
  const handleCaptureFrame = useCallback(async () => {
98
99
  if (!renderer) {
99
- showToast('No renderer available', 'error');
100
+ showToast("No renderer available", "error");
100
101
  return;
101
102
  }
102
103
  try {
103
- const result = await captureFrameToFile(renderer, 'manual');
104
+ const result = await captureFrameToFile(renderer, "manual");
104
105
  info(`Frame captured: ${result.framePath}`);
105
106
  if (config.notifications.toastsEnabled) {
106
- showToast('Frame captured');
107
+ showToast("Frame captured");
107
108
  }
108
109
  } catch (err) {
109
- const msg = err instanceof Error ? err.message : 'Unknown error';
110
+ const msg = err instanceof Error ? err.message : "Unknown error";
110
111
  info(`Frame capture failed: ${msg}`);
111
112
  if (config.notifications.toastsEnabled) {
112
- showToast('Capture failed', 'error');
113
+ showToast("Capture failed", "error");
113
114
  }
114
115
  }
115
116
  }, [renderer, info, showToast, config.notifications.toastsEnabled]);
@@ -127,13 +128,15 @@ function AppContent() {
127
128
  }
128
129
 
129
130
  burstRecorderRef.current = createBurstRecorder(renderer, { frameCount: 10, minInterval: 200 });
130
- info('Burst recording started (10 frames)');
131
+ info("Burst recording started (10 frames)");
131
132
  if (config.notifications.toastsEnabled) {
132
- showToast('Recording burst...');
133
+ showToast("Recording burst...");
133
134
  }
134
135
 
135
136
  const frames = await burstRecorderRef.current.start();
136
- info(`Burst complete: ${frames.length} frames in ${frames[0]?.framePath.split('/').slice(0, -1).join('/')}`);
137
+ info(
138
+ `Burst complete: ${frames.length} frames in ${frames[0]?.framePath.split("/").slice(0, -1).join("/")}`,
139
+ );
137
140
  if (config.notifications.toastsEnabled) {
138
141
  showToast(`Burst: ${frames.length} frames`);
139
142
  }
@@ -148,11 +151,11 @@ function AppContent() {
148
151
  try {
149
152
  await copyToClipboard(text);
150
153
  if (config.notifications.toastsEnabled) {
151
- showToast('Copied to clipboard');
154
+ showToast("Copied to clipboard");
152
155
  }
153
156
  } catch {
154
157
  if (config.notifications.toastsEnabled) {
155
- showToast('Copy failed', 'error');
158
+ showToast("Copy failed", "error");
156
159
  }
157
160
  }
158
161
  renderer.clearSelection();
@@ -161,37 +164,87 @@ function AppContent() {
161
164
 
162
165
  const commands: CommandAction[] = useMemo(() => {
163
166
  const base: CommandAction[] = [
164
- { id: 'view-dashboard', label: 'Go to Dashboard', shortcut: '1', action: () => setActiveView('dashboard') },
165
- { id: 'view-providers', label: 'Go to Providers', shortcut: '2', action: () => setActiveView('providers') },
166
- { id: 'view-trends', label: 'Go to Trends', shortcut: '3', action: () => setActiveView('trends') },
167
- { id: 'view-projects', label: 'Go to Projects', shortcut: '4', action: () => setActiveView('projects') },
168
- { id: 'open-settings', label: 'Open Settings', shortcut: ',', action: () => setShowSettings(true) },
169
- { id: 'refresh', label: 'Refresh Data', shortcut: 'r', action: () => {
170
- if (isInitialized) {
171
- info('Manual refresh triggered');
172
- refreshAllProviders().then(() => setLastRefresh(Date.now()));
173
- }
174
- }},
175
- { id: 'toggle-debug', label: 'Toggle Debug Panel', shortcut: '~', action: () => setShowDebugPanel(prev => !prev) },
176
- { id: 'capture-frame', label: 'Capture Frame', shortcut: 'Ctrl+P', action: () => handleCaptureFrame() },
177
- { id: 'quit', label: 'Quit', shortcut: 'q', action: () => void gracefulShutdown() },
167
+ {
168
+ id: "view-dashboard",
169
+ label: "Go to Dashboard",
170
+ shortcut: "1",
171
+ action: () => setActiveView("dashboard"),
172
+ },
173
+ {
174
+ id: "view-providers",
175
+ label: "Go to Providers",
176
+ shortcut: "2",
177
+ action: () => setActiveView("providers"),
178
+ },
179
+ {
180
+ id: "view-trends",
181
+ label: "Go to Trends",
182
+ shortcut: "3",
183
+ action: () => setActiveView("trends"),
184
+ },
185
+ {
186
+ id: "view-projects",
187
+ label: "Go to Projects",
188
+ shortcut: "4",
189
+ action: () => setActiveView("projects"),
190
+ },
191
+ {
192
+ id: "open-settings",
193
+ label: "Open Settings",
194
+ shortcut: ",",
195
+ action: () => setShowSettings(true),
196
+ },
197
+ {
198
+ id: "refresh",
199
+ label: "Refresh Data",
200
+ shortcut: "r",
201
+ action: () => {
202
+ if (isInitialized) {
203
+ info("Manual refresh triggered");
204
+ refreshAllProviders().then(() => setLastRefresh(Date.now()));
205
+ }
206
+ },
207
+ },
208
+ {
209
+ id: "toggle-debug",
210
+ label: "Toggle Debug Panel",
211
+ shortcut: "~",
212
+ action: () => setShowDebugPanel((prev) => !prev),
213
+ },
214
+ {
215
+ id: "capture-frame",
216
+ label: "Capture Frame",
217
+ shortcut: "Ctrl+P",
218
+ action: () => handleCaptureFrame(),
219
+ },
220
+ { id: "quit", label: "Quit", shortcut: "q", action: () => void gracefulShutdown() },
178
221
  ];
179
222
 
180
- const themeCommands: CommandAction[] = themes.map(t => ({
223
+ const themeCommands: CommandAction[] = themes.map((t) => ({
181
224
  id: `theme-${t.id}`,
182
225
  label: `Theme: ${t.name}`,
183
226
  action: () => updateConfig({ ...config, display: { ...config.display, theme: t.id } }),
184
227
  }));
185
228
 
186
229
  return [...base, ...themeCommands];
187
- }, [isInitialized, refreshAllProviders, info, handleCaptureFrame, renderer, gracefulShutdown, themes, config, updateConfig]);
230
+ }, [
231
+ isInitialized,
232
+ refreshAllProviders,
233
+ info,
234
+ handleCaptureFrame,
235
+ renderer,
236
+ gracefulShutdown,
237
+ themes,
238
+ config,
239
+ updateConfig,
240
+ ]);
188
241
 
189
242
  useKeyboard((key) => {
190
- if (key.ctrl && key.name === 'p') {
243
+ if (key.ctrl && key.name === "p") {
191
244
  handleCaptureFrame();
192
245
  return;
193
246
  }
194
- if (key.ctrl && key.shift && key.name === 'p') {
247
+ if (key.ctrl && key.shift && key.name === "p") {
195
248
  handleBurstRecord();
196
249
  return;
197
250
  }
@@ -204,35 +257,35 @@ function AppContent() {
204
257
  return;
205
258
  }
206
259
 
207
- if (key.name === '1') {
208
- setActiveView('dashboard');
260
+ if (key.name === "1") {
261
+ setActiveView("dashboard");
209
262
  }
210
- if (key.name === '2') {
211
- setActiveView('providers');
263
+ if (key.name === "2") {
264
+ setActiveView("providers");
212
265
  }
213
- if (key.name === '3') {
214
- setActiveView('trends');
266
+ if (key.name === "3") {
267
+ setActiveView("trends");
215
268
  }
216
- if (key.name === '4') {
217
- setActiveView('projects');
269
+ if (key.name === "4") {
270
+ setActiveView("projects");
218
271
  }
219
- if (key.sequence === ',') {
272
+ if (key.sequence === ",") {
220
273
  setShowSettings(true);
221
274
  }
222
275
 
223
- if (key.sequence === ':' || (key.shift && key.name === ';')) {
276
+ if (key.sequence === ":" || (key.shift && key.name === ";")) {
224
277
  setShowCommandPalette(true);
225
278
  return;
226
279
  }
227
280
 
228
- if (key.name === 'q' || (key.ctrl && key.name === 'c')) {
281
+ if (key.name === "q" || (key.ctrl && key.name === "c")) {
229
282
  void gracefulShutdown();
230
283
  }
231
- if (key.name === 'r' && isInitialized) {
232
- info('Manual refresh triggered');
284
+ if (key.name === "r" && isInitialized) {
285
+ info("Manual refresh triggered");
233
286
  refreshAllProviders().then(() => setLastRefresh(Date.now()));
234
287
  }
235
- if (key.sequence === '~' || (key.shift && key.name === 'd')) {
288
+ if (key.sequence === "~" || (key.shift && key.name === "d")) {
236
289
  setShowDebugPanel(true);
237
290
  }
238
291
  });
@@ -252,7 +305,7 @@ function AppContent() {
252
305
 
253
306
  useEffect(() => {
254
307
  if (isInitialized) {
255
- info('Application initialized');
308
+ info("Application initialized");
256
309
  refreshAllProviders().then(() => setLastRefresh(Date.now()));
257
310
  }
258
311
  }, [isInitialized]);
@@ -261,7 +314,7 @@ function AppContent() {
261
314
  if (!isInitialized || refreshInterval <= 0) return;
262
315
 
263
316
  const interval = setInterval(() => {
264
- info('Auto-refresh triggered');
317
+ info("Auto-refresh triggered");
265
318
  refreshAllProviders().then(() => setLastRefresh(Date.now()));
266
319
  }, refreshInterval);
267
320
 
@@ -277,15 +330,12 @@ function AppContent() {
277
330
  onMouseUp={handleMouseUp}
278
331
  position="relative"
279
332
  >
280
- <Header
281
- activeView={activeView}
282
- demoMode={demoMode}
283
- />
333
+ <Header activeView={activeView} demoMode={demoMode} />
284
334
 
285
- {activeView === 'dashboard' && <RealTimeDashboard />}
286
- {activeView === 'providers' && <Dashboard />}
287
- {activeView === 'trends' && <HistoricalTrendsView />}
288
- {activeView === 'projects' && <ProjectsView />}
335
+ {activeView === "dashboard" && <RealTimeDashboard />}
336
+ {activeView === "providers" && <Dashboard />}
337
+ {activeView === "trends" && <HistoricalTrendsView />}
338
+ {activeView === "projects" && <ProjectsView />}
289
339
 
290
340
  <StatusBar
291
341
  lastRefresh={lastRefresh ?? 0}
@@ -294,36 +344,21 @@ function AppContent() {
294
344
  />
295
345
 
296
346
  {toast && config.notifications.toastsEnabled && (
297
- <Toast
298
- message={toast.message}
299
- type={toast.type}
300
- onDismiss={dismissToast}
301
- />
347
+ <Toast message={toast.message} type={toast.type} onDismiss={dismissToast} />
302
348
  )}
303
349
 
304
350
  {showCommandPalette && (
305
- <CommandPalette
306
- commands={commands}
307
- onClose={() => setShowCommandPalette(false)}
308
- />
351
+ <CommandPalette commands={commands} onClose={() => setShowCommandPalette(false)} />
309
352
  )}
310
353
 
311
- {showSettings && (
312
- <SettingsModal onClose={() => setShowSettings(false)} />
313
- )}
354
+ {showSettings && <SettingsModal onClose={() => setShowSettings(false)} />}
314
355
 
315
356
  {showDebugPanel && (
316
- <DebugPanel
317
- onClose={() => setShowDebugPanel(false)}
318
- inspectorData={inspectorData}
319
- />
357
+ <DebugPanel onClose={() => setShowDebugPanel(false)} inspectorData={inspectorData} />
320
358
  )}
321
359
 
322
360
  {isDrawerOpen && selectedSession && (
323
- <SessionDetailsDrawer
324
- session={selectedSession}
325
- onClose={hideDrawer}
326
- />
361
+ <SessionDetailsDrawer session={selectedSession} onClose={hideDrawer} />
327
362
  )}
328
363
  </box>
329
364
  );
@@ -336,7 +371,7 @@ function ConfiguredApp({ cliPlugins }: { cliPlugins?: string[] }) {
336
371
  return null;
337
372
  }
338
373
 
339
- const initialTheme = builtinThemeList.find(t => t.id === config.display.theme);
374
+ const initialTheme = builtinThemeList.find((t) => t.id === config.display.theme);
340
375
  const themeProps = initialTheme ? { initialTheme } : {};
341
376
 
342
377
  return (
@@ -347,9 +382,9 @@ function ConfiguredApp({ cliPlugins }: { cliPlugins?: string[] }) {
347
382
  <RealTimeActivityProvider>
348
383
  <AgentSessionProvider autoRefresh={true} refreshInterval={1000}>
349
384
  <DashboardRuntimeProvider>
350
- <DrawerProvider>
351
- <AppContent />
352
- </DrawerProvider>
385
+ <DrawerProvider>
386
+ <AppContent />
387
+ </DrawerProvider>
353
388
  </DashboardRuntimeProvider>
354
389
  </AgentSessionProvider>
355
390
  </RealTimeActivityProvider>
@@ -360,8 +395,16 @@ function ConfiguredApp({ cliPlugins }: { cliPlugins?: string[] }) {
360
395
  );
361
396
  }
362
397
 
363
- export function App({ debug = false, demoMode = false, demoSeed, demoPreset, cliPlugins }: AppProps) {
364
- const demoProviderProps: { demoMode: boolean; demoSeed?: number; demoPreset?: DemoPreset } = { demoMode };
398
+ export function App({
399
+ debug = false,
400
+ demoMode = false,
401
+ demoSeed,
402
+ demoPreset,
403
+ cliPlugins,
404
+ }: AppProps) {
405
+ const demoProviderProps: { demoMode: boolean; demoSeed?: number; demoPreset?: DemoPreset } = {
406
+ demoMode,
407
+ };
365
408
  if (demoSeed !== undefined) demoProviderProps.demoSeed = demoSeed;
366
409
  if (demoPreset !== undefined) demoProviderProps.demoPreset = demoPreset;
367
410
 
@@ -1,8 +1,8 @@
1
- import { useState, useEffect, useMemo } from 'react';
2
- import { useKeyboard, useTerminalDimensions } from '@opentui/react';
3
- import { RGBA } from '@opentui/core';
4
- import { useColors } from '../contexts/ThemeContext.tsx';
5
- import { useInputFocus } from '../contexts/InputContext.tsx';
1
+ import { RGBA } from "@opentui/core";
2
+ import { useKeyboard, useTerminalDimensions } from "@opentui/react";
3
+ import { useEffect, useMemo, useState } from "react";
4
+ import { useInputFocus } from "../contexts/InputContext.tsx";
5
+ import { useColors } from "../contexts/ThemeContext.tsx";
6
6
 
7
7
  const OVERLAY_BG = RGBA.fromValues(0.0, 0.0, 0.0, 0.5);
8
8
 
@@ -22,7 +22,7 @@ export function CommandPalette({ commands, onClose }: CommandPaletteProps) {
22
22
  const colors = useColors();
23
23
  const { width: termWidth, height: termHeight } = useTerminalDimensions();
24
24
  const { setInputFocused } = useInputFocus();
25
- const [query, setQuery] = useState('');
25
+ const [query, setQuery] = useState("");
26
26
  const [selectedIndex, setSelectedIndex] = useState(0);
27
27
 
28
28
  useEffect(() => {
@@ -33,9 +33,9 @@ export function CommandPalette({ commands, onClose }: CommandPaletteProps) {
33
33
  const filteredCommands = useMemo(() => {
34
34
  if (!query) return commands;
35
35
  const lowerQuery = query.toLowerCase();
36
- return commands.filter(cmd =>
37
- cmd.label.toLowerCase().includes(lowerQuery) ||
38
- cmd.id.toLowerCase().includes(lowerQuery)
36
+ return commands.filter(
37
+ (cmd) =>
38
+ cmd.label.toLowerCase().includes(lowerQuery) || cmd.id.toLowerCase().includes(lowerQuery),
39
39
  );
40
40
  }, [commands, query]);
41
41
 
@@ -49,12 +49,12 @@ export function CommandPalette({ commands, onClose }: CommandPaletteProps) {
49
49
  }, [query]);
50
50
 
51
51
  useKeyboard((key) => {
52
- if (key.name === 'escape') {
52
+ if (key.name === "escape") {
53
53
  onClose();
54
54
  return;
55
55
  }
56
56
 
57
- if (key.name === 'return') {
57
+ if (key.name === "return") {
58
58
  const selected = filteredCommands[selectedIndex];
59
59
  if (selected) {
60
60
  onClose();
@@ -63,9 +63,9 @@ export function CommandPalette({ commands, onClose }: CommandPaletteProps) {
63
63
  return;
64
64
  }
65
65
 
66
- if (query === '' && !key.ctrl && !key.meta) {
67
- const keyStr = key.sequence || '';
68
- const matchingCmd = commands.find(cmd => cmd.shortcut === keyStr);
66
+ if (query === "" && !key.ctrl && !key.meta) {
67
+ const keyStr = key.sequence || "";
68
+ const matchingCmd = commands.find((cmd) => cmd.shortcut === keyStr);
69
69
  if (matchingCmd) {
70
70
  onClose();
71
71
  matchingCmd.action();
@@ -73,23 +73,31 @@ export function CommandPalette({ commands, onClose }: CommandPaletteProps) {
73
73
  }
74
74
  }
75
75
 
76
- if (key.name === 'down' || (key.ctrl && key.name === 'n') || (query === '' && key.name === 'j')) {
77
- setSelectedIndex(i => Math.min(i + 1, filteredCommands.length - 1));
76
+ if (
77
+ key.name === "down" ||
78
+ (key.ctrl && key.name === "n") ||
79
+ (query === "" && key.name === "j")
80
+ ) {
81
+ setSelectedIndex((i) => Math.min(i + 1, filteredCommands.length - 1));
78
82
  return;
79
83
  }
80
84
 
81
- if (key.name === 'up' || (key.ctrl && key.name === 'p') || (query === '' && key.name === 'k')) {
82
- setSelectedIndex(i => Math.max(i - 1, 0));
85
+ if (key.name === "up" || (key.ctrl && key.name === "p") || (query === "" && key.name === "k")) {
86
+ setSelectedIndex((i) => Math.max(i - 1, 0));
83
87
  return;
84
88
  }
85
89
 
86
- if (key.name === 'backspace') {
87
- setQuery(q => q.slice(0, -1));
90
+ if (key.name === "backspace") {
91
+ setQuery((q) => q.slice(0, -1));
88
92
  return;
89
93
  }
90
94
 
91
- if (key.sequence && key.sequence.length === 1 && /^[\w\-_., ~:;!@#$%^&*()]$/.test(key.sequence)) {
92
- setQuery(q => q + key.sequence);
95
+ if (
96
+ key.sequence &&
97
+ key.sequence.length === 1 &&
98
+ /^[\w\-_., ~:;!@#$%^&*()]$/.test(key.sequence)
99
+ ) {
100
+ setQuery((q) => q + key.sequence);
93
101
  return;
94
102
  }
95
103
  });
@@ -128,20 +136,22 @@ export function CommandPalette({ commands, onClose }: CommandPaletteProps) {
128
136
  <box flexDirection="column">
129
137
  {filteredCommands.length === 0 ? (
130
138
  <box padding={1} height={1}>
131
- <text height={1} fg={colors.textMuted}>No matching commands</text>
139
+ <text height={1} fg={colors.textMuted}>
140
+ No matching commands
141
+ </text>
132
142
  </box>
133
143
  ) : (
134
144
  filteredCommands.map((cmd, idx) => {
135
145
  const isSelected = idx === selectedIndex;
136
146
  return (
137
- <box
138
- key={cmd.id}
139
- flexDirection="row"
140
- justifyContent="space-between"
141
- paddingX={1}
142
- height={1}
143
- {...(isSelected ? { backgroundColor: colors.primary } : {})}
144
- >
147
+ <box
148
+ key={cmd.id}
149
+ flexDirection="row"
150
+ justifyContent="space-between"
151
+ paddingX={1}
152
+ height={1}
153
+ {...(isSelected ? { backgroundColor: colors.primary } : {})}
154
+ >
145
155
  <text height={1} fg={isSelected ? colors.background : colors.text}>
146
156
  {cmd.label}
147
157
  </text>
@@ -157,9 +167,11 @@ export function CommandPalette({ commands, onClose }: CommandPaletteProps) {
157
167
  </box>
158
168
  </scrollbox>
159
169
 
160
- <box paddingX={1} height={1}>
161
- <text height={1} fg={colors.textSubtle}>↑↓ navigate Enter select Esc close</text>
162
- </box>
170
+ <box paddingX={1} height={1}>
171
+ <text height={1} fg={colors.textSubtle}>
172
+ ↑↓ navigate Enter select Esc close
173
+ </text>
174
+ </box>
163
175
  </box>
164
176
  </box>
165
177
  );