@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
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env bun
2
+
2
3
  /**
3
4
  * Headless Snapshot Script for Component Isolation Testing
4
- *
5
+ *
5
6
  * Renders components in a headless environment and captures the output.
6
7
  * Useful for debugging layout issues without running the full app.
7
- *
8
+ *
8
9
  * Usage:
9
10
  * bun src/tui/debug/snapshot.tsx [component] [options]
10
- *
11
+ *
11
12
  * Examples:
12
13
  * bun src/tui/debug/snapshot.tsx --list # List all available components
13
14
  * bun src/tui/debug/snapshot.tsx header # Snapshot Header
@@ -15,49 +16,48 @@
15
16
  * bun src/tui/debug/snapshot.tsx kpi-strip # Snapshot KpiStrip
16
17
  * bun src/tui/debug/snapshot.tsx --width 120 --height 40 # Custom dimensions
17
18
  * bun src/tui/debug/snapshot.tsx toast --output frame.txt # Custom output file
18
- *
19
+ *
19
20
  * Adding New Components:
20
21
  * 1. Create a mock data factory function: createMock<ComponentName>Props()
21
22
  * 2. Register it in COMPONENT_REGISTRY with render function
22
23
  * 3. That's it! The component will be available via CLI
23
24
  */
24
25
 
25
- import * as fs from 'fs/promises';
26
- import * as path from 'path';
27
- import type { ReactNode } from 'react';
28
- import { testRender } from '@opentui/react/test-utils';
29
- import { ThemeProvider } from '../contexts/ThemeContext.tsx';
30
- import { LogProvider } from '../contexts/LogContext.tsx';
31
- import { getFramesDir } from './captureFrame.ts';
32
-
33
- import { Header } from '../components/Header.tsx';
34
- import { StatusBar } from '../components/StatusBar.tsx';
35
- import { ProviderCard } from '../components/ProviderCard.tsx';
36
- import { UsageGauge } from '../components/UsageGauge.tsx';
37
- import { Toast } from '../components/Toast.tsx';
38
- import { Spinner } from '../components/Spinner.tsx';
39
- import { SkeletonText, SkeletonGauge, SkeletonProviderContent } from '../components/Skeleton.tsx';
40
- import { DebugConsole } from '../components/DebugConsole.tsx';
41
- import { KpiStrip } from '../components/KpiStrip.tsx';
42
- import { HistoricalTrendsView } from '../views/HistoricalTrendsView.tsx';
43
- import { ProjectsView } from '../views/ProjectsView.tsx';
44
- import { SettingsView } from '../views/SettingsView.tsx';
45
- import { SessionDetailsDrawer } from '../components/SessionDetailsDrawer.tsx';
46
- import { AgentSessionProvider } from '../contexts/AgentSessionContext.tsx';
47
- import { TimeWindowProvider } from '../contexts/TimeWindowContext.tsx';
48
- import { StorageProvider } from '../contexts/StorageContext.tsx';
49
- import { InputProvider } from '../contexts/InputContext.tsx';
50
- import { PluginProvider } from '../contexts/PluginContext.tsx';
51
- import { ToastProvider } from '../contexts/ToastContext.tsx';
52
- import { ConfigProvider } from '../contexts/ConfigContext.tsx';
53
- import { DEFAULT_CONFIG } from '@/config/schema.ts';
54
- import { createDriver } from '../driver/driver.ts';
26
+ import { testRender } from "@opentui/react/test-utils";
27
+ import * as fs from "fs/promises";
28
+ import * as path from "path";
29
+ import type { ReactNode } from "react";
30
+ import { DEFAULT_CONFIG } from "@/config/schema.ts";
31
+ import { DebugConsole } from "../components/DebugConsole.tsx";
32
+ import { Header } from "../components/Header.tsx";
33
+ import { KpiStrip } from "../components/KpiStrip.tsx";
34
+ import { ProviderCard } from "../components/ProviderCard.tsx";
35
+ import { SessionDetailsDrawer } from "../components/SessionDetailsDrawer.tsx";
36
+ import { SkeletonGauge, SkeletonProviderContent, SkeletonText } from "../components/Skeleton.tsx";
37
+ import { Spinner } from "../components/Spinner.tsx";
38
+ import { StatusBar } from "../components/StatusBar.tsx";
39
+ import { Toast } from "../components/Toast.tsx";
40
+ import { UsageGauge } from "../components/UsageGauge.tsx";
41
+ import { AgentSessionProvider } from "../contexts/AgentSessionContext.tsx";
42
+ import { ConfigProvider } from "../contexts/ConfigContext.tsx";
43
+ import { InputProvider } from "../contexts/InputContext.tsx";
44
+ import { LogProvider } from "../contexts/LogContext.tsx";
45
+ import { PluginProvider } from "../contexts/PluginContext.tsx";
46
+ import { StorageProvider } from "../contexts/StorageContext.tsx";
47
+ import { ThemeProvider } from "../contexts/ThemeContext.tsx";
48
+ import { TimeWindowProvider } from "../contexts/TimeWindowContext.tsx";
49
+ import { ToastProvider } from "../contexts/ToastContext.tsx";
50
+ import { createDriver } from "../driver/driver.ts";
51
+ import { HistoricalTrendsView } from "../views/HistoricalTrendsView.tsx";
52
+ import { ProjectsView } from "../views/ProjectsView.tsx";
53
+ import { SettingsView } from "../views/SettingsView.tsx";
54
+ import { getFramesDir } from "./captureFrame.ts";
55
55
 
