@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,45 +1,45 @@
1
- import type { ThemePlugin } from '../types/theme.ts';
1
+ import type { ThemePlugin } from "../types/theme.ts";
2
2
 
3
3
  export const tokyoNightTheme: ThemePlugin = {
4
4
  apiVersion: 2,
5
- id: 'tokyo-night',
6
- type: 'theme',
7
- name: 'Tokyo Night',
8
- family: 'tokyo-night',
9
- version: '1.0.0',
5
+ id: "tokyo-night",
6
+ type: "theme",
7
+ name: "Tokyo Night",
8
+ family: "tokyo-night",
9
+ version: "1.0.0",
10
10
  permissions: {},
11
11
 
12
- colorScheme: 'dark',
12
+ colorScheme: "dark",
13
13
 
14
14
  colors: {
15
- background: '#1a1b26',
16
- foreground: '#24283b',
17
- text: '#c0caf5',
18
- textMuted: '#737aa2',
19
- textSubtle: '#565f89',
20
- primary: '#7aa2f7',
21
- secondary: '#bb9af7',
22
- accent: '#7dcfff',
23
- success: '#9ece6a',
24
- warning: '#e0af68',
25
- error: '#f7768e',
26
- info: '#7aa2f7',
27
- border: '#414868',
28
- borderMuted: '#292e42',
29
- selection: '#33467c',
30
- highlight: '#2f3549',
31
- gaugeBackground: '#24283b',
32
- gaugeFill: '#7aa2f7',
33
- gaugeWarning: '#e0af68',
34
- gaugeDanger: '#f7768e',
15
+ background: "#1a1b26",
16
+ foreground: "#24283b",
17
+ text: "#c0caf5",
18
+ textMuted: "#737aa2",
19
+ textSubtle: "#565f89",
20
+ primary: "#7aa2f7",
21
+ secondary: "#bb9af7",
22
+ accent: "#7dcfff",
23
+ success: "#9ece6a",
24
+ warning: "#e0af68",
25
+ error: "#f7768e",
26
+ info: "#7aa2f7",
27
+ border: "#414868",
28
+ borderMuted: "#292e42",
29
+ selection: "#33467c",
30
+ highlight: "#2f3549",
31
+ gaugeBackground: "#24283b",
32
+ gaugeFill: "#7aa2f7",
33
+ gaugeWarning: "#e0af68",
34
+ gaugeDanger: "#f7768e",
35
35
  },
36
36
 
37
37
  components: {
38
38
  header: {
39
- background: '#16161e',
39
+ background: "#16161e",
40
40
  },
41
41
  statusBar: {
42
- background: '#1f2335',
42
+ background: "#1f2335",
43
43
  },
44
44
  gauge: {
45
45
  height: 1,
@@ -1,6 +1,6 @@
1
- import { z } from 'zod';
2
- import type { BasePlugin, PluginHttpClient, PluginLogger } from './base.ts';
3
- import type { Credentials, OAuthCredentials, PluginContext } from './provider.ts';
1
+ import { z } from "zod";
2
+ import type { BasePlugin, PluginHttpClient, PluginLogger } from "./base.ts";
3
+ import type { Credentials, OAuthCredentials, PluginContext } from "./provider.ts";
4
4
 
5
5
  export const AgentCapabilitiesSchema = z.object({
6
6
  sessionParsing: z.boolean(),
@@ -38,7 +38,7 @@ export interface AgentProviderConfig {
38
38
 
39
39
  export interface SessionParseOptions {
40
40
  sessionId?: string;
41
- timePeriod?: 'session' | 'daily' | 'weekly' | 'monthly';
41
+ timePeriod?: "session" | "daily" | "weekly" | "monthly";
42
42
  limit?: number;
43
43
  /** Epoch ms — only return sessions updated after this timestamp. */
44
44
  since?: number;
@@ -84,7 +84,7 @@ export interface ActivityUpdate {
84
84
  export type ActivityCallback = (update: ActivityUpdate) => void;
85
85
 
86
86
  export interface AgentPlugin extends BasePlugin {
87
- readonly type: 'agent';
87
+ readonly type: "agent";
88
88
  readonly agent: AgentConfig;
89
89
  readonly capabilities: AgentCapabilities;
90
90
 
@@ -5,7 +5,7 @@
5
5
  * authoritative contract for community developers; core adapts to match.
6
6
  */
7
7
 
8
- import { z } from 'zod';
8
+ import { z } from "zod";
9
9
 
10
10
  // ---------------------------------------------------------------------------
11
11
  // Identity
@@ -14,7 +14,7 @@ import { z } from 'zod';
14
14
  /**
15
15
  * Plugin type discriminator
16
16
  */
17
- export type PluginType = 'provider' | 'agent' | 'theme' | 'notification';
17
+ export type PluginType = "provider" | "agent" | "theme" | "notification";
18
18
 
19
19
  /**
20
20
  * Current API contract version.
@@ -30,23 +30,31 @@ export const CURRENT_API_VERSION = 2;
30
30
  * Plugin permissions schema - defines what a plugin can access
31
31
  */
32
32
  export const PluginPermissionsSchema = z.object({
33
- network: z.object({
34
- enabled: z.boolean(),
35
- allowedDomains: z.array(z.string()).optional(),
36
- }).optional(),
37
- filesystem: z.object({
38
- read: z.boolean().optional(),
39
- write: z.boolean().optional(),
40
- paths: z.array(z.string()).optional(),
41
- }).optional(),
42
- env: z.object({
43
- read: z.boolean().optional(),
44
- vars: z.array(z.string()).optional(),
45
- }).optional(),
46
- system: z.object({
47
- notifications: z.boolean().optional(),
48
- clipboard: z.boolean().optional(),
49
- }).optional(),
33
+ network: z
34
+ .object({
35
+ enabled: z.boolean(),
36
+ allowedDomains: z.array(z.string()).optional(),
37
+ })
38
+ .optional(),
39
+ filesystem: z
40
+ .object({
41
+ read: z.boolean().optional(),
42
+ write: z.boolean().optional(),
43
+ paths: z.array(z.string()).optional(),
44
+ })
45
+ .optional(),
46
+ env: z
47
+ .object({
48
+ read: z.boolean().optional(),
49
+ vars: z.array(z.string()).optional(),
50
+ })
51
+ .optional(),
52
+ system: z
53
+ .object({
54
+ notifications: z.boolean().optional(),
55
+ clipboard: z.boolean().optional(),
56
+ })
57
+ .optional(),
50
58
  });
51
59
 
52
60
  export type PluginPermissions = z.infer<typeof PluginPermissionsSchema>;
@@ -95,7 +103,7 @@ export type PluginMeta = z.infer<typeof PluginMetaSchema>;
95
103
  */
96
104
  export interface ConfigField {
97
105
  /** Data type of the setting value. */
98
- type: 'string' | 'number' | 'boolean' | 'select';
106
+ type: "string" | "number" | "boolean" | "select";
99
107
  /** Label shown in the settings UI. */
100
108
  label?: string;
101
109
  /** Help text shown below the field. */
@@ -197,7 +205,10 @@ export interface BasePlugin {
197
205
  * Called when the user changes this plugin's configuration.
198
206
  * Receive the new validated config values.
199
207
  */
200
- onConfigChange?(config: Record<string, unknown>, ctx: PluginLifecycleContext): Promise<void> | void;
208
+ onConfigChange?(
209
+ config: Record<string, unknown>,
210
+ ctx: PluginLifecycleContext,
211
+ ): Promise<void> | void;
201
212
  }
202
213
 
203
214
  // ---------------------------------------------------------------------------
@@ -245,7 +256,7 @@ export interface PluginLoadResult<T extends BasePlugin = BasePlugin> {
245
256
  success: boolean;
246
257
  plugin?: T;
247
258
  error?: string;
248
- source: 'builtin' | 'local' | 'npm';
259
+ source: "builtin" | "local" | "npm";
249
260
  }
250
261
 
251
262
  // ---------------------------------------------------------------------------
@@ -259,9 +270,9 @@ export class PluginPermissionError extends Error {
259
270
  constructor(
260
271
  public readonly pluginId: string,
261
272
  public readonly permission: keyof PluginPermissions,
262
- message: string
273
+ message: string,
263
274
  ) {
264
275
  super(`Plugin "${pluginId}" permission denied: ${message}`);
265
- this.name = 'PluginPermissionError';
276
+ this.name = "PluginPermissionError";
266
277
  }
267
278
  }
@@ -1,13 +1,13 @@
1
- export * from './base.ts';
2
- export * from './provider.ts';
3
- export * from './agent.ts';
4
- export * from './theme.ts';
5
- export * from './notification.ts';
1
+ export * from "./agent.ts";
2
+ export * from "./base.ts";
3
+ export * from "./notification.ts";
4
+ export * from "./provider.ts";
5
+ export * from "./theme.ts";
6
6
 
7
- import type { ProviderPlugin } from './provider.ts';
8
- import type { AgentPlugin } from './agent.ts';
9
- import type { ThemePlugin } from './theme.ts';
10
- import type { NotificationPlugin } from './notification.ts';
7
+ import type { AgentPlugin } from "./agent.ts";
8
+ import type { NotificationPlugin } from "./notification.ts";
9
+ import type { ProviderPlugin } from "./provider.ts";
10
+ import type { ThemePlugin } from "./theme.ts";
11
11
 
12
12
  export type AnyPlugin = ProviderPlugin | AgentPlugin | ThemePlugin | NotificationPlugin;
13
13
 
@@ -1,21 +1,21 @@
1
- import type { BasePlugin, PluginLogger } from './base.ts';
1
+ import type { BasePlugin, PluginLogger } from "./base.ts";
2
2
 
3
3
  // ---------------------------------------------------------------------------
4
4
  // Notification Events
5
5
  // ---------------------------------------------------------------------------
6
6
 
7
- export type NotificationSeverity = 'info' | 'warning' | 'critical';
7
+ export type NotificationSeverity = "info" | "warning" | "critical";
8
8
 
9
9
  export type NotificationEventType =
10
- | 'budget.thresholdCrossed'
11
- | 'budget.limitReached'
12
- | 'provider.fetchFailed'
13
- | 'provider.limitReached'
14
- | 'provider.recovered'
15
- | 'plugin.crashed'
16
- | 'plugin.disabled'
17
- | 'app.started'
18
- | 'app.updated';
10
+ | "budget.thresholdCrossed"
11
+ | "budget.limitReached"
12
+ | "provider.fetchFailed"
13
+ | "provider.limitReached"
14
+ | "provider.recovered"
15
+ | "plugin.crashed"
16
+ | "plugin.disabled"
17
+ | "app.started"
18
+ | "app.updated";
19
19
 
20
20
  export interface NotificationEvent {
21
21
  type: NotificationEventType;
@@ -41,7 +41,7 @@ export interface NotificationContext {
41
41
  // ---------------------------------------------------------------------------
42
42
 
43
43
  export interface NotificationPlugin extends BasePlugin {
44
- readonly type: 'notification';
44
+ readonly type: "notification";
45
45
 
46
46
  initialize(ctx: NotificationContext): Promise<void>;
47
47
  notify(ctx: NotificationContext, event: NotificationEvent): Promise<void>;
@@ -1,4 +1,4 @@
1
- import type { BasePlugin, PluginHttpClient, PluginLogger } from './base.ts';
1
+ import type { BasePlugin, PluginHttpClient, PluginLogger } from "./base.ts";
2
2
 
3
3
  export interface ProviderCapabilities {
4
4
  usageLimits: boolean;
@@ -15,7 +15,7 @@ export interface Credentials {
15
15
  apiKey?: string;
16
16
  oauth?: OAuthCredentials;
17
17
  groupId?: string;
18
- source: 'env' | 'opencode' | 'external' | 'config';
18
+ source: "env" | "opencode" | "external" | "config";
19
19
  }
20
20
 
21
21
  export interface OAuthCredentials {
@@ -40,7 +40,7 @@ export interface RefreshedCredentials {
40
40
  export interface CredentialResult {
41
41
  ok: boolean;
42
42
  credentials?: Credentials;
43
- reason?: 'missing' | 'expired' | 'invalid' | 'error';
43
+ reason?: "missing" | "expired" | "invalid" | "error";
44
44
  message?: string;
45
45
  }
46
46
 
@@ -49,7 +49,7 @@ export interface CredentialResult {
49
49
  * Used by AuthSources.opencode.getProviderEntry().
50
50
  */
51
51
  export interface OpenCodeAuthEntry {
52
- type: 'api' | 'oauth' | 'codex' | 'github' | 'wellknown';
52
+ type: "api" | "oauth" | "codex" | "github" | "wellknown";
53
53
  key?: string;
54
54
  access?: string;
55
55
  refresh?: string;
@@ -89,7 +89,7 @@ export interface AuthSources {
89
89
 
90
90
  /** Platform information for cross-platform credential path resolution. */
91
91
  platform: {
92
- os: 'darwin' | 'linux' | 'win32';
92
+ os: "darwin" | "linux" | "win32";
93
93
  homedir: string;
94
94
  arch: string;
95
95
  };
@@ -197,7 +197,11 @@ export interface ProviderUsageData {
197
197
  cost?: {
198
198
  actual?: CostBreakdown;
199
199
  estimated?: CostBreakdown;
200
- source: 'api' | 'estimated';
200
+ /** Session-derived daily cost (today only). Set by enrichment hook. */
201
+ estimatedDaily?: CostBreakdown;
202
+ /** Session-derived monthly cost (MTD). Set by enrichment hook. */
203
+ estimatedMonthly?: CostBreakdown;
204
+ source: "api" | "estimated";
201
205
  };
202
206
  fetchedAt: number;
203
207
  error?: string;
@@ -214,7 +218,7 @@ export interface ProviderFetchContext {
214
218
  readonly config: Record<string, unknown>;
215
219
  readonly signal: AbortSignal;
216
220
  readonly options?: {
217
- timePeriod?: 'session' | 'daily' | 'weekly' | 'monthly';
221
+ timePeriod?: "session" | "daily" | "weekly" | "monthly";
218
222
  sessionId?: string;
219
223
  };
220
224
  }
@@ -224,7 +228,7 @@ export interface ProviderFetchContext {
224
228
  // ---------------------------------------------------------------------------
225
229
 
226
230
  export interface ProviderPlugin extends BasePlugin {
227
- readonly type: 'provider';
231
+ readonly type: "provider";
228
232
  readonly capabilities: ProviderCapabilities;
229
233
  readonly auth: ProviderAuth;
230
234
  readonly pricing?: ProviderPricing;
@@ -1,5 +1,5 @@
1
- import { z } from 'zod';
2
- import type { BasePlugin } from './base.ts';
1
+ import { z } from "zod";
2
+ import type { BasePlugin } from "./base.ts";
3
3
 
4
4
  export const ThemeColorsSchema = z.object({
5
5
  background: z.string(),
@@ -27,34 +27,42 @@ export const ThemeColorsSchema = z.object({
27
27
  export type ThemeColors = z.infer<typeof ThemeColorsSchema>;
28
28
 
29
29
  export const ThemeComponentsSchema = z.object({
30
- header: z.object({
31
- background: z.string().optional(),
32
- foreground: z.string().optional(),
33
- titleColor: z.string().optional(),
34
- titleAccentColor: z.string().optional(),
35
- }).optional(),
36
- statusBar: z.object({
37
- background: z.string().optional(),
38
- foreground: z.string().optional(),
39
- }).optional(),
40
- commandPalette: z.object({
41
- background: z.string().optional(),
42
- border: z.string().optional(),
43
- }).optional(),
44
- gauge: z.object({
45
- height: z.number().optional(),
46
- borderRadius: z.number().optional(),
47
- }).optional(),
30
+ header: z
31
+ .object({
32
+ background: z.string().optional(),
33
+ foreground: z.string().optional(),
34
+ titleColor: z.string().optional(),
35
+ titleAccentColor: z.string().optional(),
36
+ })
37
+ .optional(),
38
+ statusBar: z
39
+ .object({
40
+ background: z.string().optional(),
41
+ foreground: z.string().optional(),
42
+ })
43
+ .optional(),
44
+ commandPalette: z
45
+ .object({
46
+ background: z.string().optional(),
47
+ border: z.string().optional(),
48
+ })
49
+ .optional(),
50
+ gauge: z
51
+ .object({
52
+ height: z.number().optional(),
53
+ borderRadius: z.number().optional(),
54
+ })
55
+ .optional(),
48
56
  });
49
57
 
50
58
  export type ThemeComponents = z.infer<typeof ThemeComponentsSchema>;
51
59
 
52
- export type ColorScheme = 'light' | 'dark';
60
+ export type ColorScheme = "light" | "dark";
53
61
 
54
- export type ColorSchemePreference = 'auto' | 'light' | 'dark';
62
+ export type ColorSchemePreference = "auto" | "light" | "dark";
55
63
 
56
64
  export interface ThemePlugin extends BasePlugin {
57
- readonly type: 'theme';
65
+ readonly type: "theme";
58
66
  readonly family: string;
59
67
  readonly colorScheme: ColorScheme;
60
68
  readonly colors: ThemeColors;
@@ -9,7 +9,7 @@
9
9
  * skipped because they have no registry entry to compare against.
10
10
  */
11
11
 
12
- import type { PluginType } from './types/base.ts';
12
+ import type { PluginType } from "./types/base.ts";
13
13
 
14
14
  // ---------------------------------------------------------------------------
15
15
  // Types
@@ -33,7 +33,7 @@ export interface PluginUpdateInfo {
33
33
  // ---------------------------------------------------------------------------
34
34
 
35
35
  function parseVersion(v: string): [number, number, number] | null {
36
- const clean = v.replace(/^v/, '');
36
+ const clean = v.replace(/^v/, "");
37
37
  const match = clean.match(/^(\d+)\.(\d+)\.(\d+)/);
38
38
  if (!match) return null;
39
39
  return [Number(match[1]), Number(match[2]), Number(match[3])];
@@ -60,9 +60,9 @@ function isNewer(current: string, latest: string): boolean {
60
60
  * Uses the abbreviated metadata endpoint for minimal payload.
61
61
  */
62
62
  async function fetchLatestVersion(packageName: string): Promise<string> {
63
- const url = `https://registry.npmjs.org/${encodeURIComponent(packageName).replace('%40', '@')}`;
63
+ const url = `https://registry.npmjs.org/${encodeURIComponent(packageName).replace("%40", "@")}`;
64
64
  const res = await fetch(url, {
65
- headers: { Accept: 'application/vnd.npm.install-v1+json' },
65
+ headers: { Accept: "application/vnd.npm.install-v1+json" },
66
66
  signal: AbortSignal.timeout(8_000),
67
67
  });
68
68
 
@@ -70,8 +70,8 @@ async function fetchLatestVersion(packageName: string): Promise<string> {
70
70
  throw new Error(`npm registry returned ${res.status} for ${packageName}`);
71
71
  }
72
72
 
73
- const data = (await res.json()) as { 'dist-tags'?: { latest?: string } };
74
- const latest = data?.['dist-tags']?.latest;
73
+ const data = (await res.json()) as { "dist-tags"?: { latest?: string } };
74
+ const latest = data?.["dist-tags"]?.latest;
75
75
  if (!latest) {
76
76
  throw new Error(`No dist-tags.latest found for ${packageName}`);
77
77
  }
@@ -1,14 +1,9 @@
1
- import { describe, expect, test } from 'bun:test';
2
- import type { ModelPricing } from '@/plugins/types/provider.ts';
3
- import {
4
- estimateCost,
5
- estimateSessionCost,
6
- formatCost,
7
- formatTokenCount,
8
- } from './estimator.ts';
9
-
10
- describe('estimateCost()', () => {
11
- test('calculates input and output costs for a typical coding session', () => {
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { ModelPricing } from "@/plugins/types/provider.ts";
3
+ import { estimateCost, estimateSessionCost, formatCost, formatTokenCount } from "./estimator.ts";
4
+
5
+ describe("estimateCost()", () => {
6
+ test("calculates input and output costs for a typical coding session", () => {
12
7
  const usage = { input: 150_000, output: 50_000 };
13
8
  const pricing: ModelPricing = { input: 3, output: 15 };
14
9
 
@@ -16,11 +11,11 @@ describe('estimateCost()', () => {
16
11
  total: 1.2,
17
12
  input: 0.45,
18
13
  output: 0.75,
19
- currency: 'USD',
14
+ currency: "USD",
20
15
  });
21
16
  });
22
17
 
23
- test('includes cache read and write costs when both usage and pricing are present', () => {
18
+ test("includes cache read and write costs when both usage and pricing are present", () => {
24
19
  const usage = { input: 100_000, output: 25_000, cacheRead: 80_000, cacheWrite: 40_000 };
25
20
  const pricing: ModelPricing = {
26
21
  input: 3,
@@ -35,11 +30,11 @@ describe('estimateCost()', () => {
35
30
  output: 0.375,
36
31
  cacheRead: 0.024,
37
32
  cacheWrite: 0.15,
38
- currency: 'USD',
33
+ currency: "USD",
39
34
  });
40
35
  });
41
36
 
42
- test('does not include cache fields when cache pricing is unavailable', () => {
37
+ test("does not include cache fields when cache pricing is unavailable", () => {
43
38
  const usage = { input: 10_000, output: 20_000, cacheRead: 5_000, cacheWrite: 7_000 };
44
39
  const pricing: ModelPricing = { input: 2, output: 8 };
45
40
 
@@ -47,11 +42,11 @@ describe('estimateCost()', () => {
47
42
  total: 0.18,
48
43
  input: 0.02,
49
44
  output: 0.16,
50
- currency: 'USD',
45
+ currency: "USD",
51
46
  });
52
47
  });
53
48
 
54
- test('returns zero cost for zero token usage', () => {
49
+ test("returns zero cost for zero token usage", () => {
55
50
  const usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
56
51
  const pricing: ModelPricing = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
57
52
 
@@ -59,11 +54,11 @@ describe('estimateCost()', () => {
59
54
  total: 0,
60
55
  input: 0,
61
56
  output: 0,
62
- currency: 'USD',
57
+ currency: "USD",
63
58
  });
64
59
  });
65
60
 
66
- test('handles very large token counts correctly', () => {
61
+ test("handles very large token counts correctly", () => {
67
62
  const usage = {
68
63
  input: 123_456_789,
69
64
  output: 987_654_321,
@@ -83,11 +78,11 @@ describe('estimateCost()', () => {
83
78
  output: 9876.54321,
84
79
  cacheRead: 5.555556,
85
80
  cacheWrite: 44.444444,
86
- currency: 'USD',
81
+ currency: "USD",
87
82
  });
88
83
  });
89
84
 
90
- test('rounds costs to six decimal places', () => {
85
+ test("rounds costs to six decimal places", () => {
91
86
  const usage = { input: 1, output: 1 };
92
87
  const pricing: ModelPricing = { input: 1.234567, output: 2.345678 };
93
88
 
@@ -95,13 +90,13 @@ describe('estimateCost()', () => {
95
90
  total: 0.000004,
96
91
  input: 0.000001,
97
92
  output: 0.000002,
98
- currency: 'USD',
93
+ currency: "USD",
99
94
  });
100
95
  });
101
96
  });
102
97
 
103
- describe('estimateSessionCost()', () => {
104
- test('aggregates multiple sessions before estimating cost', () => {
98
+ describe("estimateSessionCost()", () => {
99
+ test("aggregates multiple sessions before estimating cost", () => {
105
100
  const sessions = [
106
101
  { input: 100_000, output: 20_000, cacheRead: 10_000, cacheWrite: 5_000 },
107
102
  { input: 50_000, output: 10_000, cacheRead: 5_000, cacheWrite: 10_000 },
@@ -120,22 +115,22 @@ describe('estimateSessionCost()', () => {
120
115
  output: 0.525,
121
116
  cacheRead: 0.0045,
122
117
  cacheWrite: 0.05625,
123
- currency: 'USD',
118
+ currency: "USD",
124
119
  });
125
120
  });
126
121
 
127
- test('returns zero cost for an empty session list', () => {
122
+ test("returns zero cost for an empty session list", () => {
128
123
  const pricing: ModelPricing = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
129
124
 
130
125
  expect(estimateSessionCost([], pricing)).toEqual({
131
126
  total: 0,
132
127
  input: 0,
133
128
  output: 0,
134
- currency: 'USD',
129
+ currency: "USD",
135
130
  });
136
131
  });
137
132
 
138
- test('matches estimateCost for a single session', () => {
133
+ test("matches estimateCost for a single session", () => {
139
134
  const session = { input: 150_000, output: 50_000, cacheRead: 80_000, cacheWrite: 40_000 };
140
135
  const pricing: ModelPricing = {
141
136
  input: 3,
@@ -148,34 +143,34 @@ describe('estimateSessionCost()', () => {
148
143
  });
149
144
  });
150
145
 
151
- describe('formatCost()', () => {
152
- test('formats USD costs below $0.01 with 4 decimal places', () => {
153
- expect(formatCost(0.009876)).toBe('$0.0099');
146
+ describe("formatCost()", () => {
147
+ test("formats USD costs below $0.01 with 4 decimal places", () => {
148
+ expect(formatCost(0.009876)).toBe("$0.0099");
154
149
  });
155
150
 
156
- test('formats USD costs below $1 with 3 decimal places', () => {
157
- expect(formatCost(0.45678)).toBe('$0.457');
151
+ test("formats USD costs below $1 with 3 decimal places", () => {
152
+ expect(formatCost(0.45678)).toBe("$0.457");
158
153
  });
159
154
 
160
- test('formats USD costs at or above $1 with 2 decimal places', () => {
161
- expect(formatCost(12.3456)).toBe('$12.35');
155
+ test("formats USD costs at or above $1 with 2 decimal places", () => {
156
+ expect(formatCost(12.3456)).toBe("$12.35");
162
157
  });
163
158
 
164
- test('formats non-USD currency with 4 decimals and currency suffix', () => {
165
- expect(formatCost(12.3456, 'EUR')).toBe('12.3456 EUR');
159
+ test("formats non-USD currency with 4 decimals and currency suffix", () => {
160
+ expect(formatCost(12.3456, "EUR")).toBe("12.3456 EUR");
166
161
  });
167
162
  });
168
163
 
169
- describe('formatTokenCount()', () => {
170
- test('returns raw token count for values below 1K', () => {
171
- expect(formatTokenCount(999)).toBe('999');
164
+ describe("formatTokenCount()", () => {
165
+ test("returns raw token count for values below 1K", () => {
166
+ expect(formatTokenCount(999)).toBe("999");
172
167
  });
173
168
 
174
- test('formats token count in K for values below 1M', () => {
175
- expect(formatTokenCount(12_500)).toBe('12.5K');
169
+ test("formats token count in K for values below 1M", () => {
170
+ expect(formatTokenCount(12_500)).toBe("12.5K");
176
171
  });
177
172
 
178
- test('formats token count in M for values at or above 1M', () => {
179
- expect(formatTokenCount(2_345_678)).toBe('2.35M');
173
+ test("formats token count in M for values at or above 1M", () => {
174
+ expect(formatTokenCount(2_345_678)).toBe("2.35M");
180
175
  });
181
176
  });