@tokentop/ttop 0.2.2 → 0.4.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 (160) 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 +42 -38
  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 +36 -23
  19. package/src/plugins/notifications/visual-flash.ts +31 -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 +68 -57
  34. package/src/plugins/registry.ts +80 -71
  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/persistence-service.ts +206 -0
  73. package/src/storage/repos/agentSessions.ts +33 -23
  74. package/src/storage/repos/providerSnapshots.ts +5 -5
  75. package/src/storage/repos/usageEvents.ts +40 -28
  76. package/src/storage/retention.ts +146 -0
  77. package/src/storage/types.ts +5 -5
  78. package/src/tui/App.tsx +180 -137
  79. package/src/tui/components/CommandPalette.tsx +46 -34
  80. package/src/tui/components/DebugConsole.tsx +39 -33
  81. package/src/tui/components/DebugPanel.tsx +314 -183
  82. package/src/tui/components/FooterHints.tsx +2 -2
  83. package/src/tui/components/GhostProviderCard.tsx +37 -35
  84. package/src/tui/components/Header.tsx +68 -35
  85. package/src/tui/components/HelpOverlay.tsx +99 -29
  86. package/src/tui/components/InlineGauge.tsx +10 -7
  87. package/src/tui/components/InlineSparkline.tsx +17 -11
  88. package/src/tui/components/KpiStrip.tsx +128 -98
  89. package/src/tui/components/LimitGauge.tsx +96 -59
  90. package/src/tui/components/ModalBackdrop.tsx +4 -4
  91. package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
  92. package/src/tui/components/ProviderCard.tsx +232 -226
  93. package/src/tui/components/ProviderDetailPanel.tsx +133 -58
  94. package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
  95. package/src/tui/components/ProvidersList.tsx +215 -126
  96. package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
  97. package/src/tui/components/SessionsTable.tsx +320 -169
  98. package/src/tui/components/SettingsModal.tsx +593 -269
  99. package/src/tui/components/SidebarBreakdown.tsx +30 -16
  100. package/src/tui/components/Skeleton.tsx +24 -17
  101. package/src/tui/components/SmartSidebar.tsx +360 -258
  102. package/src/tui/components/Sparkline.tsx +64 -52
  103. package/src/tui/components/Spinner.tsx +3 -3
  104. package/src/tui/components/StatusBar.tsx +27 -27
  105. package/src/tui/components/ThemePicker.tsx +67 -66
  106. package/src/tui/components/Toast.tsx +9 -6
  107. package/src/tui/components/UsageGauge.tsx +10 -13
  108. package/src/tui/components/index.ts +5 -5
  109. package/src/tui/contexts/AgentSessionContext.tsx +164 -151
  110. package/src/tui/contexts/ConfigContext.tsx +15 -13
  111. package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
  112. package/src/tui/contexts/DemoModeContext.tsx +22 -15
  113. package/src/tui/contexts/DrawerContext.tsx +11 -9
  114. package/src/tui/contexts/InputContext.tsx +2 -2
  115. package/src/tui/contexts/LogContext.tsx +62 -47
  116. package/src/tui/contexts/PluginContext.tsx +238 -183
  117. package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
  118. package/src/tui/contexts/StorageContext.tsx +95 -149
  119. package/src/tui/contexts/ThemeContext.tsx +21 -16
  120. package/src/tui/contexts/TimeWindowContext.tsx +72 -61
  121. package/src/tui/contexts/ToastContext.tsx +10 -7
  122. package/src/tui/contexts/index.ts +9 -5
  123. package/src/tui/createApp.tsx +8 -12
  124. package/src/tui/debug/captureFrame.ts +25 -28
  125. package/src/tui/debug/snapshot.tsx +179 -162
  126. package/src/tui/driver/assertions.ts +30 -22
  127. package/src/tui/driver/cli.ts +231 -221
  128. package/src/tui/driver/coverage.ts +27 -44
  129. package/src/tui/driver/demo-snapshot.ts +33 -33
  130. package/src/tui/driver/diff.ts +26 -32
  131. package/src/tui/driver/driver.ts +43 -48
  132. package/src/tui/driver/index.ts +36 -40
  133. package/src/tui/driver/recorder.ts +37 -48
  134. package/src/tui/driver/test-workflow.ts +51 -51
  135. package/src/tui/driver/test.ts +25 -25
  136. package/src/tui/hooks/index.ts +24 -20
  137. package/src/tui/hooks/useAnimatedValue.ts +13 -18
  138. package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
  139. package/src/tui/hooks/useDashboardState.ts +26 -24
  140. package/src/tui/hooks/useEmaActivity.ts +82 -74
  141. package/src/tui/hooks/useEntranceAnimation.ts +11 -11
  142. package/src/tui/hooks/useExitAnimation.ts +7 -3
  143. package/src/tui/hooks/usePulse.ts +4 -4
  144. package/src/tui/hooks/useSafeRenderer.ts +5 -5
  145. package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
  146. package/src/tui/hooks/useValueFlash.ts +10 -14
  147. package/src/tui/index.tsx +29 -6
  148. package/src/tui/shutdown.ts +19 -0
  149. package/src/tui/utils/providerColor.ts +1 -1
  150. package/src/tui/views/Dashboard.tsx +164 -102
  151. package/src/tui/views/HistoricalTrendsView.tsx +579 -250
  152. package/src/tui/views/ProjectsView.tsx +495 -243
  153. package/src/tui/views/RealTimeDashboard.tsx +176 -116
  154. package/src/tui/views/SettingsView.tsx +225 -189
  155. package/src/tui/views/index.ts +2 -2
  156. package/src/types/opentui-test-utils.d.ts +7 -7
  157. package/src/utils/clipboard.ts +29 -29
  158. package/src/utils/currency.test.ts +53 -53
  159. package/src/utils/currency.ts +35 -32
  160. package/src/version.ts +1 -1