56
56
  function createMockHeaderProps() {
57
57
  return {
58
- title: 'tokentop',
59
- subtitle: 'v0.1.0',
60
- activeView: 'dashboard' as const,
58
+ title: "tokentop",
59
+ subtitle: "v0.1.0",
60
+ activeView: "dashboard" as const,
61
61
  };
62
62
  }
63
63
 
@@ -66,38 +66,38 @@ function createMockStatusBarProps() {
66
66
  return {
67
67
  lastRefresh: now - 30000,
68
68
  nextRefresh: now + 30000,
69
- message: 'tokentop - htop for AI usage',
69
+ message: "tokentop - htop for AI usage",
70
70
  };
71
71
  }
72
72
 
73
73
  function createMockProviderCardProps() {
74
74
  const now = Date.now();
75
75
  return {
76
- name: 'Anthropic',
76
+ name: "Anthropic",
77
77
  configured: true,
78
78
  loading: false,
79
- color: '#D97706',
79
+ color: "#D97706",
80
80
  focused: false,
81
81
  usage: {
82
- planType: 'Claude Pro',
82
+ planType: "Claude Pro",
83
83
  limitReached: false,
84
84
  limits: {
85
85
  primary: {
86
86
  usedPercent: 65,
87
- label: 'Daily Messages',
87
+ label: "Daily Messages",
88
88
  windowMinutes: 1440,
89
89
  resetsAt: now + 3600000,
90
90
  },
91
91
  secondary: {
92
92
  usedPercent: 23,
93
- label: 'Weekly Tokens',
93
+ label: "Weekly Tokens",
94
94
  windowMinutes: 10080,
95
95
  },
96
96
  },
97
97
  credits: {
98
98
  hasCredits: true,
99
99
  unlimited: false,
100
- balance: '$42.50',
100
+ balance: "$42.50",
101
101
  },
102
102
  fetchedAt: now,
103
103
  },
@@ -106,10 +106,10 @@ function createMockProviderCardProps() {
106
106
 
107
107
  function createMockProviderCardLoadingProps() {
108
108
  return {
109
- name: 'OpenAI',
109
+ name: "OpenAI",
110
110
  configured: true,
111
111
  loading: true,
112
- color: '#10A37F',
112
+ color: "#10A37F",
113
113
  focused: false,
114
114
  usage: null,
115
115
  };
@@ -117,10 +117,10 @@ function createMockProviderCardLoadingProps() {
117
117
 
118
118
  function createMockProviderCardUnconfiguredProps() {
119
119
  return {
120
- name: 'Google',
120
+ name: "Google",
121
121
  configured: false,
122
122
  loading: false,
123
- color: '#4285F4',
123
+ color: "#4285F4",
124
124
  focused: false,
125
125
  usage: null,
126
126
  };
@@ -129,13 +129,13 @@ function createMockProviderCardUnconfiguredProps() {
129
129
  function createMockProviderCardErrorProps() {
130
130
  const now = Date.now();
131
131
  return {
132
- name: 'Mistral',
132
+ name: "Mistral",
133
133
  configured: true,
134
134
  loading: false,
135
- color: '#FF7000',
135
+ color: "#FF7000",
136
136
  focused: false,
137
137
  usage: {
138
- error: 'API rate limit exceeded',
138
+ error: "API rate limit exceeded",
139
139
  fetchedAt: now,
140
140
  },
141
141
  };
@@ -144,19 +144,19 @@ function createMockProviderCardErrorProps() {
144
144
  function createMockUsageGaugeProps() {
145
145
  const now = Date.now();
146
146
  return {
147
- label: 'Daily Tokens',
147
+ label: "Daily Tokens",
148
148
  usedPercent: 73,
149
- windowLabel: '24-hour window',
149
+ windowLabel: "24-hour window",
150
150
  resetsAt: now + 7200000,
151
151
  width: 40,
152
- color: '#7C3AED',
152
+ color: "#7C3AED",
153
153
  };
154
154
  }
155
155
 
156
156
  function createMockToastProps() {
157
157
  return {
158
- message: 'Settings saved successfully!',
159
- type: 'success' as const,
158
+ message: "Settings saved successfully!",
159
+ type: "success" as const,
160
160
  duration: 999999,
161
161
  onDismiss: () => {},
162
162
  };
@@ -164,8 +164,8 @@ function createMockToastProps() {
164
164
 
165
165
  function createMockToastErrorProps() {
166
166
  return {
167
- message: 'Failed to connect to API',
168
- type: 'error' as const,
167
+ message: "Failed to connect to API",
168
+ type: "error" as const,
169
169
  duration: 999999,
170
170
  onDismiss: () => {},
171
171
  };
@@ -173,8 +173,8 @@ function createMockToastErrorProps() {
173
173
 
174
174
  function createMockToastWarningProps() {
175
175
  return {
176
- message: 'Rate limit approaching',
177
- type: 'warning' as const,
176
+ message: "Rate limit approaching",
177
+ type: "warning" as const,
178
178
  duration: 999999,
179
179
  onDismiss: () => {},
180
180
  };
@@ -196,14 +196,14 @@ function createMockKpiStripProps() {
196
196
  isSpike: false,
197
197
  },
198
198
  sparkData: [
199
- 4, 6, 8, 12, 18, 22, 28, 36, 32, 40, 44, 48, 52, 48, 44, 40, 36, 32,
200
- 28, 24, 22, 20, 18, 16, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54,
199
+ 4, 6, 8, 12, 18, 22, 28, 36, 32, 40, 44, 48, 52, 48, 44, 40, 36, 32, 28, 24, 22, 20, 18, 16,
200
+ 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54,
201
201
  ],
202
202
  budget: {
203
203
  limit: 25,
204
204
  budgetCost: 12.43,
205
- budgetType: 'daily' as const,
206
- budgetTypeLabel: 'Daily',
205
+ budgetType: "daily" as const,
206
+ budgetTypeLabel: "Daily",
207
207
  warningPercent: 80,
208
208
  criticalPercent: 90,
209
209
  },
@@ -213,7 +213,7 @@ function createMockKpiStripProps() {
213
213
 
214
214
  function createMockSpinnerProps() {
215
215
  return {
216
- color: '#7C3AED',
216
+ color: "#7C3AED",
217
217
  };
218
218
  }
219
219
 
@@ -221,14 +221,14 @@ function createMockSessionDetailsDrawerProps() {
221
221
  const now = Date.now();
222
222
  return {
223
223
  session: {
224
- sessionId: 'ses_39c52c5b5ffe4D0eUfUnnCdNw4',
225
- sessionName: 'test-smoke done',
226
- agentId: 'opencode',
227
- agentName: 'OpenCode',
228
- projectPath: '/Users/nigel/development/opencode-mission-control',
224
+ sessionId: "ses_39c52c5b5ffe4D0eUfUnnCdNw4",
225
+ sessionName: "test-smoke done",
226
+ agentId: "opencode",
227
+ agentName: "OpenCode",
228
+ projectPath: "/Users/nigel/development/opencode-mission-control",
229
229
  startedAt: now - 10000000,
230
230
  lastActivityAt: now,
231
- status: 'active' as const,
231
+ status: "active" as const,
232
232
  totals: {
233
233
  input: 1500000,
234
234
  output: 200000,
@@ -239,8 +239,8 @@ function createMockSessionDetailsDrawerProps() {
239
239
  requestCount: 150,
240
240
  streams: [
241
241
  {
242
- providerId: 'anthropic',
243
- modelId: 'claude-3-opus-20240229',
242
+ providerId: "anthropic",
243
+ modelId: "claude-3-opus-20240229",
244
244
  tokens: {
245
245
  input: 1500000,
246
246
  output: 200000,
@@ -254,7 +254,19 @@ function createMockSessionDetailsDrawerProps() {
254
254
  input: 0.01,
255
255
  output: 1.01,
256
256
  cacheRead: 10.03,
257
- cacheWrite: 5.00,
257
+ cacheWrite: 5.0,
258
+ },
259
+ },
260
+ {
261
+ providerId: "openai",
262
+ modelId: "gpt-4o",
263
+ tokens: { input: 50000, output: 12000 },
264
+ requestCount: 8,
265
+ costUsd: 0.42,
266
+ costBreakdown: {
267
+ total: 0.42,
268
+ input: 0.13,
269
+ output: 0.29,
258
270
  },
259
271
  },
260
272
  ],
@@ -275,128 +287,128 @@ interface ComponentEntry {
275
287
  }
276
288
 
277
289
  const COMPONENT_REGISTRY: Record<string, ComponentEntry> = {
278
- 'header': {
279
- name: 'Header',
280
- description: 'Top navigation header with title and view tabs',
290
+ header: {
291
+ name: "Header",
292
+ description: "Top navigation header with title and view tabs",
281
293
  defaultWidth: 100,
282
294
  defaultHeight: 3,
283
295
  render: () => <Header {...createMockHeaderProps()} />,
284
296
  },
285
- 'status-bar': {
286
- name: 'StatusBar',
287
- description: 'Bottom status bar with refresh timing',
297
+ "status-bar": {
298
+ name: "StatusBar",
299
+ description: "Bottom status bar with refresh timing",
288
300
  defaultWidth: 100,
289
301
  defaultHeight: 3,
290
302
  render: () => <StatusBar {...createMockStatusBarProps()} />,
291
303
  },
292
- 'provider-card': {
293
- name: 'ProviderCard',
294
- description: 'Provider usage card with gauges (configured state)',
304
+ "provider-card": {
305
+ name: "ProviderCard",
306
+ description: "Provider usage card with gauges (configured state)",
295
307
  defaultWidth: 50,
296
308
  defaultHeight: 20,
297
309
  render: () => <ProviderCard {...createMockProviderCardProps()} />,
298
310
  },
299
- 'provider-card-loading': {
300
- name: 'ProviderCard (Loading)',
301
- description: 'Provider card in loading state with skeleton',
311
+ "provider-card-loading": {
312
+ name: "ProviderCard (Loading)",
313
+ description: "Provider card in loading state with skeleton",
302
314
  defaultWidth: 50,
303
315
  defaultHeight: 20,
304
316
  render: () => <ProviderCard {...createMockProviderCardLoadingProps()} />,
305
317
  },
306
- 'provider-card-unconfigured': {
307
- name: 'ProviderCard (Unconfigured)',
308
- description: 'Provider card not configured state',
318
+ "provider-card-unconfigured": {
319
+ name: "ProviderCard (Unconfigured)",
320
+ description: "Provider card not configured state",
309
321
  defaultWidth: 50,
310
322
  defaultHeight: 20,
311
323
  render: () => <ProviderCard {...createMockProviderCardUnconfiguredProps()} />,
312
324
  },
313
- 'provider-card-error': {
314
- name: 'ProviderCard (Error)',
315
- description: 'Provider card with error state',
325
+ "provider-card-error": {
326
+ name: "ProviderCard (Error)",
327
+ description: "Provider card with error state",
316
328
  defaultWidth: 50,
317
329
  defaultHeight: 20,
318
330
  render: () => <ProviderCard {...createMockProviderCardErrorProps()} />,
319
331
  },
320
- 'usage-gauge': {
321
- name: 'UsageGauge',
322
- description: 'Single usage gauge with progress bar',
332
+ "usage-gauge": {
333
+ name: "UsageGauge",
334
+ description: "Single usage gauge with progress bar",
323
335
  defaultWidth: 45,
324
336
  defaultHeight: 5,
325
337
  render: () => <UsageGauge {...createMockUsageGaugeProps()} />,
326
338
  },
327
- 'toast': {
328
- name: 'Toast (Success)',
329
- description: 'Toast notification - success variant',
339
+ toast: {
340
+ name: "Toast (Success)",
341
+ description: "Toast notification - success variant",
330
342
  defaultWidth: 50,
331
343
  defaultHeight: 8,
332
344
  render: () => <Toast {...createMockToastProps()} />,
333
345
  },
334
- 'toast-error': {
335
- name: 'Toast (Error)',
336
- description: 'Toast notification - error variant',
346
+ "toast-error": {
347
+ name: "Toast (Error)",
348
+ description: "Toast notification - error variant",
337
349
  defaultWidth: 50,
338
350
  defaultHeight: 8,
339
351
  render: () => <Toast {...createMockToastErrorProps()} />,
340
352
  },
341
- 'toast-warning': {
342
- name: 'Toast (Warning)',
343
- description: 'Toast notification - warning variant',
353
+ "toast-warning": {
354
+ name: "Toast (Warning)",
355
+ description: "Toast notification - warning variant",
344
356
  defaultWidth: 50,
345
357
  defaultHeight: 8,
346
358
  render: () => <Toast {...createMockToastWarningProps()} />,
347
359
  },
348
- 'spinner': {
349
- name: 'Spinner',
350
- description: 'Animated loading spinner',
360
+ spinner: {
361
+ name: "Spinner",
362
+ description: "Animated loading spinner",
351
363
  defaultWidth: 10,
352
364
  defaultHeight: 3,
353
365
  render: () => <Spinner {...createMockSpinnerProps()} />,
354
366
  },
355
- 'skeleton-text': {
356
- name: 'SkeletonText',
357
- description: 'Shimmer loading placeholder for text',
367
+ "skeleton-text": {
368
+ name: "SkeletonText",
369
+ description: "Shimmer loading placeholder for text",
358
370
  defaultWidth: 20,
359
371
  defaultHeight: 3,
360
372
  render: () => <SkeletonText width={15} />,
361
373
  },
362
- 'skeleton-gauge': {
363
- name: 'SkeletonGauge',
364
- description: 'Shimmer loading placeholder for gauge',
374
+ "skeleton-gauge": {
375
+ name: "SkeletonGauge",
376
+ description: "Shimmer loading placeholder for gauge",
365
377
  defaultWidth: 45,
366
378
  defaultHeight: 5,
367
379
  render: () => <SkeletonGauge barWidth={30} />,
368
380
  },
369
- 'skeleton-provider': {
370
- name: 'SkeletonProviderContent',
371
- description: 'Full skeleton for provider card content',
381
+ "skeleton-provider": {
382
+ name: "SkeletonProviderContent",
383
+ description: "Full skeleton for provider card content",
372
384
  defaultWidth: 45,
373
385
  defaultHeight: 12,
374
386
  render: () => <SkeletonProviderContent />,
375
387
  },
376
- 'debug-console': {
377
- name: 'DebugConsole',
378
- description: 'Debug console with log entries',
388
+ "debug-console": {
389
+ name: "DebugConsole",
390
+ description: "Debug console with log entries",
379
391
  defaultWidth: 100,
380
392
  defaultHeight: 20,
381
393
  render: () => <DebugConsole height={15} follow={true} />,
382
394
  },
383
- 'kpi-strip': {
384
- name: 'KpiStrip',
385
- description: 'KPI strip with activity sparkline ramp',
395
+ "kpi-strip": {
396
+ name: "KpiStrip",
397
+ description: "KPI strip with activity sparkline ramp",
386
398
  defaultWidth: 120,
387
399
  defaultHeight: 6,
388
400
  render: () => <KpiStrip {...createMockKpiStripProps()} />,
389
401
  },
390
- 'historical-trends': {
391
- name: 'HistoricalTrendsView',
392
- description: 'ASCII chart showing cost trends over time',
402
+ "historical-trends": {
403
+ name: "HistoricalTrendsView",
404
+ description: "ASCII chart showing cost trends over time",
393
405
  defaultWidth: 80,
394
406
  defaultHeight: 20,
395
407
  render: () => <HistoricalTrendsView />,
396
408
  },
397
- 'projects-view': {
398
- name: 'ProjectsView',
399
- description: 'Sessions grouped by project with cost breakdown',
409
+ "projects-view": {
410
+ name: "ProjectsView",
411
+ description: "Sessions grouped by project with cost breakdown",
400
412
  defaultWidth: 100,
401
413
  defaultHeight: 25,
402
414
  render: () => (
@@ -413,9 +425,9 @@ const COMPONENT_REGISTRY: Record<string, ComponentEntry> = {
413
425
  </InputProvider>
414
426
  ),
415
427
  },
416
- 'settings-view': {
417
- name: 'SettingsView',
418
- description: 'Application settings with categories',
428
+ "settings-view": {
429
+ name: "SettingsView",
430
+ description: "Application settings with categories",
419
431
  defaultWidth: 80,
420
432
  defaultHeight: 25,
421
433
  render: () => (
@@ -426,9 +438,9 @@ const COMPONENT_REGISTRY: Record<string, ComponentEntry> = {
426
438
  </ConfigProvider>
427
439
  ),
428
440
  },
429
- 'session-details': {
430
- name: 'SessionDetailsDrawer',
431
- description: 'Detailed view of a session',
441
+ "session-details": {
442
+ name: "SessionDetailsDrawer",
443
+ description: "Detailed view of a session",
432
444
  defaultWidth: 100,
433
445
  defaultHeight: 30,
434
446
  render: () => <SessionDetailsDrawer {...createMockSessionDetailsDrawerProps()} />,
@@ -447,39 +459,39 @@ interface SnapshotOptions {
447
459
  function parseArgs(): SnapshotOptions {
448
460
  const args = process.argv.slice(2);
449
461
  const options: SnapshotOptions = {
450
- component: 'debug-inspector',
462
+ component: "debug-inspector",
451
463
  list: false,
452
464
  all: false,
453
465
  };
454
466
 
455
467
  for (let i = 0; i < args.length; i++) {
456
468
  const arg = args[i]!;
457
- if (arg === '--list' || arg === '-l') {
469
+ if (arg === "--list" || arg === "-l") {
458
470
  options.list = true;
459
- } else if (arg === '--all' || arg === '-a') {
471
+ } else if (arg === "--all" || arg === "-a") {
460
472
  options.all = true;
461
- } else if (arg === '--width' || arg === '-w') {
473
+ } else if (arg === "--width" || arg === "-w") {
462
474
  const val = args[i + 1];
463
475
  if (val) {
464
476
  options.width = parseInt(val, 10);
465
477
  i++;
466
478
  }
467
- } else if (arg === '--height' || arg === '-h') {
479
+ } else if (arg === "--height" || arg === "-h") {
468
480
  const val = args[i + 1];
469
481
  if (val) {
470
482
  options.height = parseInt(val, 10);
471
483
  i++;
472
484
  }
473
- } else if (arg === '--output' || arg === '-o') {
485
+ } else if (arg === "--output" || arg === "-o") {
474
486
  const val = args[i + 1];
475
487
  if (val) {
476
488
  options.output = val;
477
489
  i++;
478
490
  }
479
- } else if (arg === '--help') {
491
+ } else if (arg === "--help") {
480
492
  printHelp();
481
493
  process.exit(0);
482
- } else if (!arg.startsWith('-')) {
494
+ } else if (!arg.startsWith("-")) {
483
495
  options.component = arg;
484
496
  }
485
497
  }
@@ -512,22 +524,22 @@ Examples:
512
524
  }
513
525
 
514
526
  function printComponentList(): void {
515
- console.log('\nAvailable Components:\n');
516
- console.log(' ID Description');
517
- console.log(' ' + '-'.repeat(70));
518
-
527
+ console.log("\nAvailable Components:\n");
528
+ console.log(" ID Description");
529
+ console.log(" " + "-".repeat(70));
530
+
519
531
  for (const [id, entry] of Object.entries(COMPONENT_REGISTRY)) {
520
532
  const paddedId = id.padEnd(28);
521
533
  console.log(` ${paddedId}${entry.description}`);
522
534
  }
523
-
524
- console.log('\nUsage: bun src/tui/debug/snapshot.tsx <component-id>\n');
535
+
536
+ console.log("\nUsage: bun src/tui/debug/snapshot.tsx <component-id>\n");
525
537
  }
526
538
 
527
539
  async function snapshotComponent(
528
540
  componentId: string,
529
541
  width?: number,
530
- height?: number
542
+ height?: number,
531
543
  ): Promise<string> {
532
544
  const entry = COMPONENT_REGISTRY[componentId];
533
545
  if (!entry) {
@@ -537,7 +549,7 @@ async function snapshotComponent(
537
549
  const finalWidth = width ?? entry.defaultWidth;
538
550
  const finalHeight = height ?? entry.defaultHeight;
539
551
 
540
- if (componentId === 'kpi-strip') {
552
+ if (componentId === "kpi-strip") {
541
553
  const driver = await createDriver({ width: finalWidth, height: finalHeight });
542
554
  await driver.launch();
543
555
  await driver.waitForStable();
@@ -573,15 +585,17 @@ async function snapshotComponent(
573
585
  async function saveSnapshot(
574
586
  componentId: string,
575
587
  frame: string,
576
- outputPath?: string
588
+ outputPath?: string,
577
589
  ): Promise<string> {
578
- const finalPath = outputPath ?? path.join(
579
- getFramesDir(),
580
- `snapshot-${componentId}-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`
581
- );
590
+ const finalPath =
591
+ outputPath ??
592
+ path.join(
593
+ getFramesDir(),
594
+ `snapshot-${componentId}-${new Date().toISOString().replace(/[:.]/g, "-")}.txt`,
595
+ );
582
596
 
583
597
  await fs.mkdir(path.dirname(finalPath), { recursive: true });
584
- await fs.writeFile(finalPath, frame, 'utf-8');
598
+ await fs.writeFile(finalPath, frame, "utf-8");
585
599
 
586
600
  return finalPath;
587
601
  }
@@ -595,16 +609,19 @@ async function main() {
595
609
  }
596
610
 
597
611
  if (options.all) {
598
- console.log('Capturing all components...\n');
612
+ console.log("Capturing all components...\n");
599
613
  const framesDir = getFramesDir();
600
- const allDir = path.join(framesDir, `snapshot-all-${new Date().toISOString().replace(/[:.]/g, '-')}`);
614
+ const allDir = path.join(
615
+ framesDir,
616
+ `snapshot-all-${new Date().toISOString().replace(/[:.]/g, "-")}`,
617
+ );
601
618
  await fs.mkdir(allDir, { recursive: true });
602
619
 
603
620
  for (const [id, entry] of Object.entries(COMPONENT_REGISTRY)) {
604
621
  try {
605
622
  const frame = await snapshotComponent(id, options.width, options.height);
606
623
  const outputPath = path.join(allDir, `${id}.txt`);
607
- await fs.writeFile(outputPath, frame, 'utf-8');
624
+ await fs.writeFile(outputPath, frame, "utf-8");
608
625
  console.log(` ✓ ${entry.name} -> ${id}.txt`);
609
626
  } catch (err) {
610
627
  console.error(` ✗ ${entry.name}: ${err}`);
@@ -618,7 +635,7 @@ async function main() {
618
635
  const entry = COMPONENT_REGISTRY[options.component];
619
636
  if (!entry) {
620
637
  console.error(`Unknown component: ${options.component}`);
621
- console.error('\nAvailable components:');
638
+ console.error("\nAvailable components:");
622
639
  for (const id of Object.keys(COMPONENT_REGISTRY)) {
623
640
  console.error(` - ${id}`);
624
641
  }
@@ -634,11 +651,11 @@ async function main() {
634
651
  const outputPath = await saveSnapshot(options.component, frame, options.output);
635
652
 
636
653
  console.log(`Snapshot saved to: ${outputPath}`);
637
- console.log('\n--- Preview ---');
654
+ console.log("\n--- Preview ---");
638
655
  console.log(frame);
639
656
  }
640
657
 
641
658
  main().catch((err) => {
642
- console.error('Snapshot failed:', err);
659
+ console.error("Snapshot failed:", err);
643
660
  process.exit(1);
644
661
  });