@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.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +273 -0
  3. package/bin/ttop.js +49 -0
  4. package/package.json +68 -0
  5. package/src/agents/aggregator.ts +176 -0
  6. package/src/agents/costing.ts +74 -0
  7. package/src/agents/index.ts +12 -0
  8. package/src/agents/types.ts +71 -0
  9. package/src/cli.ts +167 -0
  10. package/src/config/schema.test.ts +166 -0
  11. package/src/config/schema.ts +165 -0
  12. package/src/demo/simulator.ts +745 -0
  13. package/src/plugins/agents/index.ts +2 -0
  14. package/src/plugins/auth-sources.ts +186 -0
  15. package/src/plugins/lifecycle.ts +177 -0
  16. package/src/plugins/loader.test.ts +195 -0
  17. package/src/plugins/loader.ts +282 -0
  18. package/src/plugins/notification-bus.ts +153 -0
  19. package/src/plugins/notifications/index.ts +2 -0
  20. package/src/plugins/notifications/terminal-bell.ts +66 -0
  21. package/src/plugins/notifications/visual-flash.ts +57 -0
  22. package/src/plugins/npm-installer.ts +164 -0
  23. package/src/plugins/plugin-context-factory.ts +67 -0
  24. package/src/plugins/plugin-host.ts +205 -0
  25. package/src/plugins/providers/anthropic.ts +325 -0
  26. package/src/plugins/providers/antigravity.ts +309 -0
  27. package/src/plugins/providers/codex.ts +250 -0
  28. package/src/plugins/providers/gemini.ts +391 -0
  29. package/src/plugins/providers/github-copilot.ts +194 -0
  30. package/src/plugins/providers/index.ts +12 -0
  31. package/src/plugins/providers/minimax.ts +223 -0
  32. package/src/plugins/providers/openai-api.ts +250 -0
  33. package/src/plugins/providers/opencode-zen.ts +125 -0
  34. package/src/plugins/providers/perplexity.ts +177 -0
  35. package/src/plugins/providers/zai.ts +225 -0
  36. package/src/plugins/registry.ts +251 -0
  37. package/src/plugins/sandbox-guard.test.ts +191 -0
  38. package/src/plugins/sandbox-guard.ts +155 -0
  39. package/src/plugins/sandbox.ts +138 -0
  40. package/src/plugins/themes/catppuccin-latte.ts +45 -0
  41. package/src/plugins/themes/catppuccin-mocha.ts +45 -0
  42. package/src/plugins/themes/claude-code.ts +45 -0
  43. package/src/plugins/themes/dracula.ts +45 -0
  44. package/src/plugins/themes/github-light.ts +45 -0
  45. package/src/plugins/themes/gruvbox-dark.ts +45 -0
  46. package/src/plugins/themes/gruvbox-light.ts +45 -0
  47. package/src/plugins/themes/index.ts +15 -0
  48. package/src/plugins/themes/kanagawa.ts +45 -0
  49. package/src/plugins/themes/nord.ts +45 -0
  50. package/src/plugins/themes/one-dark.ts +45 -0
  51. package/src/plugins/themes/opencode.ts +47 -0
  52. package/src/plugins/themes/rose-pine-dawn.ts +45 -0
  53. package/src/plugins/themes/rose-pine.ts +45 -0
  54. package/src/plugins/themes/solarized-light.ts +45 -0
  55. package/src/plugins/themes/tokyo-night.ts +49 -0
  56. package/src/plugins/types/agent.ts +108 -0
  57. package/src/plugins/types/base.ts +267 -0
  58. package/src/plugins/types/index.ts +19 -0
  59. package/src/plugins/types/notification.ts +51 -0
  60. package/src/plugins/types/provider.ts +233 -0
  61. package/src/plugins/types/theme.ts +70 -0
  62. package/src/plugins/update-checker.ts +177 -0
  63. package/src/pricing/estimator.test.ts +181 -0
  64. package/src/pricing/estimator.ts +80 -0
  65. package/src/pricing/fallback.test.ts +71 -0
  66. package/src/pricing/fallback.ts +62 -0
  67. package/src/pricing/index.ts +66 -0
  68. package/src/pricing/models-dev.ts +130 -0
  69. package/src/storage/database.ts +613 -0
  70. package/src/storage/db.ts +64 -0
  71. package/src/storage/index.ts +66 -0
  72. package/src/storage/migrations/index.ts +189 -0
  73. package/src/storage/paths.ts +24 -0
  74. package/src/storage/repos/agentSessions.ts +210 -0
  75. package/src/storage/repos/providerSnapshots.ts +116 -0
  76. package/src/storage/repos/usageEvents.ts +334 -0
  77. package/src/storage/types.ts +288 -0
  78. package/src/tui/App.tsx +381 -0
  79. package/src/tui/components/CommandPalette.tsx +166 -0
  80. package/src/tui/components/DebugConsole.tsx +161 -0
  81. package/src/tui/components/DebugPanel.tsx +660 -0
  82. package/src/tui/components/FooterHints.tsx +22 -0
  83. package/src/tui/components/GhostProviderCard.tsx +45 -0
  84. package/src/tui/components/Header.tsx +125 -0
  85. package/src/tui/components/HelpOverlay.tsx +45 -0
  86. package/src/tui/components/InlineGauge.tsx +34 -0
  87. package/src/tui/components/InlineSparkline.tsx +78 -0
  88. package/src/tui/components/KpiStrip.tsx +246 -0
  89. package/src/tui/components/LimitGauge.tsx +186 -0
  90. package/src/tui/components/ModalBackdrop.tsx +42 -0
  91. package/src/tui/components/ProviderAggregateStrip.tsx +200 -0
  92. package/src/tui/components/ProviderCard.tsx +309 -0
  93. package/src/tui/components/ProviderDetailPanel.tsx +237 -0
  94. package/src/tui/components/ProviderLimitsPanel.tsx +308 -0
  95. package/src/tui/components/ProvidersList.tsx +380 -0
  96. package/src/tui/components/SessionDetailsDrawer.tsx +317 -0
  97. package/src/tui/components/SessionsTable.tsx +401 -0
  98. package/src/tui/components/SettingsModal.tsx +587 -0
  99. package/src/tui/components/SidebarBreakdown.tsx +87 -0
  100. package/src/tui/components/Skeleton.tsx +102 -0
  101. package/src/tui/components/SmartSidebar.tsx +644 -0
  102. package/src/tui/components/Sparkline.tsx +180 -0
  103. package/src/tui/components/Spinner.tsx +36 -0
  104. package/src/tui/components/StatusBar.tsx +96 -0
  105. package/src/tui/components/ThemePicker.tsx +234 -0
  106. package/src/tui/components/Toast.tsx +61 -0
  107. package/src/tui/components/UsageGauge.tsx +80 -0
  108. package/src/tui/components/index.ts +5 -0
  109. package/src/tui/contexts/AgentSessionContext.tsx +307 -0
  110. package/src/tui/contexts/ConfigContext.tsx +141 -0
  111. package/src/tui/contexts/DashboardRuntimeContext.tsx +102 -0
  112. package/src/tui/contexts/DemoModeContext.tsx +60 -0
  113. package/src/tui/contexts/DrawerContext.tsx +42 -0
  114. package/src/tui/contexts/InputContext.tsx +30 -0
  115. package/src/tui/contexts/LogContext.tsx +150 -0
  116. package/src/tui/contexts/PluginContext.tsx +424 -0
  117. package/src/tui/contexts/RealTimeActivityContext.tsx +119 -0
  118. package/src/tui/contexts/StorageContext.tsx +233 -0
  119. package/src/tui/contexts/ThemeContext.tsx +95 -0
  120. package/src/tui/contexts/TimeWindowContext.tsx +158 -0
  121. package/src/tui/contexts/ToastContext.tsx +40 -0
  122. package/src/tui/contexts/index.ts +5 -0
  123. package/src/tui/createApp.tsx +47 -0
  124. package/src/tui/debug/captureFrame.ts +262 -0
  125. package/src/tui/debug/snapshot.tsx +644 -0
  126. package/src/tui/driver/assertions.ts +279 -0
  127. package/src/tui/driver/cli.ts +717 -0
  128. package/src/tui/driver/coverage.ts +204 -0
  129. package/src/tui/driver/demo-snapshot.ts +66 -0
  130. package/src/tui/driver/diff.ts +166 -0
  131. package/src/tui/driver/driver.ts +270 -0
  132. package/src/tui/driver/index.ts +53 -0
  133. package/src/tui/driver/recorder.ts +303 -0
  134. package/src/tui/driver/test-workflow.ts +115 -0
  135. package/src/tui/driver/test.ts +47 -0
  136. package/src/tui/hooks/index.ts +20 -0
  137. package/src/tui/hooks/useAnimatedValue.ts +113 -0
  138. package/src/tui/hooks/useDashboardKeyboard.ts +341 -0
  139. package/src/tui/hooks/useDashboardState.ts +116 -0
  140. package/src/tui/hooks/useEmaActivity.ts +223 -0
  141. package/src/tui/hooks/useEntranceAnimation.ts +62 -0
  142. package/src/tui/hooks/useExitAnimation.ts +137 -0
  143. package/src/tui/hooks/usePulse.ts +138 -0
  144. package/src/tui/hooks/useSafeRenderer.ts +24 -0
  145. package/src/tui/hooks/useValueFlash.ts +111 -0
  146. package/src/tui/index.tsx +50 -0
  147. package/src/tui/utils/providerColor.ts +32 -0
  148. package/src/tui/views/Dashboard.tsx +340 -0
  149. package/src/tui/views/HistoricalTrendsView.tsx +1431 -0
  150. package/src/tui/views/ProjectsView.tsx +1277 -0
  151. package/src/tui/views/RealTimeDashboard.tsx +393 -0
  152. package/src/tui/views/SettingsView.tsx +465 -0
  153. package/src/tui/views/index.ts +2 -0
  154. package/src/types/global.d.ts +5 -0
  155. package/src/types/opentui-test-utils.d.ts +18 -0
  156. package/src/utils/clipboard.ts +118 -0
  157. package/src/utils/currency.test.ts +106 -0
  158. package/src/utils/currency.ts +79 -0
  159. package/src/version.ts +1 -0
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Frame Capture Utilities for TUI Debugging
3
+ *
4
+ * Provides functions to capture the current terminal render buffer as text,
5
+ * enabling AI-assisted debugging of visual issues.
6
+ *
7
+ * @see docs/debugging.md for comprehensive usage guide
8
+ */
9
+
10
+ import * as fs from 'fs/promises';
11
+ import * as path from 'path';
12
+ import type { CliRenderer } from '@opentui/core';
13
+ import { PATHS } from '@/storage/paths.ts';
14
+
15
+ /** Metadata stored alongside each frame capture */
16
+ export interface FrameMetadata {
17
+ timestamp: string;
18
+ label: string;
19
+ width: number;
20
+ height: number;
21
+ frameIndex?: number;
22
+ burstId?: string;
23
+ }
24
+
25
+ /** Result from a frame capture operation */
26
+ export interface CaptureResult {
27
+ framePath: string;
28
+ metadataPath: string;
29
+ metadata: FrameMetadata;
30
+ }
31
+
32
+ /** Configuration for burst recording */
33
+ export interface BurstConfig {
34
+ /** Number of frames to capture */
35
+ frameCount: number;
36
+ /** Minimum interval between captures in ms (to avoid duplicates) */
37
+ minInterval?: number;
38
+ }
39
+
40
+ /**
41
+ * Get the frames output directory
42
+ */
43
+ export function getFramesDir(): string {
44
+ return path.join(PATHS.data.logs, 'frames');
45
+ }
46
+
47
+ /**
48
+ * Capture the current render buffer as a text string.
49
+ * This captures exactly what is displayed in the terminal.
50
+ *
51
+ * @param renderer - The CliRenderer instance
52
+ * @returns The current frame as a text string with line breaks
53
+ */
54
+ export function captureFrameText(renderer: CliRenderer): string {
55
+ const bytes = renderer.currentRenderBuffer.getRealCharBytes(true);
56
+ return new TextDecoder().decode(bytes);
57
+ }
58
+
59
+ /**
60
+ * Generate a timestamp-based filename prefix
61
+ */
62
+ function generateTimestamp(): string {
63
+ return new Date().toISOString().replace(/[:.]/g, '-');
64
+ }
65
+
66
+ /**
67
+ * Capture the current frame and write to files.
68
+ *
69
+ * Creates two files:
70
+ * - frame-{timestamp}-{label}.txt - The raw frame text
71
+ * - frame-{timestamp}-{label}.json - Metadata (dimensions, timestamp, etc.)
72
+ *
73
+ * @param renderer - The CliRenderer instance
74
+ * @param label - Optional label for the capture (default: "manual")
75
+ * @returns Information about the captured files
76
+ */
77
+ export async function captureFrameToFile(
78
+ renderer: CliRenderer,
79
+ label = 'manual'
80
+ ): Promise<CaptureResult> {
81
+ const dir = getFramesDir();
82
+ await fs.mkdir(dir, { recursive: true });
83
+
84
+ const ts = generateTimestamp();
85
+ const baseName = `frame-${ts}-${label}`;
86
+
87
+ const frame = captureFrameText(renderer);
88
+ const metadata: FrameMetadata = {
89
+ timestamp: new Date().toISOString(),
90
+ label,
91
+ width: renderer.width,
92
+ height: renderer.height,
93
+ };
94
+
95
+ const framePath = path.join(dir, `${baseName}.txt`);
96
+ const metadataPath = path.join(dir, `${baseName}.json`);
97
+
98
+ await Promise.all([
99
+ fs.writeFile(framePath, frame, 'utf-8'),
100
+ fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8'),
101
+ ]);
102
+
103
+ return { framePath, metadataPath, metadata };
104
+ }
105
+
106
+ /**
107
+ * Burst recorder for capturing multiple sequential frames.
108
+ * Useful for debugging dynamic/animated issues.
109
+ */
110
+ export class BurstRecorder {
111
+ private renderer: CliRenderer;
112
+ private config: Required<BurstConfig>;
113
+ private burstId: string;
114
+ private framesCaptured: number = 0;
115
+ private lastCaptureTime: number = 0;
116
+ private isRecording: boolean = false;
117
+ private cleanupFn: (() => void) | null = null;
118
+ private resolvePromise: ((results: CaptureResult[]) => void) | null = null;
119
+ private capturedFrames: CaptureResult[] = [];
120
+
121
+ constructor(renderer: CliRenderer, config: BurstConfig) {
122
+ this.renderer = renderer;
123
+ this.config = {
124
+ frameCount: config.frameCount,
125
+ minInterval: config.minInterval ?? 100, // Default 100ms between frames
126
+ };
127
+ this.burstId = generateTimestamp();
128
+ }
129
+
130
+ /**
131
+ * Start recording frames. Returns a promise that resolves when
132
+ * the burst is complete (all frames captured).
133
+ */
134
+ async start(): Promise<CaptureResult[]> {
135
+ if (this.isRecording) {
136
+ throw new Error('Burst recording already in progress');
137
+ }
138
+
139
+ this.isRecording = true;
140
+ this.framesCaptured = 0;
141
+ this.capturedFrames = [];
142
+
143
+ // Set up post-process hook to capture after each render
144
+ const postProcessFn = async () => {
145
+ if (!this.isRecording) return;
146
+
147
+ const now = Date.now();
148
+ if (now - this.lastCaptureTime < this.config.minInterval) {
149
+ return; // Skip if too soon
150
+ }
151
+
152
+ this.lastCaptureTime = now;
153
+
154
+ try {
155
+ const result = await this.captureFrame();
156
+ this.capturedFrames.push(result);
157
+ this.framesCaptured++;
158
+
159
+ if (this.framesCaptured >= this.config.frameCount) {
160
+ this.stop();
161
+ }
162
+ } catch {
163
+ // Silently continue on capture errors
164
+ }
165
+ };
166
+
167
+ // Add the post-process function
168
+ this.renderer.addPostProcessFn(postProcessFn);
169
+ this.cleanupFn = () => {
170
+ this.renderer.removePostProcessFn(postProcessFn);
171
+ };
172
+
173
+ return new Promise((resolve) => {
174
+ this.resolvePromise = resolve;
175
+ });
176
+ }
177
+
178
+ /**
179
+ * Stop recording and return captured frames.
180
+ */
181
+ stop(): CaptureResult[] {
182
+ if (!this.isRecording) {
183
+ return this.capturedFrames;
184
+ }
185
+
186
+ this.isRecording = false;
187
+
188
+ if (this.cleanupFn) {
189
+ this.cleanupFn();
190
+ this.cleanupFn = null;
191
+ }
192
+
193
+ if (this.resolvePromise) {
194
+ this.resolvePromise(this.capturedFrames);
195
+ this.resolvePromise = null;
196
+ }
197
+
198
+ return this.capturedFrames;
199
+ }
200
+
201
+ /**
202
+ * Check if currently recording
203
+ */
204
+ get recording(): boolean {
205
+ return this.isRecording;
206
+ }
207
+
208
+ /**
209
+ * Get progress info
210
+ */
211
+ get progress(): { captured: number; total: number } {
212
+ return {
213
+ captured: this.framesCaptured,
214
+ total: this.config.frameCount,
215
+ };
216
+ }
217
+
218
+ private async captureFrame(): Promise<CaptureResult> {
219
+ const dir = path.join(getFramesDir(), `burst-${this.burstId}`);
220
+ await fs.mkdir(dir, { recursive: true });
221
+
222
+ const frameNum = String(this.framesCaptured + 1).padStart(4, '0');
223
+ const baseName = `frame-${frameNum}`;
224
+
225
+ const frame = captureFrameText(this.renderer);
226
+ const metadata: FrameMetadata = {
227
+ timestamp: new Date().toISOString(),
228
+ label: 'burst',
229
+ width: this.renderer.width,
230
+ height: this.renderer.height,
231
+ frameIndex: this.framesCaptured + 1,
232
+ burstId: this.burstId,
233
+ };
234
+
235
+ const framePath = path.join(dir, `${baseName}.txt`);
236
+ const metadataPath = path.join(dir, `${baseName}.json`);
237
+
238
+ await Promise.all([
239
+ fs.writeFile(framePath, frame, 'utf-8'),
240
+ fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8'),
241
+ ]);
242
+
243
+ return { framePath, metadataPath, metadata };
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Create a burst recorder for capturing multiple frames.
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * const recorder = createBurstRecorder(renderer, { frameCount: 5 });
253
+ * const frames = await recorder.start();
254
+ * console.log(`Captured ${frames.length} frames`);
255
+ * ```
256
+ */
257
+ export function createBurstRecorder(
258
+ renderer: CliRenderer,
259
+ config: BurstConfig
260
+ ): BurstRecorder {
261
+ return new BurstRecorder(renderer, config);
262
+ }