@tokentop/ttop 0.2.2
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/LICENSE +21 -0
- package/README.md +273 -0
- package/bin/ttop.js +49 -0
- package/package.json +68 -0
- package/src/agents/aggregator.ts +176 -0
- package/src/agents/costing.ts +74 -0
- package/src/agents/index.ts +12 -0
- package/src/agents/types.ts +71 -0
- package/src/cli.ts +167 -0
- package/src/config/schema.test.ts +166 -0
- package/src/config/schema.ts +165 -0
- package/src/demo/simulator.ts +745 -0
- package/src/plugins/agents/index.ts +2 -0
- package/src/plugins/auth-sources.ts +186 -0
- package/src/plugins/lifecycle.ts +177 -0
- package/src/plugins/loader.test.ts +195 -0
- package/src/plugins/loader.ts +282 -0
- package/src/plugins/notification-bus.ts +153 -0
- package/src/plugins/notifications/index.ts +2 -0
- package/src/plugins/notifications/terminal-bell.ts +66 -0
- package/src/plugins/notifications/visual-flash.ts +57 -0
- package/src/plugins/npm-installer.ts +164 -0
- package/src/plugins/plugin-context-factory.ts +67 -0
- package/src/plugins/plugin-host.ts +205 -0
- package/src/plugins/providers/anthropic.ts +325 -0
- package/src/plugins/providers/antigravity.ts +309 -0
- package/src/plugins/providers/codex.ts +250 -0
- package/src/plugins/providers/gemini.ts +391 -0
- package/src/plugins/providers/github-copilot.ts +194 -0
- package/src/plugins/providers/index.ts +12 -0
- package/src/plugins/providers/minimax.ts +223 -0
- package/src/plugins/providers/openai-api.ts +250 -0
- package/src/plugins/providers/opencode-zen.ts +125 -0
- package/src/plugins/providers/perplexity.ts +177 -0
- package/src/plugins/providers/zai.ts +225 -0
- package/src/plugins/registry.ts +251 -0
- package/src/plugins/sandbox-guard.test.ts +191 -0
- package/src/plugins/sandbox-guard.ts +155 -0
- package/src/plugins/sandbox.ts +138 -0
- package/src/plugins/themes/catppuccin-latte.ts +45 -0
- package/src/plugins/themes/catppuccin-mocha.ts +45 -0
- package/src/plugins/themes/claude-code.ts +45 -0
- package/src/plugins/themes/dracula.ts +45 -0
- package/src/plugins/themes/github-light.ts +45 -0
- package/src/plugins/themes/gruvbox-dark.ts +45 -0
- package/src/plugins/themes/gruvbox-light.ts +45 -0
- package/src/plugins/themes/index.ts +15 -0
- package/src/plugins/themes/kanagawa.ts +45 -0
- package/src/plugins/themes/nord.ts +45 -0
- package/src/plugins/themes/one-dark.ts +45 -0
- package/src/plugins/themes/opencode.ts +47 -0
- package/src/plugins/themes/rose-pine-dawn.ts +45 -0
- package/src/plugins/themes/rose-pine.ts +45 -0
- package/src/plugins/themes/solarized-light.ts +45 -0
- package/src/plugins/themes/tokyo-night.ts +49 -0
- package/src/plugins/types/agent.ts +108 -0
- package/src/plugins/types/base.ts +267 -0
- package/src/plugins/types/index.ts +19 -0
- package/src/plugins/types/notification.ts +51 -0
- package/src/plugins/types/provider.ts +233 -0
- package/src/plugins/types/theme.ts +70 -0
- package/src/plugins/update-checker.ts +177 -0
- package/src/pricing/estimator.test.ts +181 -0
- package/src/pricing/estimator.ts +80 -0
- package/src/pricing/fallback.test.ts +71 -0
- package/src/pricing/fallback.ts +62 -0
- package/src/pricing/index.ts +66 -0
- package/src/pricing/models-dev.ts +130 -0
- package/src/storage/database.ts +613 -0
- package/src/storage/db.ts +64 -0
- package/src/storage/index.ts +66 -0
- package/src/storage/migrations/index.ts +189 -0
- package/src/storage/paths.ts +24 -0
- package/src/storage/repos/agentSessions.ts +210 -0
- package/src/storage/repos/providerSnapshots.ts +116 -0
- package/src/storage/repos/usageEvents.ts +334 -0
- package/src/storage/types.ts +288 -0
- package/src/tui/App.tsx +381 -0
- package/src/tui/components/CommandPalette.tsx +166 -0
- package/src/tui/components/DebugConsole.tsx +161 -0
- package/src/tui/components/DebugPanel.tsx +660 -0
- package/src/tui/components/FooterHints.tsx +22 -0
- package/src/tui/components/GhostProviderCard.tsx +45 -0
- package/src/tui/components/Header.tsx +125 -0
- package/src/tui/components/HelpOverlay.tsx +45 -0
- package/src/tui/components/InlineGauge.tsx +34 -0
- package/src/tui/components/InlineSparkline.tsx +78 -0
- package/src/tui/components/KpiStrip.tsx +246 -0
- package/src/tui/components/LimitGauge.tsx +186 -0
- package/src/tui/components/ModalBackdrop.tsx +42 -0
- package/src/tui/components/ProviderAggregateStrip.tsx +200 -0
- package/src/tui/components/ProviderCard.tsx +309 -0
- package/src/tui/components/ProviderDetailPanel.tsx +237 -0
- package/src/tui/components/ProviderLimitsPanel.tsx +308 -0
- package/src/tui/components/ProvidersList.tsx +380 -0
- package/src/tui/components/SessionDetailsDrawer.tsx +317 -0
- package/src/tui/components/SessionsTable.tsx +401 -0
- package/src/tui/components/SettingsModal.tsx +587 -0
- package/src/tui/components/SidebarBreakdown.tsx +87 -0
- package/src/tui/components/Skeleton.tsx +102 -0
- package/src/tui/components/SmartSidebar.tsx +644 -0
- package/src/tui/components/Sparkline.tsx +180 -0
- package/src/tui/components/Spinner.tsx +36 -0
- package/src/tui/components/StatusBar.tsx +96 -0
- package/src/tui/components/ThemePicker.tsx +234 -0
- package/src/tui/components/Toast.tsx +61 -0
- package/src/tui/components/UsageGauge.tsx +80 -0
- package/src/tui/components/index.ts +5 -0
- package/src/tui/contexts/AgentSessionContext.tsx +307 -0
- package/src/tui/contexts/ConfigContext.tsx +141 -0
- package/src/tui/contexts/DashboardRuntimeContext.tsx +102 -0
- package/src/tui/contexts/DemoModeContext.tsx +60 -0
- package/src/tui/contexts/DrawerContext.tsx +42 -0
- package/src/tui/contexts/InputContext.tsx +30 -0
- package/src/tui/contexts/LogContext.tsx +150 -0
- package/src/tui/contexts/PluginContext.tsx +424 -0
- package/src/tui/contexts/RealTimeActivityContext.tsx +119 -0
- package/src/tui/contexts/StorageContext.tsx +233 -0
- package/src/tui/contexts/ThemeContext.tsx +95 -0
- package/src/tui/contexts/TimeWindowContext.tsx +158 -0
- package/src/tui/contexts/ToastContext.tsx +40 -0
- package/src/tui/contexts/index.ts +5 -0
- package/src/tui/createApp.tsx +47 -0
- package/src/tui/debug/captureFrame.ts +262 -0
- package/src/tui/debug/snapshot.tsx +644 -0
- package/src/tui/driver/assertions.ts +279 -0
- package/src/tui/driver/cli.ts +717 -0
- package/src/tui/driver/coverage.ts +204 -0
- package/src/tui/driver/demo-snapshot.ts +66 -0
- package/src/tui/driver/diff.ts +166 -0
- package/src/tui/driver/driver.ts +270 -0
- package/src/tui/driver/index.ts +53 -0
- package/src/tui/driver/recorder.ts +303 -0
- package/src/tui/driver/test-workflow.ts +115 -0
- package/src/tui/driver/test.ts +47 -0
- package/src/tui/hooks/index.ts +20 -0
- package/src/tui/hooks/useAnimatedValue.ts +113 -0
- package/src/tui/hooks/useDashboardKeyboard.ts +341 -0
- package/src/tui/hooks/useDashboardState.ts +116 -0
- package/src/tui/hooks/useEmaActivity.ts +223 -0
- package/src/tui/hooks/useEntranceAnimation.ts +62 -0
- package/src/tui/hooks/useExitAnimation.ts +137 -0
- package/src/tui/hooks/usePulse.ts +138 -0
- package/src/tui/hooks/useSafeRenderer.ts +24 -0
- package/src/tui/hooks/useValueFlash.ts +111 -0
- package/src/tui/index.tsx +50 -0
- package/src/tui/utils/providerColor.ts +32 -0
- package/src/tui/views/Dashboard.tsx +340 -0
- package/src/tui/views/HistoricalTrendsView.tsx +1431 -0
- package/src/tui/views/ProjectsView.tsx +1277 -0
- package/src/tui/views/RealTimeDashboard.tsx +393 -0
- package/src/tui/views/SettingsView.tsx +465 -0
- package/src/tui/views/index.ts +2 -0
- package/src/types/global.d.ts +5 -0
- package/src/types/opentui-test-utils.d.ts +18 -0
- package/src/utils/clipboard.ts +118 -0
- package/src/utils/currency.test.ts +106 -0
- package/src/utils/currency.ts +79 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Headless Snapshot Script for Component Isolation Testing
|
|
4
|
+
*
|
|
5
|
+
* Renders components in a headless environment and captures the output.
|
|
6
|
+
* Useful for debugging layout issues without running the full app.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* bun src/tui/debug/snapshot.tsx [component] [options]
|
|
10
|
+
*
|
|
11
|
+
* Examples:
|
|
12
|
+
* bun src/tui/debug/snapshot.tsx --list # List all available components
|
|
13
|
+
* bun src/tui/debug/snapshot.tsx header # Snapshot Header
|
|
14
|
+
* bun src/tui/debug/snapshot.tsx provider-card # Snapshot ProviderCard
|
|
15
|
+
* bun src/tui/debug/snapshot.tsx kpi-strip # Snapshot KpiStrip
|
|
16
|
+
* bun src/tui/debug/snapshot.tsx --width 120 --height 40 # Custom dimensions
|
|
17
|
+
* bun src/tui/debug/snapshot.tsx toast --output frame.txt # Custom output file
|
|
18
|
+
*
|
|
19
|
+
* Adding New Components:
|
|
20
|
+
* 1. Create a mock data factory function: createMock<ComponentName>Props()
|
|
21
|
+
* 2. Register it in COMPONENT_REGISTRY with render function
|
|
22
|
+
* 3. That's it! The component will be available via CLI
|
|
23
|
+
*/
|
|
24
|
+
|
|
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';
|
|
55
|
+
|
|
56
|
+
function createMockHeaderProps() {
|
|
57
|
+
return {
|
|
58
|
+
title: 'tokentop',
|
|
59
|
+
subtitle: 'v0.1.0',
|
|
60
|
+
activeView: 'dashboard' as const,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createMockStatusBarProps() {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
return {
|
|
67
|
+
lastRefresh: now - 30000,
|
|
68
|
+
nextRefresh: now + 30000,
|
|
69
|
+
message: 'tokentop - htop for AI usage',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function createMockProviderCardProps() {
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
return {
|
|
76
|
+
name: 'Anthropic',
|
|
77
|
+
configured: true,
|
|
78
|
+
loading: false,
|
|
79
|
+
color: '#D97706',
|
|
80
|
+
focused: false,
|
|
81
|
+
usage: {
|
|
82
|
+
planType: 'Claude Pro',
|
|
83
|
+
limitReached: false,
|
|
84
|
+
limits: {
|
|
85
|
+
primary: {
|
|
86
|
+
usedPercent: 65,
|
|
87
|
+
label: 'Daily Messages',
|
|
88
|
+
windowMinutes: 1440,
|
|
89
|
+
resetsAt: now + 3600000,
|
|
90
|
+
},
|
|
91
|
+
secondary: {
|
|
92
|
+
usedPercent: 23,
|
|
93
|
+
label: 'Weekly Tokens',
|
|
94
|
+
windowMinutes: 10080,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
credits: {
|
|
98
|
+
hasCredits: true,
|
|
99
|
+
unlimited: false,
|
|
100
|
+
balance: '$42.50',
|
|
101
|
+
},
|
|
102
|
+
fetchedAt: now,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function createMockProviderCardLoadingProps() {
|
|
108
|
+
return {
|
|
109
|
+
name: 'OpenAI',
|
|
110
|
+
configured: true,
|
|
111
|
+
loading: true,
|
|
112
|
+
color: '#10A37F',
|
|
113
|
+
focused: false,
|
|
114
|
+
usage: null,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function createMockProviderCardUnconfiguredProps() {
|
|
119
|
+
return {
|
|
120
|
+
name: 'Google',
|
|
121
|
+
configured: false,
|
|
122
|
+
loading: false,
|
|
123
|
+
color: '#4285F4',
|
|
124
|
+
focused: false,
|
|
125
|
+
usage: null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function createMockProviderCardErrorProps() {
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
return {
|
|
132
|
+
name: 'Mistral',
|
|
133
|
+
configured: true,
|
|
134
|
+
loading: false,
|
|
135
|
+
color: '#FF7000',
|
|
136
|
+
focused: false,
|
|
137
|
+
usage: {
|
|
138
|
+
error: 'API rate limit exceeded',
|
|
139
|
+
fetchedAt: now,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function createMockUsageGaugeProps() {
|
|
145
|
+
const now = Date.now();
|
|
146
|
+
return {
|
|
147
|
+
label: 'Daily Tokens',
|
|
148
|
+
usedPercent: 73,
|
|
149
|
+
windowLabel: '24-hour window',
|
|
150
|
+
resetsAt: now + 7200000,
|
|
151
|
+
width: 40,
|
|
152
|
+
color: '#7C3AED',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function createMockToastProps() {
|
|
157
|
+
return {
|
|
158
|
+
message: 'Settings saved successfully!',
|
|
159
|
+
type: 'success' as const,
|
|
160
|
+
duration: 999999,
|
|
161
|
+
onDismiss: () => {},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function createMockToastErrorProps() {
|
|
166
|
+
return {
|
|
167
|
+
message: 'Failed to connect to API',
|
|
168
|
+
type: 'error' as const,
|
|
169
|
+
duration: 999999,
|
|
170
|
+
onDismiss: () => {},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function createMockToastWarningProps() {
|
|
175
|
+
return {
|
|
176
|
+
message: 'Rate limit approaching',
|
|
177
|
+
type: 'warning' as const,
|
|
178
|
+
duration: 999999,
|
|
179
|
+
onDismiss: () => {},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function createMockKpiStripProps() {
|
|
184
|
+
const now = Date.now();
|
|
185
|
+
return {
|
|
186
|
+
totalCost: 12.43,
|
|
187
|
+
totalTokens: 48250,
|
|
188
|
+
totalRequests: 128,
|
|
189
|
+
activeCount: 3,
|
|
190
|
+
deltaCost: 3.12,
|
|
191
|
+
deltaTokens: 12450,
|
|
192
|
+
windowSec: 300,
|
|
193
|
+
activity: {
|
|
194
|
+
instantRate: 48,
|
|
195
|
+
avgRate: 32,
|
|
196
|
+
isSpike: false,
|
|
197
|
+
},
|
|
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,
|
|
201
|
+
],
|
|
202
|
+
budget: {
|
|
203
|
+
limit: 25,
|
|
204
|
+
budgetCost: 12.43,
|
|
205
|
+
budgetType: 'daily' as const,
|
|
206
|
+
budgetTypeLabel: 'Daily',
|
|
207
|
+
warningPercent: 80,
|
|
208
|
+
criticalPercent: 90,
|
|
209
|
+
},
|
|
210
|
+
lastRefreshAt: now - 30000,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function createMockSpinnerProps() {
|
|
215
|
+
return {
|
|
216
|
+
color: '#7C3AED',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function createMockSessionDetailsDrawerProps() {
|
|
221
|
+
const now = Date.now();
|
|
222
|
+
return {
|
|
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',
|
|
229
|
+
startedAt: now - 10000000,
|
|
230
|
+
lastActivityAt: now,
|
|
231
|
+
status: 'active' as const,
|
|
232
|
+
totals: {
|
|
233
|
+
input: 1500000,
|
|
234
|
+
output: 200000,
|
|
235
|
+
cacheRead: 10000000,
|
|
236
|
+
cacheWrite: 5000000,
|
|
237
|
+
},
|
|
238
|
+
totalCostUsd: 16.05,
|
|
239
|
+
requestCount: 150,
|
|
240
|
+
streams: [
|
|
241
|
+
{
|
|
242
|
+
providerId: 'anthropic',
|
|
243
|
+
modelId: 'claude-3-opus-20240229',
|
|
244
|
+
tokens: {
|
|
245
|
+
input: 1500000,
|
|
246
|
+
output: 200000,
|
|
247
|
+
cacheRead: 10000000,
|
|
248
|
+
cacheWrite: 5000000,
|
|
249
|
+
},
|
|
250
|
+
requestCount: 150,
|
|
251
|
+
costUsd: 16.05,
|
|
252
|
+
costBreakdown: {
|
|
253
|
+
total: 16.05,
|
|
254
|
+
input: 0.01,
|
|
255
|
+
output: 1.01,
|
|
256
|
+
cacheRead: 10.03,
|
|
257
|
+
cacheWrite: 5.00,
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
costInDay: 16.05,
|
|
262
|
+
costInWeek: 16.05,
|
|
263
|
+
costInMonth: 16.05,
|
|
264
|
+
},
|
|
265
|
+
onClose: () => {},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
interface ComponentEntry {
|
|
270
|
+
name: string;
|
|
271
|
+
description: string;
|
|
272
|
+
defaultWidth: number;
|
|
273
|
+
defaultHeight: number;
|
|
274
|
+
render: () => ReactNode;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const COMPONENT_REGISTRY: Record<string, ComponentEntry> = {
|
|
278
|
+
'header': {
|
|
279
|
+
name: 'Header',
|
|
280
|
+
description: 'Top navigation header with title and view tabs',
|
|
281
|
+
defaultWidth: 100,
|
|
282
|
+
defaultHeight: 3,
|
|
283
|
+
render: () => <Header {...createMockHeaderProps()} />,
|
|
284
|
+
},
|
|
285
|
+
'status-bar': {
|
|
286
|
+
name: 'StatusBar',
|
|
287
|
+
description: 'Bottom status bar with refresh timing',
|
|
288
|
+
defaultWidth: 100,
|
|
289
|
+
defaultHeight: 3,
|
|
290
|
+
render: () => <StatusBar {...createMockStatusBarProps()} />,
|
|
291
|
+
},
|
|
292
|
+
'provider-card': {
|
|
293
|
+
name: 'ProviderCard',
|
|
294
|
+
description: 'Provider usage card with gauges (configured state)',
|
|
295
|
+
defaultWidth: 50,
|
|
296
|
+
defaultHeight: 20,
|
|
297
|
+
render: () => <ProviderCard {...createMockProviderCardProps()} />,
|
|
298
|
+
},
|
|
299
|
+
'provider-card-loading': {
|
|
300
|
+
name: 'ProviderCard (Loading)',
|
|
301
|
+
description: 'Provider card in loading state with skeleton',
|
|
302
|
+
defaultWidth: 50,
|
|
303
|
+
defaultHeight: 20,
|
|
304
|
+
render: () => <ProviderCard {...createMockProviderCardLoadingProps()} />,
|
|
305
|
+
},
|
|
306
|
+
'provider-card-unconfigured': {
|
|
307
|
+
name: 'ProviderCard (Unconfigured)',
|
|
308
|
+
description: 'Provider card not configured state',
|
|
309
|
+
defaultWidth: 50,
|
|
310
|
+
defaultHeight: 20,
|
|
311
|
+
render: () => <ProviderCard {...createMockProviderCardUnconfiguredProps()} />,
|
|
312
|
+
},
|
|
313
|
+
'provider-card-error': {
|
|
314
|
+
name: 'ProviderCard (Error)',
|
|
315
|
+
description: 'Provider card with error state',
|
|
316
|
+
defaultWidth: 50,
|
|
317
|
+
defaultHeight: 20,
|
|
318
|
+
render: () => <ProviderCard {...createMockProviderCardErrorProps()} />,
|
|
319
|
+
},
|
|
320
|
+
'usage-gauge': {
|
|
321
|
+
name: 'UsageGauge',
|
|
322
|
+
description: 'Single usage gauge with progress bar',
|
|
323
|
+
defaultWidth: 45,
|
|
324
|
+
defaultHeight: 5,
|
|
325
|
+
render: () => <UsageGauge {...createMockUsageGaugeProps()} />,
|
|
326
|
+
},
|
|
327
|
+
'toast': {
|
|
328
|
+
name: 'Toast (Success)',
|
|
329
|
+
description: 'Toast notification - success variant',
|
|
330
|
+
defaultWidth: 50,
|
|
331
|
+
defaultHeight: 8,
|
|
332
|
+
render: () => <Toast {...createMockToastProps()} />,
|
|
333
|
+
},
|
|
334
|
+
'toast-error': {
|
|
335
|
+
name: 'Toast (Error)',
|
|
336
|
+
description: 'Toast notification - error variant',
|
|
337
|
+
defaultWidth: 50,
|
|
338
|
+
defaultHeight: 8,
|
|
339
|
+
render: () => <Toast {...createMockToastErrorProps()} />,
|
|
340
|
+
},
|
|
341
|
+
'toast-warning': {
|
|
342
|
+
name: 'Toast (Warning)',
|
|
343
|
+
description: 'Toast notification - warning variant',
|
|
344
|
+
defaultWidth: 50,
|
|
345
|
+
defaultHeight: 8,
|
|
346
|
+
render: () => <Toast {...createMockToastWarningProps()} />,
|
|
347
|
+
},
|
|
348
|
+
'spinner': {
|
|
349
|
+
name: 'Spinner',
|
|
350
|
+
description: 'Animated loading spinner',
|
|
351
|
+
defaultWidth: 10,
|
|
352
|
+
defaultHeight: 3,
|
|
353
|
+
render: () => <Spinner {...createMockSpinnerProps()} />,
|
|
354
|
+
},
|
|
355
|
+
'skeleton-text': {
|
|
356
|
+
name: 'SkeletonText',
|
|
357
|
+
description: 'Shimmer loading placeholder for text',
|
|
358
|
+
defaultWidth: 20,
|
|
359
|
+
defaultHeight: 3,
|
|
360
|
+
render: () => <SkeletonText width={15} />,
|
|
361
|
+
},
|
|
362
|
+
'skeleton-gauge': {
|
|
363
|
+
name: 'SkeletonGauge',
|
|
364
|
+
description: 'Shimmer loading placeholder for gauge',
|
|
365
|
+
defaultWidth: 45,
|
|
366
|
+
defaultHeight: 5,
|
|
367
|
+
render: () => <SkeletonGauge barWidth={30} />,
|
|
368
|
+
},
|
|
369
|
+
'skeleton-provider': {
|
|
370
|
+
name: 'SkeletonProviderContent',
|
|
371
|
+
description: 'Full skeleton for provider card content',
|
|
372
|
+
defaultWidth: 45,
|
|
373
|
+
defaultHeight: 12,
|
|
374
|
+
render: () => <SkeletonProviderContent />,
|
|
375
|
+
},
|
|
376
|
+
'debug-console': {
|
|
377
|
+
name: 'DebugConsole',
|
|
378
|
+
description: 'Debug console with log entries',
|
|
379
|
+
defaultWidth: 100,
|
|
380
|
+
defaultHeight: 20,
|
|
381
|
+
render: () => <DebugConsole height={15} follow={true} />,
|
|
382
|
+
},
|
|
383
|
+
'kpi-strip': {
|
|
384
|
+
name: 'KpiStrip',
|
|
385
|
+
description: 'KPI strip with activity sparkline ramp',
|
|
386
|
+
defaultWidth: 120,
|
|
387
|
+
defaultHeight: 6,
|
|
388
|
+
render: () => <KpiStrip {...createMockKpiStripProps()} />,
|
|
389
|
+
},
|
|
390
|
+
'historical-trends': {
|
|
391
|
+
name: 'HistoricalTrendsView',
|
|
392
|
+
description: 'ASCII chart showing cost trends over time',
|
|
393
|
+
defaultWidth: 80,
|
|
394
|
+
defaultHeight: 20,
|
|
395
|
+
render: () => <HistoricalTrendsView />,
|
|
396
|
+
},
|
|
397
|
+
'projects-view': {
|
|
398
|
+
name: 'ProjectsView',
|
|
399
|
+
description: 'Sessions grouped by project with cost breakdown',
|
|
400
|
+
defaultWidth: 100,
|
|
401
|
+
defaultHeight: 25,
|
|
402
|
+
render: () => (
|
|
403
|
+
<InputProvider>
|
|
404
|
+
<StorageProvider>
|
|
405
|
+
<TimeWindowProvider defaultWindow="24h">
|
|
406
|
+
<PluginProvider>
|
|
407
|
+
<AgentSessionProvider autoRefresh={false}>
|
|
408
|
+
<ProjectsView />
|
|
409
|
+
</AgentSessionProvider>
|
|
410
|
+
</PluginProvider>
|
|
411
|
+
</TimeWindowProvider>
|
|
412
|
+
</StorageProvider>
|
|
413
|
+
</InputProvider>
|
|
414
|
+
),
|
|
415
|
+
},
|
|
416
|
+
'settings-view': {
|
|
417
|
+
name: 'SettingsView',
|
|
418
|
+
description: 'Application settings with categories',
|
|
419
|
+
defaultWidth: 80,
|
|
420
|
+
defaultHeight: 25,
|
|
421
|
+
render: () => (
|
|
422
|
+
<ConfigProvider initialConfig={DEFAULT_CONFIG}>
|
|
423
|
+
<ToastProvider>
|
|
424
|
+
<SettingsView />
|
|
425
|
+
</ToastProvider>
|
|
426
|
+
</ConfigProvider>
|
|
427
|
+
),
|
|
428
|
+
},
|
|
429
|
+
'session-details': {
|
|
430
|
+
name: 'SessionDetailsDrawer',
|
|
431
|
+
description: 'Detailed view of a session',
|
|
432
|
+
defaultWidth: 100,
|
|
433
|
+
defaultHeight: 30,
|
|
434
|
+
render: () => <SessionDetailsDrawer {...createMockSessionDetailsDrawerProps()} />,
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
interface SnapshotOptions {
|
|
439
|
+
width?: number;
|
|
440
|
+
height?: number;
|
|
441
|
+
output?: string;
|
|
442
|
+
component: string;
|
|
443
|
+
list: boolean;
|
|
444
|
+
all: boolean;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function parseArgs(): SnapshotOptions {
|
|
448
|
+
const args = process.argv.slice(2);
|
|
449
|
+
const options: SnapshotOptions = {
|
|
450
|
+
component: 'debug-inspector',
|
|
451
|
+
list: false,
|
|
452
|
+
all: false,
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
for (let i = 0; i < args.length; i++) {
|
|
456
|
+
const arg = args[i]!;
|
|
457
|
+
if (arg === '--list' || arg === '-l') {
|
|
458
|
+
options.list = true;
|
|
459
|
+
} else if (arg === '--all' || arg === '-a') {
|
|
460
|
+
options.all = true;
|
|
461
|
+
} else if (arg === '--width' || arg === '-w') {
|
|
462
|
+
const val = args[i + 1];
|
|
463
|
+
if (val) {
|
|
464
|
+
options.width = parseInt(val, 10);
|
|
465
|
+
i++;
|
|
466
|
+
}
|
|
467
|
+
} else if (arg === '--height' || arg === '-h') {
|
|
468
|
+
const val = args[i + 1];
|
|
469
|
+
if (val) {
|
|
470
|
+
options.height = parseInt(val, 10);
|
|
471
|
+
i++;
|
|
472
|
+
}
|
|
473
|
+
} else if (arg === '--output' || arg === '-o') {
|
|
474
|
+
const val = args[i + 1];
|
|
475
|
+
if (val) {
|
|
476
|
+
options.output = val;
|
|
477
|
+
i++;
|
|
478
|
+
}
|
|
479
|
+
} else if (arg === '--help') {
|
|
480
|
+
printHelp();
|
|
481
|
+
process.exit(0);
|
|
482
|
+
} else if (!arg.startsWith('-')) {
|
|
483
|
+
options.component = arg;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return options;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function printHelp(): void {
|
|
491
|
+
console.log(`
|
|
492
|
+
Headless Component Snapshot Tool
|
|
493
|
+
|
|
494
|
+
Usage:
|
|
495
|
+
bun src/tui/debug/snapshot.tsx [component] [options]
|
|
496
|
+
|
|
497
|
+
Options:
|
|
498
|
+
--list, -l List all available components
|
|
499
|
+
--all, -a Snapshot all components
|
|
500
|
+
--width, -w <n> Set terminal width (default: component-specific)
|
|
501
|
+
--height, -h <n> Set terminal height (default: component-specific)
|
|
502
|
+
--output, -o <path> Output file path (default: auto-generated)
|
|
503
|
+
--help Show this help message
|
|
504
|
+
|
|
505
|
+
Examples:
|
|
506
|
+
bun src/tui/debug/snapshot.tsx --list
|
|
507
|
+
bun src/tui/debug/snapshot.tsx debug-inspector
|
|
508
|
+
bun src/tui/debug/snapshot.tsx provider-card --width 60 --height 25
|
|
509
|
+
bun src/tui/debug/snapshot.tsx toast --output my-toast.txt
|
|
510
|
+
bun src/tui/debug/snapshot.tsx --all
|
|
511
|
+
`);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function printComponentList(): void {
|
|
515
|
+
console.log('\nAvailable Components:\n');
|
|
516
|
+
console.log(' ID Description');
|
|
517
|
+
console.log(' ' + '-'.repeat(70));
|
|
518
|
+
|
|
519
|
+
for (const [id, entry] of Object.entries(COMPONENT_REGISTRY)) {
|
|
520
|
+
const paddedId = id.padEnd(28);
|
|
521
|
+
console.log(` ${paddedId}${entry.description}`);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
console.log('\nUsage: bun src/tui/debug/snapshot.tsx <component-id>\n');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async function snapshotComponent(
|
|
528
|
+
componentId: string,
|
|
529
|
+
width?: number,
|
|
530
|
+
height?: number
|
|
531
|
+
): Promise<string> {
|
|
532
|
+
const entry = COMPONENT_REGISTRY[componentId];
|
|
533
|
+
if (!entry) {
|
|
534
|
+
throw new Error(`Unknown component: ${componentId}`);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const finalWidth = width ?? entry.defaultWidth;
|
|
538
|
+
const finalHeight = height ?? entry.defaultHeight;
|
|
539
|
+
|
|
540
|
+
if (componentId === 'kpi-strip') {
|
|
541
|
+
const driver = await createDriver({ width: finalWidth, height: finalHeight });
|
|
542
|
+
await driver.launch();
|
|
543
|
+
await driver.waitForStable();
|
|
544
|
+
const frame = await driver.capture();
|
|
545
|
+
await driver.close();
|
|
546
|
+
return frame;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const element = entry.render();
|
|
550
|
+
|
|
551
|
+
const wrappedElement = (
|
|
552
|
+
<ThemeProvider>
|
|
553
|
+
<LogProvider>
|
|
554
|
+
<box width={finalWidth} height={finalHeight}>
|
|
555
|
+
{element}
|
|
556
|
+
</box>
|
|
557
|
+
</LogProvider>
|
|
558
|
+
</ThemeProvider>
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
const testSetup = await testRender(wrappedElement, {
|
|
562
|
+
width: finalWidth,
|
|
563
|
+
height: finalHeight,
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
await testSetup.renderOnce();
|
|
567
|
+
const frame = testSetup.captureCharFrame();
|
|
568
|
+
testSetup.renderer.destroy();
|
|
569
|
+
|
|
570
|
+
return frame;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
async function saveSnapshot(
|
|
574
|
+
componentId: string,
|
|
575
|
+
frame: string,
|
|
576
|
+
outputPath?: string
|
|
577
|
+
): Promise<string> {
|
|
578
|
+
const finalPath = outputPath ?? path.join(
|
|
579
|
+
getFramesDir(),
|
|
580
|
+
`snapshot-${componentId}-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
await fs.mkdir(path.dirname(finalPath), { recursive: true });
|
|
584
|
+
await fs.writeFile(finalPath, frame, 'utf-8');
|
|
585
|
+
|
|
586
|
+
return finalPath;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
async function main() {
|
|
590
|
+
const options = parseArgs();
|
|
591
|
+
|
|
592
|
+
if (options.list) {
|
|
593
|
+
printComponentList();
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (options.all) {
|
|
598
|
+
console.log('Capturing all components...\n');
|
|
599
|
+
const framesDir = getFramesDir();
|
|
600
|
+
const allDir = path.join(framesDir, `snapshot-all-${new Date().toISOString().replace(/[:.]/g, '-')}`);
|
|
601
|
+
await fs.mkdir(allDir, { recursive: true });
|
|
602
|
+
|
|
603
|
+
for (const [id, entry] of Object.entries(COMPONENT_REGISTRY)) {
|
|
604
|
+
try {
|
|
605
|
+
const frame = await snapshotComponent(id, options.width, options.height);
|
|
606
|
+
const outputPath = path.join(allDir, `${id}.txt`);
|
|
607
|
+
await fs.writeFile(outputPath, frame, 'utf-8');
|
|
608
|
+
console.log(` ✓ ${entry.name} -> ${id}.txt`);
|
|
609
|
+
} catch (err) {
|
|
610
|
+
console.error(` ✗ ${entry.name}: ${err}`);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
console.log(`\nAll snapshots saved to: ${allDir}`);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const entry = COMPONENT_REGISTRY[options.component];
|
|
619
|
+
if (!entry) {
|
|
620
|
+
console.error(`Unknown component: ${options.component}`);
|
|
621
|
+
console.error('\nAvailable components:');
|
|
622
|
+
for (const id of Object.keys(COMPONENT_REGISTRY)) {
|
|
623
|
+
console.error(` - ${id}`);
|
|
624
|
+
}
|
|
625
|
+
process.exit(1);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const width = options.width ?? entry.defaultWidth;
|
|
629
|
+
const height = options.height ?? entry.defaultHeight;
|
|
630
|
+
|
|
631
|
+
console.log(`Capturing ${entry.name} at ${width}x${height}...`);
|
|
632
|
+
|
|
633
|
+
const frame = await snapshotComponent(options.component, width, height);
|
|
634
|
+
const outputPath = await saveSnapshot(options.component, frame, options.output);
|
|
635
|
+
|
|
636
|
+
console.log(`Snapshot saved to: ${outputPath}`);
|
|
637
|
+
console.log('\n--- Preview ---');
|
|
638
|
+
console.log(frame);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
main().catch((err) => {
|
|
642
|
+
console.error('Snapshot failed:', err);
|
|
643
|
+
process.exit(1);
|
|
644
|
+
});
|