@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,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
|
+
}
|