@@ -1,58 +1,58 @@
1
- import { createContext, useContext, useState, useCallback, useMemo, type ReactNode } from 'react';
1
+ import { createContext, type ReactNode, useCallback, useContext, useMemo, useState } from "react";
2
2
 
3
- export type TimeWindow = '5m' | '15m' | '1h' | '24h' | '7d' | '30d' | 'all';
3
+ export type TimeWindow = "5m" | "15m" | "1h" | "24h" | "7d" | "30d" | "all";
4
4
 
5
- export const TIME_WINDOW_OPTIONS: TimeWindow[] = ['5m', '15m', '1h', '24h', '7d', '30d', 'all'];
5
+ export const TIME_WINDOW_OPTIONS: TimeWindow[] = ["5m", "15m", "1h", "24h", "7d", "30d", "all"];
6
6
 
7
7
  export const TIME_WINDOW_LABELS: Record<TimeWindow, string> = {
8
- '5m': '5 min',
9
- '15m': '15 min',
10
- '1h': '1 hour',
11
- '24h': '24 hours',
12
- '7d': '7 days',
13
- '30d': '30 days',
14
- 'all': 'All time',
8
+ "5m": "5 min",
9
+ "15m": "15 min",
10
+ "1h": "1 hour",
11
+ "24h": "24 hours",
12
+ "7d": "7 days",
13
+ "30d": "30 days",
14
+ all: "All time",
15
15
  };
16
16
 
17
17
  export const TIME_WINDOW_MS: Record<TimeWindow, number | null> = {
18
- '5m': 5 * 60 * 1000,
19
- '15m': 15 * 60 * 1000,
20
- '1h': 60 * 60 * 1000,
21
- '24h': 24 * 60 * 60 * 1000,
22
- '7d': 7 * 24 * 60 * 60 * 1000,
23
- '30d': 30 * 24 * 60 * 60 * 1000,
24
- 'all': null,
18
+ "5m": 5 * 60 * 1000,
19
+ "15m": 15 * 60 * 1000,
20
+ "1h": 60 * 60 * 1000,
21
+ "24h": 24 * 60 * 60 * 1000,
22
+ "7d": 7 * 24 * 60 * 60 * 1000,
23
+ "30d": 30 * 24 * 60 * 60 * 1000,
24
+ all: null,
25
25
  };
26
26
 
27
- export type BudgetType = 'daily' | 'weekly' | 'monthly' | 'none';
27
+ export type BudgetType = "daily" | "weekly" | "monthly" | "none";
28
28
 
