@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,14 +1,14 @@
1
- import * as fs from 'fs/promises';
2
- import * as path from 'path';
3
- import { PATHS } from '@/storage/paths.ts';
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ import { PATHS } from "@/storage/paths.ts";
4
4
 
5
- export type TimeWindow = '5m' | '15m' | '1h' | '24h' | '7d' | '30d' | 'all';
5
+ export type TimeWindow = "5m" | "15m" | "1h" | "24h" | "7d" | "30d" | "all";
6
6
 
7
7
  export interface BudgetConfig {
8
8
  daily: number | null;
9
9
  weekly: number | null;
10
10
  monthly: number | null;
11
- currency: 'USD' | 'EUR' | 'GBP';
11
+ currency: "USD" | "EUR" | "GBP";
12
12
  }
13
13
 
14
14
  export interface AlertThresholds {
@@ -16,8 +16,8 @@ export interface AlertThresholds {
16
16
  criticalPercent: number;
17
17
  }
18
18
 
19
- export type SparklineStyle = 'braille' | 'block';
20
- export type SparklineOrientation = 'up' | 'down';
19
+ export type SparklineStyle = "braille" | "block";
20
+ export type SparklineOrientation = "up" | "down";
21
21
 
22
22
  export interface SparklineConfig {
23
23
  style: SparklineStyle;
@@ -41,11 +41,11 @@ export interface AppConfig {
41
41
  display: {
42
42
  defaultTimeWindow: TimeWindow;
43
43
  sidebarCollapsed: boolean;
44
- timeFormat: '12h' | '24h';
45
- numberFormat: 'full' | 'compact';
44
+ timeFormat: "12h" | "24h";
45
+ numberFormat: "full" | "compact";
46
46
  sparkline: SparklineConfig;
47
47
  theme: string;
48
- colorScheme: 'auto' | 'light' | 'dark';
48
+ colorScheme: "auto" | "light" | "dark";
49
49
  };
50
50
  notifications: {
51
51
  toastsEnabled: boolean;
@@ -67,19 +67,19 @@ export const DEFAULT_CONFIG: AppConfig = {
67
67
  pauseAutoRefresh: false,
68
68
  staleThresholdMs: 300000,
69
69
  },
70
- display: {
71
- defaultTimeWindow: '1h',
72
- sidebarCollapsed: false,
73
- timeFormat: '24h',
74
- numberFormat: 'compact',
75
- sparkline: {
76
- style: 'braille',
77
- orientation: 'up',
78
- showBaseline: true,
79
- },
80
- theme: 'tokyo-night',
81
- colorScheme: 'auto',
70
+ display: {
71
+ defaultTimeWindow: "1h",
72
+ sidebarCollapsed: false,
73
+ timeFormat: "24h",
74
+ numberFormat: "compact",
75
+ sparkline: {
76
+ style: "braille",
77
+ orientation: "up",
78
+ showBaseline: true,
82
79
  },
80
+ theme: "tokyo-night",
81
+ colorScheme: "auto",
82
+ },
83
83
  notifications: {
84
84
  toastsEnabled: true,
85
85
  soundEnabled: false,
@@ -88,7 +88,7 @@ export const DEFAULT_CONFIG: AppConfig = {
88
88
  daily: null,
89
89
  weekly: null,
90
90
  monthly: null,
91
- currency: 'USD',
91
+ currency: "USD",
92
92
  },
93
93
  alerts: {
94
94
  warningPercent: 80,
@@ -149,7 +149,7 @@ function deepMerge(target: AppConfig, source: Partial<AppConfig>): AppConfig {
149
149
 
150
150
  export async function loadConfig(): Promise<AppConfig> {
151
151
  try {
152
- const content = await fs.readFile(PATHS.config.file, 'utf-8');
152
+ const content = await fs.readFile(PATHS.config.file, "utf-8");
153
153
  const loaded = JSON.parse(content) as Partial<AppConfig>;
154
154
  return deepMerge(DEFAULT_CONFIG, loaded);
155
155
  } catch {
@@ -159,7 +159,7 @@ export async function loadConfig(): Promise<AppConfig> {
159
159
 
160
160
  export async function saveConfig(config: AppConfig): Promise<void> {
161
161
  await fs.mkdir(path.dirname(PATHS.config.file), { recursive: true });
162
- const tempFile = PATHS.config.file + '.tmp';
163
- await fs.writeFile(tempFile, JSON.stringify(config, null, 2), 'utf-8');
162
+ const tempFile = PATHS.config.file + ".tmp";
163
+ await fs.writeFile(tempFile, JSON.stringify(config, null, 2), "utf-8");
164
164
  await fs.rename(tempFile, PATHS.config.file);
165
165
  }
@@ -1,6 +1,6 @@
1
- import type { AgentSessionAggregate, AgentSessionStream } from '@/agents/types.ts';
2
- import type { ProviderUsageData } from '@/plugins/types/provider.ts';
3
- import type { UsageEventInsert } from '@/storage/types.ts';
1
+ import type { AgentSessionAggregate, AgentSessionStream } from "@/agents/types.ts";
2
+ import type { ProviderUsageData } from "@/plugins/types/provider.ts";
3
+ import type { UsageEventInsert } from "@/storage/types.ts";
4
4
 
5
5
  /**
6
6
  * Demo presets control the intensity of simulated activity.
@@ -8,7 +8,7 @@ import type { UsageEventInsert } from '@/storage/types.ts';
8
8
  * - normal: Moderate activity, balanced simulation (default)
9
9
  * - heavy: High activity, more sessions, faster token accumulation
10
10
  */
11
- export type DemoPreset = 'light' | 'normal' | 'heavy';
11
+ export type DemoPreset = "light" | "normal" | "heavy";
12
12
 
13
13
  export interface DemoPresetConfig {
14
14
  sessionCount: number;
@@ -53,8 +53,8 @@ export const DEMO_PRESETS: Record<DemoPreset, DemoPresetConfig> = {
53
53
  export interface DemoSessionSeed {
54
54
  sessionId: string;
55
55
  sessionName?: string;
56
- agentId: 'opencode' | 'claude-code' | 'cursor';
57
- agentName: 'OpenCode' | 'Claude Code' | 'Cursor';
56
+ agentId: "opencode" | "claude-code" | "cursor";
57
+ agentName: "OpenCode" | "Claude Code" | "Cursor";
58
58
  projectPath: string;
59
59
  modelId: string;
60
60
  providerId: string;
@@ -65,69 +65,69 @@ export interface DemoSessionSeed {
65
65
 
66
66
  const DEFAULT_SESSIONS: DemoSessionSeed[] = [
67
67
  {
68
- sessionId: 'demo-opencode-1',
69
- sessionName: 'Implement dashboard view with real-time updates',
70
- agentId: 'opencode',
71
- agentName: 'OpenCode',
72
- projectPath: '/Users/demo/workspace/tokentop',
73
- modelId: 'claude-3-5-sonnet',
74
- providerId: 'anthropic',
68
+ sessionId: "demo-opencode-1",
69
+ sessionName: "Implement dashboard view with real-time updates",
70
+ agentId: "opencode",
71
+ agentName: "OpenCode",
72
+ projectPath: "/Users/demo/workspace/tokentop",
73
+ modelId: "claude-3-5-sonnet",
74
+ providerId: "anthropic",
75
75
  baseTokens: 3200,
76
76
  baseCost: 1.24,
77
77
  },
78
78
  {
79
- sessionId: 'demo-opencode-2',
80
- sessionName: 'Fix Kubernetes deployment configuration',
81
- agentId: 'opencode',
82
- agentName: 'OpenCode',
83
- projectPath: '/Users/demo/workspace/infra',
84
- modelId: 'gpt-4.1',
85
- providerId: 'openai',
79
+ sessionId: "demo-opencode-2",
80
+ sessionName: "Fix Kubernetes deployment configuration",
81
+ agentId: "opencode",
82
+ agentName: "OpenCode",
83
+ projectPath: "/Users/demo/workspace/infra",
84
+ modelId: "gpt-4.1",
85
+ providerId: "openai",
86
86
  baseTokens: 2100,
87
87
  baseCost: 0.92,
88
88
  },
89
89
  {
90
- sessionId: 'demo-claude-1',
91
- sessionName: 'Add authentication flow to mobile app',
92
- agentId: 'claude-code',
93
- agentName: 'Claude Code',
94
- projectPath: '/Users/demo/workspace/mobile',
95
- modelId: 'claude-3-opus',
96
- providerId: 'anthropic',
90
+ sessionId: "demo-claude-1",
91
+ sessionName: "Add authentication flow to mobile app",
92
+ agentId: "claude-code",
93
+ agentName: "Claude Code",
94
+ projectPath: "/Users/demo/workspace/mobile",
95
+ modelId: "claude-3-opus",
96
+ providerId: "anthropic",
97
97
  baseTokens: 1800,
98
98
  baseCost: 0.78,
99
99
  },
100
100
  {
101
- sessionId: 'demo-cursor-1',
102
- sessionName: 'Refactor API endpoints for better performance',
103
- agentId: 'cursor',
104
- agentName: 'Cursor',
105
- projectPath: '/Users/demo/workspace/webapp',
106
- modelId: 'gemini-2.0-pro',
107
- providerId: 'google-gemini',
101
+ sessionId: "demo-cursor-1",
102
+ sessionName: "Refactor API endpoints for better performance",
103
+ agentId: "cursor",
104
+ agentName: "Cursor",
105
+ projectPath: "/Users/demo/workspace/webapp",
106
+ modelId: "gemini-2.0-pro",
107
+ providerId: "google-gemini",
108
108
  baseTokens: 2600,
109
109
  baseCost: 0.64,
110
110
  },
111
111
  {
112
- sessionId: 'demo-opencode-old-1',
113
- sessionName: 'Migrate legacy API to REST v2',
114
- agentId: 'opencode',
115
- agentName: 'OpenCode',
116
- projectPath: '/Users/demo/workspace/legacy-api',
117
- modelId: 'claude-3-5-sonnet',
118
- providerId: 'anthropic',
112
+ sessionId: "demo-opencode-old-1",
113
+ sessionName: "Migrate legacy API to REST v2",
114
+ agentId: "opencode",
115
+ agentName: "OpenCode",
116
+ projectPath: "/Users/demo/workspace/legacy-api",
117
+ modelId: "claude-3-5-sonnet",
118
+ providerId: "anthropic",
119
119
  baseTokens: 5400,
120
120
  baseCost: 2.18,
121
121
  inactive: true,
122
122
  },
123
123
  {
124
- sessionId: 'demo-claude-old-1',
125
- sessionName: 'Update documentation with new API examples',
126
- agentId: 'claude-code',
127
- agentName: 'Claude Code',
128
- projectPath: '/Users/demo/workspace/docs-site',
129
- modelId: 'gpt-4.1',
130
- providerId: 'openai',
124
+ sessionId: "demo-claude-old-1",
125
+ sessionName: "Update documentation with new API examples",
126
+ agentId: "claude-code",
127
+ agentName: "Claude Code",
128
+ projectPath: "/Users/demo/workspace/docs-site",
129
+ modelId: "gpt-4.1",
130
+ providerId: "openai",
131
131
  baseTokens: 3800,
132
132
  baseCost: 1.56,
133
133
  inactive: true,
@@ -135,22 +135,22 @@ const DEFAULT_SESSIONS: DemoSessionSeed[] = [
135
135
  ];
136
136
 
137
137
  const PROVIDER_LIMITS: Record<string, { label: string; windowMinutes: number }> = {
138
- anthropic: { label: 'Daily Tokens', windowMinutes: 1440 },
139
- openai: { label: 'Daily Tokens', windowMinutes: 1440 },
140
- 'google-gemini': { label: 'Daily Tokens', windowMinutes: 1440 },
138
+ anthropic: { label: "Daily Tokens", windowMinutes: 1440 },
139
+ openai: { label: "Daily Tokens", windowMinutes: 1440 },
140
+ "google-gemini": { label: "Daily Tokens", windowMinutes: 1440 },
141
141
  };
142
142
 
143
143
  const EXTRA_PROVIDERS: Array<{ id: string; label: string; balance?: string }> = [
144
- { id: 'codex', label: 'ChatGPT Plus', balance: '$18.00' },
145
- { id: 'github-copilot', label: 'Copilot Pro', balance: '$26.00' },
146
- { id: 'perplexity', label: 'Perplexity Pro', balance: '$12.50' },
147
- { id: 'antigravity', label: 'Antigravity AI', balance: '$31.00' },
148
- { id: 'minimax', label: 'MiniMax', balance: '$22.00' },
149
- { id: 'cohere', label: 'Cohere Enterprise', balance: '$45.00' },
150
- { id: 'mistral', label: 'Mistral API', balance: '$15.00' },
151
- { id: 'groq', label: 'Groq Cloud', balance: '$8.50' },
152
- { id: 'together', label: 'Together AI', balance: '$20.00' },
153
- { id: 'fireworks', label: 'Fireworks AI', balance: '$12.00' },
144
+ { id: "codex", label: "ChatGPT Plus", balance: "$18.00" },
145
+ { id: "github-copilot", label: "Copilot Pro", balance: "$26.00" },
146
+ { id: "perplexity", label: "Perplexity Pro", balance: "$12.50" },
147
+ { id: "antigravity", label: "Antigravity AI", balance: "$31.00" },
148
+ { id: "minimax", label: "MiniMax", balance: "$22.00" },
149
+ { id: "cohere", label: "Cohere Enterprise", balance: "$45.00" },
150
+ { id: "mistral", label: "Mistral API", balance: "$15.00" },
151
+ { id: "groq", label: "Groq Cloud", balance: "$8.50" },
152
+ { id: "together", label: "Together AI", balance: "$20.00" },
153
+ { id: "fireworks", label: "Fireworks AI", balance: "$12.00" },
154
154
  ];
155
155
 
156
156
  function clamp(value: number, min: number, max: number): number {
@@ -212,7 +212,7 @@ export class DemoSimulator {
212
212
 
213
213
  constructor(options: DemoSimulatorOptions = {}) {
214
214
  const seed = options.seed ?? 1337;
215
- this.preset = options.preset ?? 'normal';
215
+ this.preset = options.preset ?? "normal";
216
216
  this.presetConfig = DEMO_PRESETS[this.preset];
217
217
  this.rng = new DemoRng(seed);
218
218
  const now = Date.now();
@@ -221,14 +221,14 @@ export class DemoSimulator {
221
221
  const sessionCount = Math.min(this.presetConfig.sessionCount, DEFAULT_SESSIONS.length);
222
222
 
223
223
  // Select sessions: include a mix of inactive history and active sessions per preset size.
224
- const inactiveSessions = DEFAULT_SESSIONS.filter(s => s.inactive ?? false);
225
- const activeSessions = DEFAULT_SESSIONS.filter(s => !(s.inactive ?? false));
224
+ const inactiveSessions = DEFAULT_SESSIONS.filter((s) => s.inactive ?? false);
225
+ const activeSessions = DEFAULT_SESSIONS.filter((s) => !(s.inactive ?? false));
226
226
  const desiredInactive = Math.max(1, Math.round(sessionCount * 0.3));
227
227
  let inactiveCount = Math.min(inactiveSessions.length, desiredInactive);
228
228
  if (activeSessions.length > 0) {
229
229
  inactiveCount = Math.min(inactiveCount, sessionCount - 1);
230
230
  }
231
- let activeCount = Math.min(activeSessions.length, sessionCount - inactiveCount);
231
+ const activeCount = Math.min(activeSessions.length, sessionCount - inactiveCount);
232
232
  if (activeCount + inactiveCount < sessionCount) {
233
233
  inactiveCount = Math.min(inactiveSessions.length, sessionCount - activeCount);
234
234
  }
@@ -250,7 +250,7 @@ export class DemoSimulator {
250
250
  tokens: { input: inputTokens, output: outputTokens },
251
251
  requestCount: Math.max(1, Math.floor(tokens / 800)),
252
252
  costUsd: cost,
253
- pricingSource: 'fallback',
253
+ pricingSource: "fallback",
254
254
  },
255
255
  ];
256
256
 
@@ -263,23 +263,31 @@ export class DemoSimulator {
263
263
  : now - this.rng.range(10_000, 50_000);
264
264
 
265
265
  const nowDate = new Date(now);
266
- const startOfDay = new Date(nowDate.getFullYear(), nowDate.getMonth(), nowDate.getDate()).getTime();
266
+ const startOfDay = new Date(
267
+ nowDate.getFullYear(),
268
+ nowDate.getMonth(),
269
+ nowDate.getDate(),
270
+ ).getTime();
267
271
  const dayOfWeek = nowDate.getDay();
268
- const startOfWeek = new Date(nowDate.getFullYear(), nowDate.getMonth(), nowDate.getDate() - dayOfWeek).getTime();
272
+ const startOfWeek = new Date(
273
+ nowDate.getFullYear(),
274
+ nowDate.getMonth(),
275
+ nowDate.getDate() - dayOfWeek,
276
+ ).getTime();
269
277
  const startOfMonth = new Date(nowDate.getFullYear(), nowDate.getMonth(), 1).getTime();
270
278
 
271
279
  const costInDay = lastActivityAt >= startOfDay ? cost : 0;
272
280
  const costInWeek = lastActivityAt >= startOfWeek ? cost : 0;
273
281
  const costInMonth = lastActivityAt >= startOfMonth ? cost : 0;
274
282
 
275
- const baseSession: Omit<AgentSessionAggregate, 'endedAt'> = {
283
+ const baseSession: Omit<AgentSessionAggregate, "endedAt"> = {
276
284
  sessionId: seedSession.sessionId,
277
285
  agentId: seedSession.agentId,
278
286
  agentName: seedSession.agentName,
279
287
  projectPath: seedSession.projectPath,
280
288
  startedAt,
281
289
  lastActivityAt,
282
- status: isInactive ? 'idle' : (index % 3 === 0 ? 'idle' : 'active'),
290
+ status: isInactive ? "idle" : index % 3 === 0 ? "idle" : "active",
283
291
  totals: {
284
292
  input: inputTokens,
285
293
  output: outputTokens,
@@ -297,12 +305,10 @@ export class DemoSimulator {
297
305
  baseSession.sessionName = seedSession.sessionName;
298
306
  }
299
307
 
300
- return isInactive
301
- ? { ...baseSession, endedAt: lastActivityAt }
302
- : baseSession;
308
+ return isInactive ? { ...baseSession, endedAt: lastActivityAt } : baseSession;
303
309
  });
304
310
 
305
- const sessionProviderIds = new Set(selectedSessions.map(s => s.providerId));
311
+ const sessionProviderIds = new Set(selectedSessions.map((s) => s.providerId));
306
312
  this.fixedProviderIds = Array.from(sessionProviderIds);
307
313
 
308
314
  const extraCount = Math.min(this.presetConfig.extraProviderCount, EXTRA_PROVIDERS.length);
@@ -317,7 +323,11 @@ export class DemoSimulator {
317
323
  const providerTotals = new Map<string, { tokens: number; cost: number; requests: number }>();
318
324
  for (const session of this.sessions) {
319
325
  for (const stream of session.streams) {
320
- const current = providerTotals.get(stream.providerId) ?? { tokens: 0, cost: 0, requests: 0 };
326
+ const current = providerTotals.get(stream.providerId) ?? {
327
+ tokens: 0,
328
+ cost: 0,
329
+ requests: 0,
330
+ };
321
331
  const streamTokens = stream.tokens.input + stream.tokens.output;
322
332
  providerTotals.set(stream.providerId, {
323
333
  tokens: current.tokens + streamTokens,
@@ -329,12 +339,12 @@ export class DemoSimulator {
329
339
 
330
340
  for (const providerId of this.fixedProviderIds) {
331
341
  const totals = providerTotals.get(providerId) ?? { tokens: 0, cost: 0, requests: 0 };
332
- const limit = PROVIDER_LIMITS[providerId] ?? { label: 'Daily Tokens', windowMinutes: 1440 };
342
+ const limit = PROVIDER_LIMITS[providerId] ?? { label: "Daily Tokens", windowMinutes: 1440 };
333
343
  const usedPercent = clamp((totals.tokens / 50_000) * 100, 5, 98);
334
344
  const limitReached = usedPercent > 95;
335
345
 
336
346
  this.providerUsage.set(providerId, {
337
- planType: 'Pro',
347
+ planType: "Pro",
338
348
  limitReached,
339
349
  limits: {
340
350
  primary: {
@@ -355,14 +365,14 @@ export class DemoSimulator {
355
365
  total: totals.cost,
356
366
  input: totals.cost * 0.55,
357
367
  output: totals.cost * 0.45,
358
- currency: 'USD',
368
+ currency: "USD",
359
369
  },
360
- source: 'estimated',
370
+ source: "estimated",
361
371
  },
362
372
  credits: {
363
373
  hasCredits: true,
364
374
  unlimited: false,
365
- balance: '$42.50',
375
+ balance: "$42.50",
366
376
  },
367
377
  fetchedAt: now,
368
378
  });
@@ -377,7 +387,7 @@ export class DemoSimulator {
377
387
  limits: {
378
388
  primary: {
379
389
  usedPercent,
380
- label: 'Monthly Tokens',
390
+ label: "Monthly Tokens",
381
391
  windowMinutes: 43200,
382
392
  resetsAt: now + 10 * 24 * 60 * 60 * 1000,
383
393
  },
@@ -391,14 +401,14 @@ export class DemoSimulator {
391
401
  total: usedPercent * 0.08,
392
402
  input: usedPercent * 0.04,
393
403
  output: usedPercent * 0.04,
394
- currency: 'USD',
404
+ currency: "USD",
395
405
  },
396
- source: 'estimated',
406
+ source: "estimated",
397
407
  },
398
408
  credits: {
399
409
  hasCredits: true,
400
410
  unlimited: false,
401
- balance: provider.balance ?? '$25.00',
411
+ balance: provider.balance ?? "$25.00",
402
412
  },
403
413
  fetchedAt: now,
404
414
  });
@@ -409,7 +419,11 @@ export class DemoSimulator {
409
419
  const providerTotals = new Map<string, { tokens: number; cost: number; requests: number }>();
410
420
  for (const session of this.sessions) {
411
421
  for (const stream of session.streams) {
412
- const current = providerTotals.get(stream.providerId) ?? { tokens: 0, cost: 0, requests: 0 };
422
+ const current = providerTotals.get(stream.providerId) ?? {
423
+ tokens: 0,
424
+ cost: 0,
425
+ requests: 0,
426
+ };
413
427
  const streamTokens = stream.tokens.input + stream.tokens.output;
414
428
  providerTotals.set(stream.providerId, {
415
429
  tokens: current.tokens + streamTokens,
@@ -421,13 +435,13 @@ export class DemoSimulator {
421
435
 
422
436
  for (const providerId of this.fixedProviderIds) {
423
437
  const totals = providerTotals.get(providerId) ?? { tokens: 0, cost: 0, requests: 0 };
424
- const limit = PROVIDER_LIMITS[providerId] ?? { label: 'Daily Tokens', windowMinutes: 1440 };
438
+ const limit = PROVIDER_LIMITS[providerId] ?? { label: "Daily Tokens", windowMinutes: 1440 };
425
439
  const usedPercent = clamp((totals.tokens / 50_000) * 100, 5, 98);
426
440
  const limitReached = usedPercent > 95;
427
441
 
428
442
  const existing = this.providerUsage.get(providerId);
429
443
  this.providerUsage.set(providerId, {
430
- planType: existing?.planType ?? 'Pro',
444
+ planType: existing?.planType ?? "Pro",
431
445
  limitReached,
432
446
  limits: {
433
447
  primary: {
@@ -448,14 +462,14 @@ export class DemoSimulator {
448
462
  total: totals.cost,
449
463
  input: totals.cost * 0.55,
450
464
  output: totals.cost * 0.45,
451
- currency: 'USD',
465
+ currency: "USD",
452
466
  },
453
- source: 'estimated',
467
+ source: "estimated",
454
468
  },
455
469
  credits: existing?.credits ?? {
456
470
  hasCredits: true,
457
471
  unlimited: false,
458
- balance: '$42.50',
472
+ balance: "$42.50",
459
473
  },
460
474
  fetchedAt: now,
461
475
  });
@@ -487,9 +501,9 @@ export class DemoSimulator {
487
501
  total: newPercent * 0.08,
488
502
  input: newPercent * 0.04,
489
503
  output: newPercent * 0.04,
490
- currency: 'USD',
504
+ currency: "USD",
491
505
  },
492
- source: 'estimated',
506
+ source: "estimated",
493
507
  },
494
508
  fetchedAt: now,
495
509
  });
@@ -514,16 +528,17 @@ export class DemoSimulator {
514
528
  const sessionRng = tickRng.fork(sessionIndex);
515
529
  const isIdle = sessionRng.next() < this.presetConfig.idleProbability;
516
530
  if (isIdle) {
517
- return { ...session, status: 'idle' as const };
531
+ return { ...session, status: "idle" as const };
518
532
  }
519
533
 
520
534
  const isBurst = sessionRng.next() < this.presetConfig.burstProbability;
521
535
  const burstFactor = isBurst ? this.presetConfig.burstMultiplier : 1;
522
536
  const activityFactor = sessionRng.range(0.4, 1.6) * burstFactor;
523
- const tokensPerSec = session.agentId === 'opencode' ? 35 : session.agentId === 'claude-code' ? 28 : 22;
537
+ const tokensPerSec =
538
+ session.agentId === "opencode" ? 35 : session.agentId === "claude-code" ? 28 : 22;
524
539
  const deltaTokens = Math.floor(tokensPerSec * activityFactor * activityMultiplier * dtSec);
525
540
  if (deltaTokens === 0) {
526
- return { ...session, status: 'idle' as const };
541
+ return { ...session, status: "idle" as const };
527
542
  }
528
543
  const inputDelta = Math.floor(deltaTokens * 0.6);
529
544
  const outputDelta = deltaTokens - inputDelta;
@@ -542,9 +557,9 @@ export class DemoSimulator {
542
557
 
543
558
  usageEvents.push({
544
559
  timestamp: now,
545
- source: 'agent',
546
- provider: updatedStreams[0]?.providerId ?? 'anthropic',
547
- model: updatedStreams[0]?.modelId ?? 'claude-3-5-sonnet',
560
+ source: "agent",
561
+ provider: updatedStreams[0]?.providerId ?? "anthropic",
562
+ model: updatedStreams[0]?.modelId ?? "claude-3-5-sonnet",
548
563
  agentId: session.agentId,
549
564
  sessionId: session.sessionId,
550
565
  projectPath: session.projectPath ?? null,
@@ -554,12 +569,12 @@ export class DemoSimulator {
554
569
  cacheWriteTokens: Math.floor(deltaTokens * 0.02),
555
570
  costUsd: costDelta,
556
571
  requestCount: Math.max(1, Math.floor(deltaTokens / 700)),
557
- pricingSource: 'fallback',
572
+ pricingSource: "fallback",
558
573
  });
559
574
 
560
575
  return {
561
576
  ...session,
562
- status: 'active' as const,
577
+ status: "active" as const,
563
578
  lastActivityAt: now,
564
579
  totals: {
565
580
  ...session.totals,
@@ -593,17 +608,25 @@ export class DemoSimulator {
593
608
  }
594
609
 
595
610
  getProviderIds(): string[] {
596
- return [...this.fixedProviderIds, ...this.fixedExtraProviders.map(p => p.id)];
611
+ return [...this.fixedProviderIds, ...this.fixedExtraProviders.map((p) => p.id)];
597
612
  }
598
613
 
599
- generateHistoricalCostDataByProvider(daysBack: number): Array<{ date: number; provider: string; cost: number; tokens: number; requests: number }> {
614
+ generateHistoricalCostDataByProvider(
615
+ daysBack: number,
616
+ ): Array<{ date: number; provider: string; cost: number; tokens: number; requests: number }> {
600
617
  const totalDaily = this.generateHistoricalCostData(daysBack);
601
- const result: Array<{ date: number; provider: string; cost: number; tokens: number; requests: number }> = [];
618
+ const result: Array<{
619
+ date: number;
620
+ provider: string;
621
+ cost: number;
622
+ tokens: number;
623
+ requests: number;
624
+ }> = [];
602
625
 
603
626
  const providerShares = [
604
- { id: 'anthropic', share: 0.50 },
605
- { id: 'openai', share: 0.25 },
606
- { id: 'google-gemini', share: 0.15 },
627
+ { id: "anthropic", share: 0.5 },
628
+ { id: "openai", share: 0.25 },
629
+ { id: "google-gemini", share: 0.15 },
607
630
  ];
608
631
 
609
632
  const providerRng = this.rng.fork(88888);
@@ -636,15 +659,23 @@ export class DemoSimulator {
636
659
  return result;
637
660
  }
638
661
 
639
- generateHistoricalCostDataByModel(daysBack: number): Array<{ date: number; model: string; cost: number; tokens: number; requests: number }> {
662
+ generateHistoricalCostDataByModel(
663
+ daysBack: number,
664
+ ): Array<{ date: number; model: string; cost: number; tokens: number; requests: number }> {
640
665
  const totalDaily = this.generateHistoricalCostData(daysBack);
641
- const result: Array<{ date: number; model: string; cost: number; tokens: number; requests: number }> = [];
666
+ const result: Array<{
667
+ date: number;
668
+ model: string;
669
+ cost: number;
670
+ tokens: number;
671
+ requests: number;
672
+ }> = [];
642
673
 
643
674
  const modelShares = [
644
- { id: 'claude-3-5-sonnet', share: 0.40 },
645
- { id: 'claude-3-opus', share: 0.20 },
646
- { id: 'gpt-4.1', share: 0.25 },
647
- { id: 'gemini-2.0-pro', share: 0.15 },
675
+ { id: "claude-3-5-sonnet", share: 0.4 },
676
+ { id: "claude-3-opus", share: 0.2 },
677
+ { id: "gpt-4.1", share: 0.25 },
678
+ { id: "gemini-2.0-pro", share: 0.15 },
648
679
  ];
649
680
 
650
681
  const modelRng = this.rng.fork(77777);
@@ -677,14 +708,22 @@ export class DemoSimulator {
677
708
  return result;
678
709
  }
679
710
 
680
- generateHistoricalCostDataByProject(daysBack: number): Array<{ date: number; projectPath: string; cost: number; tokens: number; requests: number }> {
711
+ generateHistoricalCostDataByProject(
712
+ daysBack: number,
713
+ ): Array<{ date: number; projectPath: string; cost: number; tokens: number; requests: number }> {
681
714
  const totalDaily = this.generateHistoricalCostData(daysBack);
682
- const result: Array<{ date: number; projectPath: string; cost: number; tokens: number; requests: number }> = [];
715
+ const result: Array<{
716
+ date: number;
717
+ projectPath: string;
718
+ cost: number;
719
+ tokens: number;
720
+ requests: number;
721
+ }> = [];
683
722
 
684
723
  const projectShares = [
685
- { path: '/Users/demo/workspace/tokentop', share: 0.45 },
686
- { path: '/Users/demo/workspace/webapp', share: 0.30 },
687
- { path: '/Users/demo/workspace/infra', share: 0.25 },
724
+ { path: "/Users/demo/workspace/tokentop", share: 0.45 },
725
+ { path: "/Users/demo/workspace/webapp", share: 0.3 },
726
+ { path: "/Users/demo/workspace/infra", share: 0.25 },
688
727
  ];
689
728
 
690
729
  const projectRng = this.rng.fork(66666);
@@ -724,19 +763,19 @@ export class DemoSimulator {
724
763
  const result: Array<{ date: number; cost: number }> = [];
725
764
 
726
765
  const baseCost = this.presetConfig.activityMultiplier * 2.5;
727
-
766
+
728
767
  for (let i = daysBack - 1; i >= 0; i--) {
729
768
  const dayTimestamp = now - i * msPerDay;
730
769
  const dayRng = historyRng.fork(i);
731
-
770
+
732
771
  const isWeekend = new Date(dayTimestamp).getDay() % 6 === 0;
733
772
  const weekendFactor = isWeekend ? 0.4 : 1.0;
734
-
773
+
735
774
  const variance = dayRng.range(0.5, 1.5);
736
775
  const spikeFactor = dayRng.next() < 0.1 ? dayRng.range(1.5, 2.5) : 1.0;
737
-
776
+
738
777
  const cost = baseCost * variance * weekendFactor * spikeFactor;
739
-
778
+
740
779
  result.push({ date: dayTimestamp, cost: Math.round(cost * 100) / 100 });
741
780
  }
742
781