29
29
  export const TIME_WINDOW_BUDGET_TYPE: Record<TimeWindow, BudgetType> = {
30
- '5m': 'daily',
31
- '15m': 'daily',
32
- '1h': 'daily',
33
- '24h': 'daily',
34
- '7d': 'weekly',
35
- '30d': 'monthly',
36
- 'all': 'none',
30
+ "5m": "daily",
31
+ "15m": "daily",
32
+ "1h": "daily",
33
+ "24h": "daily",
34
+ "7d": "weekly",
35
+ "30d": "monthly",
36
+ all: "none",
37
37
  };
38
38
 
39
39
  export const BUDGET_TYPE_LABELS: Record<BudgetType, string> = {
40
- daily: 'Daily',
41
- weekly: 'Weekly',
42
- monthly: 'Monthly',
43
- none: 'Total',
40
+ daily: "Daily",
41
+ weekly: "Weekly",
42
+ monthly: "Monthly",
43
+ none: "Total",
44
44
  };
45
45
 
46
- export type BudgetLock = BudgetType | 'sync';
46
+ export type BudgetLock = BudgetType | "sync";
47
47
 
48
- const BUDGET_LOCK_CYCLE: BudgetLock[] = ['sync', 'daily', 'weekly', 'monthly'];
48
+ const BUDGET_LOCK_CYCLE: BudgetLock[] = ["sync", "daily", "weekly", "monthly"];
49
49
 
50
50
  export const BUDGET_LOCK_LABELS: Record<BudgetLock, string> = {
51
- sync: 'Sync',
52
- daily: 'Daily',
53
- weekly: 'Weekly',
54
- monthly: 'Monthly',
55
- none: 'None',
51
+ sync: "Sync",
52
+ daily: "Daily",
53
+ weekly: "Weekly",
54
+ monthly: "Monthly",
55
+ none: "None",
56
56
  };
57
57
 
58
58
  interface TimeWindowContextValue {
@@ -90,9 +90,9 @@ function getPrevWindow(current: TimeWindow): TimeWindow {
90
90
  return TIME_WINDOW_OPTIONS[prevIdx] as TimeWindow;
91
91
  }
92
92
 
93
- export function TimeWindowProvider({ children, defaultWindow = '24h' }: TimeWindowProviderProps) {
93
+ export function TimeWindowProvider({ children, defaultWindow = "24h" }: TimeWindowProviderProps) {
94
94
  const [window, setWindowState] = useState<TimeWindow>(defaultWindow);
95
- const [budgetLock, setBudgetLock] = useState<BudgetLock>('sync');
95
+ const [budgetLock, setBudgetLock] = useState<BudgetLock>("sync");
96
96
 
97
97
  const setWindow = useCallback((w: TimeWindow) => {
98
98
  setWindowState(w);
@@ -107,7 +107,7 @@ export function TimeWindowProvider({ children, defaultWindow = '24h' }: TimeWind
107
107
  }, []);
108
108
 
109
109
  const cycleBudgetLock = useCallback(() => {
110
- setBudgetLock(current => {
110
+ setBudgetLock((current) => {
111
111
  const idx = BUDGET_LOCK_CYCLE.indexOf(current);
112
112
  const nextIdx = (idx + 1) % BUDGET_LOCK_CYCLE.length;
113
113
  return BUDGET_LOCK_CYCLE[nextIdx] as BudgetLock;
@@ -116,7 +116,7 @@ export function TimeWindowProvider({ children, defaultWindow = '24h' }: TimeWind
116
116
 
117
117
  const windowMs = TIME_WINDOW_MS[window];
118
118
  const windowLabel = TIME_WINDOW_LABELS[window];
119
- const budgetType = budgetLock === 'sync' ? TIME_WINDOW_BUDGET_TYPE[window] : budgetLock;
119
+ const budgetType = budgetLock === "sync" ? TIME_WINDOW_BUDGET_TYPE[window] : budgetLock;
120
120
  const budgetTypeLabel = BUDGET_TYPE_LABELS[budgetType];
121
121
  const budgetLockLabel = BUDGET_LOCK_LABELS[budgetLock];
122
122
 
@@ -126,33 +126,44 @@ export function TimeWindowProvider({ children, defaultWindow = '24h' }: TimeWind
126
126
  return Date.now() - ms;
127
127
  }, [window]);
128
128
 
129
- const value: TimeWindowContextValue = useMemo(() => ({
130
- window,
131
- setWindow,
132
- cycleWindow,
133
- cycleWindowBack,
134
- windowMs,
135
- windowLabel,
136
- getWindowStart,
137
- budgetType,
138
- budgetTypeLabel,
139
- budgetLock,
140
- setBudgetLock,
141
- cycleBudgetLock,
142
- budgetLockLabel,
143
- }), [window, setWindow, cycleWindow, windowMs, windowLabel, getWindowStart, budgetType, budgetTypeLabel, budgetLock, cycleBudgetLock, budgetLockLabel]);
144
-
145
- return (
146
- <TimeWindowContext.Provider value={value}>
147
- {children}
148
- </TimeWindowContext.Provider>
129
+ const value: TimeWindowContextValue = useMemo(
130
+ () => ({
131
+ window,
132
+ setWindow,
133
+ cycleWindow,
134
+ cycleWindowBack,
135
+ windowMs,
136
+ windowLabel,
137
+ getWindowStart,
138
+ budgetType,
139
+ budgetTypeLabel,
140
+ budgetLock,
141
+ setBudgetLock,
142
+ cycleBudgetLock,
143
+ budgetLockLabel,
144
+ }),
145
+ [
146
+ window,
147
+ setWindow,
148
+ cycleWindow,
149
+ windowMs,
150
+ windowLabel,
151
+ getWindowStart,
152
+ budgetType,
153
+ budgetTypeLabel,
154
+ budgetLock,
155
+ cycleBudgetLock,
156
+ budgetLockLabel,
157
+ ],
149
158
  );
159
+
160
+ return <TimeWindowContext.Provider value={value}>{children}</TimeWindowContext.Provider>;
150
161
  }
151
162
 
152
163
  export function useTimeWindow(): TimeWindowContextValue {
153
164
  const context = useContext(TimeWindowContext);
154
165
  if (!context) {
155
- throw new Error('useTimeWindow must be used within TimeWindowProvider');
166
+ throw new Error("useTimeWindow must be used within TimeWindowProvider");
156
167
  }
157
168
  return context;
158
169
  }
@@ -1,13 +1,13 @@
1
- import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
1
+ import { createContext, type ReactNode, useCallback, useContext, useState } from "react";
2
2
 
3
3
  interface ToastState {
4
4
  message: string;
5
- type: 'info' | 'success' | 'warning' | 'error';
5
+ type: "info" | "success" | "warning" | "error";
6
6
  }
7
7
 
8
8
  interface ToastContextValue {
9
9
  toast: ToastState | null;
10
- showToast: (message: string, type?: 'info' | 'success' | 'warning' | 'error') => void;
10
+ showToast: (message: string, type?: "info" | "success" | "warning" | "error") => void;
11
11
  dismissToast: () => void;
12
12
  }
13
13
 
@@ -16,9 +16,12 @@ const ToastContext = createContext<ToastContextValue | null>(null);
16
16
  export function ToastProvider({ children }: { children: ReactNode }) {
17
17
  const [toast, setToast] = useState<ToastState | null>(null);
18
18
 
19
- const showToast = useCallback((message: string, type: 'info' | 'success' | 'warning' | 'error' = 'success') => {
20
- setToast({ message, type });
21
- }, []);
19
+ const showToast = useCallback(
20
+ (message: string, type: "info" | "success" | "warning" | "error" = "success") => {
21
+ setToast({ message, type });
22
+ },
23
+ [],
24
+ );
22
25
 
23
26
  const dismissToast = useCallback(() => {
24
27
  setToast(null);
@@ -34,7 +37,7 @@ export function ToastProvider({ children }: { children: ReactNode }) {
34
37
  export function useToastContext() {
35
38
  const context = useContext(ToastContext);
36
39
  if (!context) {
37
- throw new Error('useToastContext must be used within a ToastProvider');
40
+ throw new Error("useToastContext must be used within a ToastProvider");
38
41
  }
39
42
  return context;
40
43
  }
@@ -1,5 +1,9 @@
1
- export { ThemeProvider, useTheme, useColors } from './ThemeContext.tsx';
2
- export { PluginProvider, usePlugins, useProvider, useConfiguredProviders } from './PluginContext.tsx';
3
- export { LogProvider, useLogs, useLogger, type LogLevel, type LogEntry } from './LogContext.tsx';
4
- export { InputProvider, useInputFocus } from './InputContext.tsx';
5
-
1
+ export { InputProvider, useInputFocus } from "./InputContext.tsx";
2
+ export { type LogEntry, type LogLevel, LogProvider, useLogger, useLogs } from "./LogContext.tsx";
3
+ export {
4
+ PluginProvider,
5
+ useConfiguredProviders,
6
+ usePlugins,
7
+ useProvider,
8
+ } from "./PluginContext.tsx";
9
+ export { ThemeProvider, useColors, useTheme } from "./ThemeContext.tsx";
@@ -1,7 +1,7 @@
1
- import type { ReactNode } from 'react';
2
- import type { DemoPreset } from '@/demo/simulator.ts';
3
- import { TestModeContext } from './hooks/useSafeRenderer.ts';
4
- import { App } from './App.tsx';
1
+ import type { ReactNode } from "react";
2
+ import type { DemoPreset } from "@/demo/simulator.ts";
3
+ import { App } from "./App.tsx";
4
+ import { TestModeContext } from "./hooks/useSafeRenderer.ts";
5
5
 
6
6
  export { App };
7
7
 
@@ -24,7 +24,7 @@ export const DEFAULT_APP_OPTIONS = {
24
24
 
25
25
  export function createAppElement(options: CreateAppOptions = {}): ReactNode {
26
26
  const isTestMode = options.testMode ?? DEFAULT_APP_OPTIONS.testMode;
27
-
27
+
28
28
  const appElement = (
29
29
  <App
30
30
  debug={options.debug ?? DEFAULT_APP_OPTIONS.debug}
@@ -34,14 +34,10 @@ export function createAppElement(options: CreateAppOptions = {}): ReactNode {
34
34
  {...(options.cliPlugins ? { cliPlugins: options.cliPlugins } : {})}
35
35
  />
36
36
  );
37
-
37
+
38
38
  if (isTestMode) {
39
- return (
40
- <TestModeContext.Provider value={true}>
41
- {appElement}
42
- </TestModeContext.Provider>
43
- );
39
+ return <TestModeContext.Provider value={true}>{appElement}</TestModeContext.Provider>;
44
40
  }
45
-
41
+
46
42
  return appElement;
47
43
  }
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * Frame Capture Utilities for TUI Debugging
3
- *
3
+ *
4
4
  * Provides functions to capture the current terminal render buffer as text,
5
5
  * enabling AI-assisted debugging of visual issues.
6
- *
6
+ *
7
7
  * @see docs/debugging.md for comprehensive usage guide
8
8
  */
9
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';
10
+ import type { CliRenderer } from "@opentui/core";
11
+ import * as fs from "fs/promises";
12
+ import * as path from "path";
13
+ import { PATHS } from "@/storage/paths.ts";
14
14
 
15
15
  /** Metadata stored alongside each frame capture */
16
16
  export interface FrameMetadata {
@@ -41,13 +41,13 @@ export interface BurstConfig {
41
41
  * Get the frames output directory
42
42
  */
43
43
  export function getFramesDir(): string {
44
- return path.join(PATHS.data.logs, 'frames');
44
+ return path.join(PATHS.data.logs, "frames");
45
45
  }
46
46
 
47
47
  /**
48
48
  * Capture the current render buffer as a text string.
49
49
  * This captures exactly what is displayed in the terminal.
50
- *
50
+ *
51
51
  * @param renderer - The CliRenderer instance
52
52
  * @returns The current frame as a text string with line breaks
53
53
  */
@@ -60,30 +60,30 @@ export function captureFrameText(renderer: CliRenderer): string {
60
60
  * Generate a timestamp-based filename prefix
61
61
  */
62
62
  function generateTimestamp(): string {
63
- return new Date().toISOString().replace(/[:.]/g, '-');
63
+ return new Date().toISOString().replace(/[:.]/g, "-");
64
64
  }
65
65
 
66
66
  /**
67
67
  * Capture the current frame and write to files.
68
- *
68
+ *
69
69
  * Creates two files:
70
70
  * - frame-{timestamp}-{label}.txt - The raw frame text
71
71
  * - frame-{timestamp}-{label}.json - Metadata (dimensions, timestamp, etc.)
72
- *
72
+ *
73
73
  * @param renderer - The CliRenderer instance
74
74
  * @param label - Optional label for the capture (default: "manual")
75
75
  * @returns Information about the captured files
76
76
  */
77
77
  export async function captureFrameToFile(
78
78
  renderer: CliRenderer,
79
- label = 'manual'
79
+ label = "manual",
80
80
  ): Promise<CaptureResult> {
81
81
  const dir = getFramesDir();
82
82
  await fs.mkdir(dir, { recursive: true });
83
83
 
84
84
  const ts = generateTimestamp();
85
85
  const baseName = `frame-${ts}-${label}`;
86
-
86
+
87
87
  const frame = captureFrameText(renderer);
88
88
  const metadata: FrameMetadata = {
89
89
  timestamp: new Date().toISOString(),
@@ -96,8 +96,8 @@ export async function captureFrameToFile(
96
96
  const metadataPath = path.join(dir, `${baseName}.json`);
97
97
 
98
98
  await Promise.all([
99
- fs.writeFile(framePath, frame, 'utf-8'),
100
- fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8'),
99
+ fs.writeFile(framePath, frame, "utf-8"),
100
+ fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8"),
101
101
  ]);
102
102
 
103
103
  return { framePath, metadataPath, metadata };
@@ -133,7 +133,7 @@ export class BurstRecorder {
133
133
  */
134
134
  async start(): Promise<CaptureResult[]> {
135
135
  if (this.isRecording) {
136
- throw new Error('Burst recording already in progress');
136
+ throw new Error("Burst recording already in progress");
137
137
  }
138
138
 
139
139
  this.isRecording = true;
@@ -150,7 +150,7 @@ export class BurstRecorder {
150
150
  }
151
151
 
152
152
  this.lastCaptureTime = now;
153
-
153
+
154
154
  try {
155
155
  const result = await this.captureFrame();
156
156
  this.capturedFrames.push(result);
@@ -184,7 +184,7 @@ export class BurstRecorder {
184
184
  }
185
185
 
186
186
  this.isRecording = false;
187
-
187
+
188
188
  if (this.cleanupFn) {
189
189
  this.cleanupFn();
190
190
  this.cleanupFn = null;
@@ -219,13 +219,13 @@ export class BurstRecorder {
219
219
  const dir = path.join(getFramesDir(), `burst-${this.burstId}`);
220
220
  await fs.mkdir(dir, { recursive: true });
221
221
 
222
- const frameNum = String(this.framesCaptured + 1).padStart(4, '0');
222
+ const frameNum = String(this.framesCaptured + 1).padStart(4, "0");
223
223
  const baseName = `frame-${frameNum}`;
224
-
224
+
225
225
  const frame = captureFrameText(this.renderer);
226
226
  const metadata: FrameMetadata = {
227
227
  timestamp: new Date().toISOString(),
228
- label: 'burst',
228
+ label: "burst",
229
229
  width: this.renderer.width,
230
230
  height: this.renderer.height,
231
231
  frameIndex: this.framesCaptured + 1,
@@ -236,8 +236,8 @@ export class BurstRecorder {
236
236
  const metadataPath = path.join(dir, `${baseName}.json`);
237
237
 
238
238
  await Promise.all([
239
- fs.writeFile(framePath, frame, 'utf-8'),
240
- fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8'),
239
+ fs.writeFile(framePath, frame, "utf-8"),
240
+ fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8"),
241
241
  ]);
242
242
 
243
243
  return { framePath, metadataPath, metadata };
@@ -246,7 +246,7 @@ export class BurstRecorder {
246
246
 
247
247
  /**
248
248
  * Create a burst recorder for capturing multiple frames.
249
- *
249
+ *
250
250
  * @example
251
251
  * ```ts
252
252
  * const recorder = createBurstRecorder(renderer, { frameCount: 5 });
@@ -254,9 +254,6 @@ export class BurstRecorder {
254
254
  * console.log(`Captured ${frames.length} frames`);
255
255
  * ```
256
256
  */
257
- export function createBurstRecorder(
258
- renderer: CliRenderer,
259
- config: BurstConfig
260
- ): BurstRecorder {
257
+ export function createBurstRecorder(renderer: CliRenderer, config: BurstConfig): BurstRecorder {
261
258
  return new BurstRecorder(renderer, config);
262
259
  }