@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,34 +1,40 @@
1
1
  import type {
2
- ProviderPlugin,
3
- ProviderFetchContext,
4
- ProviderUsageData,
5
- UsageLimit,
6
- Credentials,
7
2
  CredentialResult,
3
+ Credentials,
8
4
  OAuthCredentials,
9
- RefreshedCredentials,
10
5
  PluginContext,
11
6
  ProviderAuth,
12
- } from '../types/provider.ts';
7
+ ProviderFetchContext,
8
+ ProviderPlugin,
9
+ ProviderUsageData,
10
+ RefreshedCredentials,
11
+ UsageLimit,
12
+ } from "../types/provider.ts";
13
13
 
14
14
  const ANTIGRAVITY_ENDPOINTS = [
15
- 'https://daily-cloudcode-pa.sandbox.googleapis.com',
16
- 'https://autopush-cloudcode-pa.sandbox.googleapis.com',
17
- 'https://cloudcode-pa.googleapis.com',
15
+ "https://daily-cloudcode-pa.sandbox.googleapis.com",
16
+ "https://autopush-cloudcode-pa.sandbox.googleapis.com",
17
+ "https://cloudcode-pa.googleapis.com",
18
18
  ];
19
19
 
20
20
  const ANTIGRAVITY_HEADERS = {
21
- 'User-Agent': 'antigravity/1.11.5 windows/amd64',
22
- 'X-Goog-Api-Client': 'google-cloud-sdk vscode_cloudshelleditor/0.1',
23
- 'Client-Metadata': '{"ideType":"IDE_UNSPECIFIED","platform":"PLATFORM_UNSPECIFIED","pluginType":"GEMINI"}',
21
+ "User-Agent": "antigravity/1.11.5 windows/amd64",
22
+ "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
23
+ "Client-Metadata":
24
+ '{"ideType":"IDE_UNSPECIFIED","platform":"PLATFORM_UNSPECIFIED","pluginType":"GEMINI"}',
24
25
  };
25
26
 
26
27
  // Base64 encoded to avoid triggering GitHub secret scanning.
27
28
  // These are publicly-known OAuth client IDs from the open-source Antigravity CLI.
28
- const ANTIGRAVITY_CLIENT_ID = process.env.ANTIGRAVITY_CLIENT_ID
29
- ?? Buffer.from('MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==', 'base64').toString();
30
- const ANTIGRAVITY_CLIENT_SECRET = process.env.ANTIGRAVITY_CLIENT_SECRET
31
- ?? Buffer.from('R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=', 'base64').toString();
29
+ const ANTIGRAVITY_CLIENT_ID =
30
+ process.env.ANTIGRAVITY_CLIENT_ID ??
31
+ Buffer.from(
32
+ "MTA3MTAwNjA2MDU5MS10bWhzc2luMmgyMWxjcmUyMzV2dG9sb2poNGc0MDNlcC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbQ==",
33
+ "base64",
34
+ ).toString();
35
+ const ANTIGRAVITY_CLIENT_SECRET =
36
+ process.env.ANTIGRAVITY_CLIENT_SECRET ??
37
+ Buffer.from("R09DU1BYLUs1OEZXUjQ4NkxkTEoxbUxCOHNYQzR6NnFEQWY=", "base64").toString();
32
38
  const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
33
39
 
34
40
  interface AntigravityAccount {
@@ -57,32 +63,35 @@ interface TokenResponse {
57
63
  }
58
64
 
59
65
  interface AntigravityModelsResponse {
60
- models?: Record<string, {
61
- displayName?: string;
62
- quotaInfo?: {
63
- remainingFraction?: number | null;
64
- resetTime?: string | null;
65
- };
66
- }>;
66
+ models?: Record<
67
+ string,
68
+ {
69
+ displayName?: string;
70
+ quotaInfo?: {
71
+ remainingFraction?: number | null;
72
+ resetTime?: string | null;
73
+ };
74
+ }
75
+ >;
67
76
  }
68
77
 
69
78
  export const antigravityPlugin: ProviderPlugin = {
70
79
  apiVersion: 2,
71
- id: 'antigravity',
72
- type: 'provider',
73
- name: 'Antigravity',
74
- version: '1.0.0',
80
+ id: "antigravity",
81
+ type: "provider",
82
+ name: "Antigravity",
83
+ version: "1.0.0",
75
84
 
76
85
  meta: {
77
- description: 'Antigravity (Google Gemini Advanced) subscription usage tracking',
78
- homepage: 'https://one.google.com/explore-plan/gemini-advanced',
79
- brandColor: '#4285f4',
86
+ description: "Antigravity (Google Gemini Advanced) subscription usage tracking",
87
+ homepage: "https://one.google.com/explore-plan/gemini-advanced",
88
+ brandColor: "#4285f4",
80
89
  },
81
90
 
82
91
  permissions: {
83
92
  network: {
84
93
  enabled: true,
85
- allowedDomains: ['googleapis.com', 'sandbox.googleapis.com'],
94
+ allowedDomains: ["googleapis.com", "sandbox.googleapis.com"],
86
95
  },
87
96
  env: {
88
97
  read: false,
@@ -90,7 +99,7 @@ export const antigravityPlugin: ProviderPlugin = {
90
99
  },
91
100
  filesystem: {
92
101
  read: true,
93
- paths: ['~/.config/opencode'],
102
+ paths: ["~/.config/opencode"],
94
103
  },
95
104
  },
96
105
 
@@ -102,7 +111,7 @@ export const antigravityPlugin: ProviderPlugin = {
102
111
  },
103
112
 
104
113
  pricing: {
105
- modelsDevProviderId: 'google',
114
+ modelsDevProviderId: "google",
106
115
  },
107
116
 
108
117
  auth: {
@@ -115,27 +124,29 @@ export const antigravityPlugin: ProviderPlugin = {
115
124
  const account = agData.accounts[activeIndex] ?? agData.accounts[0];
116
125
  if (account && (account.refreshToken || account.accessToken)) {
117
126
  const oauth: OAuthCredentials = {
118
- accessToken: account.accessToken ?? '',
127
+ accessToken: account.accessToken ?? "",
119
128
  ...(account.refreshToken !== undefined && { refreshToken: account.refreshToken }),
120
129
  ...(account.expiresAt !== undefined && { expiresAt: account.expiresAt }),
121
- ...(account.managedProjectId !== undefined && { managedProjectId: account.managedProjectId }),
130
+ ...(account.managedProjectId !== undefined && {
131
+ managedProjectId: account.managedProjectId,
132
+ }),
122
133
  };
123
- return { ok: true, credentials: { oauth, source: 'opencode' } };
134
+ return { ok: true, credentials: { oauth, source: "opencode" } };
124
135
  }
125
136
  }
126
137
 
127
138
  // Source 2: OpenCode OAuth under 'google' key
128
- const entry = await ctx.authSources.opencode.getProviderEntry('google');
129
- if (entry?.type === 'oauth' && entry.access) {
139
+ const entry = await ctx.authSources.opencode.getProviderEntry("google");
140
+ if (entry?.type === "oauth" && entry.access) {
130
141
  const oauth: OAuthCredentials = {
131
142
  accessToken: entry.access,
132
143
  ...(entry.refresh !== undefined && { refreshToken: entry.refresh }),
133
144
  ...(entry.expires !== undefined && { expiresAt: entry.expires }),
134
145
  };
135
- return { ok: true, credentials: { oauth, source: 'opencode' } };
146
+ return { ok: true, credentials: { oauth, source: "opencode" } };
136
147
  }
137
148
 
138
- return { ok: false, reason: 'missing', message: 'No Antigravity credentials found' };
149
+ return { ok: false, reason: "missing", message: "No Antigravity credentials found" };
139
150
  },
140
151
 
141
152
  isConfigured(credentials: Credentials): boolean {
@@ -145,14 +156,14 @@ export const antigravityPlugin: ProviderPlugin = {
145
156
 
146
157
  async refreshToken(auth: OAuthCredentials): Promise<RefreshedCredentials> {
147
158
  if (!auth.refreshToken) {
148
- throw new Error('No refresh token available');
159
+ throw new Error("No refresh token available");
149
160
  }
150
161
 
151
- const response = await fetch('https://oauth2.googleapis.com/token', {
152
- method: 'POST',
153
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
162
+ const response = await fetch("https://oauth2.googleapis.com/token", {
163
+ method: "POST",
164
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
154
165
  body: new URLSearchParams({
155
- grant_type: 'refresh_token',
166
+ grant_type: "refresh_token",
156
167
  refresh_token: auth.refreshToken,
157
168
  client_id: ANTIGRAVITY_CLIENT_ID,
158
169
  client_secret: ANTIGRAVITY_CLIENT_SECRET,
@@ -160,7 +171,7 @@ export const antigravityPlugin: ProviderPlugin = {
160
171
  });
161
172
 
162
173
  if (!response.ok) {
163
- const errorText = await response.text().catch(() => '');
174
+ const errorText = await response.text().catch(() => "");
164
175
  throw new Error(`Token refresh failed: ${response.status} ${errorText.slice(0, 100)}`);
165
176
  }
166
177
 
@@ -179,23 +190,25 @@ export const antigravityPlugin: ProviderPlugin = {
179
190
  if (!credentials.oauth?.accessToken && !credentials.oauth?.refreshToken) {
180
191
  return {
181
192
  fetchedAt: Date.now(),
182
- error: 'OAuth token required. Sign in via OpenCode with Google account.',
193
+ error: "OAuth token required. Sign in via OpenCode with Google account.",
183
194
  };
184
195
  }
185
196
 
186
197
  let accessToken = credentials.oauth.accessToken;
187
- const needsRefresh = !accessToken ||
188
- (credentials.oauth.expiresAt && credentials.oauth.expiresAt <= Date.now() + TOKEN_EXPIRY_BUFFER_MS);
198
+ const needsRefresh =
199
+ !accessToken ||
200
+ (credentials.oauth.expiresAt &&
201
+ credentials.oauth.expiresAt <= Date.now() + TOKEN_EXPIRY_BUFFER_MS);
189
202
 
190
203
  if (needsRefresh && credentials.oauth.refreshToken) {
191
204
  try {
192
- log.debug('Access token expired or missing, refreshing...');
205
+ log.debug("Access token expired or missing, refreshing...");
193
206
  const refreshed = await this.refreshToken!(credentials.oauth);
194
207
  accessToken = refreshed.accessToken;
195
- log.debug('Token refreshed successfully');
208
+ log.debug("Token refreshed successfully");
196
209
  } catch (err) {
197
210
  const msg = err instanceof Error ? err.message : String(err);
198
- log.error('Token refresh failed', { error: msg });
211
+ log.error("Token refresh failed", { error: msg });
199
212
  return {
200
213
  fetchedAt: Date.now(),
201
214
  error: `Token refresh failed: ${msg}`,
@@ -206,7 +219,7 @@ export const antigravityPlugin: ProviderPlugin = {
206
219
  if (!accessToken) {
207
220
  return {
208
221
  fetchedAt: Date.now(),
209
- error: 'No valid access token and refresh failed.',
222
+ error: "No valid access token and refresh failed.",
210
223
  };
211
224
  }
212
225
 
@@ -215,38 +228,38 @@ export const antigravityPlugin: ProviderPlugin = {
215
228
  for (const endpoint of ANTIGRAVITY_ENDPOINTS) {
216
229
  try {
217
230
  const response = await http.fetch(`${endpoint}/v1internal:fetchAvailableModels`, {
218
- method: 'POST',
231
+ method: "POST",
219
232
  headers: {
220
- 'Authorization': `Bearer ${accessToken}`,
221
- 'Content-Type': 'application/json',
233
+ Authorization: `Bearer ${accessToken}`,
234
+ "Content-Type": "application/json",
222
235
  ...ANTIGRAVITY_HEADERS,
223
236
  },
224
237
  body: JSON.stringify({}),
225
238
  });
226
239
 
227
240
  if (!response.ok) {
228
- const errorText = await response.text().catch(() => '');
241
+ const errorText = await response.text().catch(() => "");
229
242
  lastError = `${response.status} ${response.statusText}: ${errorText.slice(0, 100)}`;
230
-
243
+
231
244
  if (response.status === 401) {
232
245
  return {
233
246
  fetchedAt: Date.now(),
234
- error: 'OAuth token expired or invalid. Re-authenticate in OpenCode.',
247
+ error: "OAuth token expired or invalid. Re-authenticate in OpenCode.",
235
248
  };
236
249
  }
237
-
250
+
238
251
  continue;
239
252
  }
240
253
 
241
254
  const data = (await response.json()) as AntigravityModelsResponse;
242
255
 
243
256
  if (!data.models) {
244
- lastError = 'No models data in response';
257
+ lastError = "No models data in response";
245
258
  continue;
246
259
  }
247
260
 
248
261
  const result: ProviderUsageData = {
249
- planType: 'Gemini Advanced',
262
+ planType: "Gemini Advanced",
250
263
  allowed: true,
251
264
  fetchedAt: Date.now(),
252
265
  };
@@ -275,9 +288,9 @@ export const antigravityPlugin: ProviderPlugin = {
275
288
  .sort((a, b) => {
276
289
  const usageDiff = b.usedPercent - a.usedPercent;
277
290
  if (usageDiff !== 0) return usageDiff;
278
- const labelA = a.limit.label ?? '';
279
- const labelB = b.limit.label ?? '';
280
- return labelB.localeCompare(labelA, undefined, { numeric: true, sensitivity: 'base' });
291
+ const labelA = a.limit.label ?? "";
292
+ const labelB = b.limit.label ?? "";
293
+ return labelB.localeCompare(labelA, undefined, { numeric: true, sensitivity: "base" });
281
294
  });
282
295
 
283
296
  if (quotas.length > 0) {
@@ -296,14 +309,13 @@ export const antigravityPlugin: ProviderPlugin = {
296
309
  return result;
297
310
  } catch (err) {
298
311
  lastError = err instanceof Error ? err.message : String(err);
299
- continue;
300
312
  }
301
313
  }
302
314
 
303
- log.error('Failed to fetch Antigravity usage from all endpoints', { error: lastError });
315
+ log.error("Failed to fetch Antigravity usage from all endpoints", { error: lastError });
304
316
  return {
305
317
  fetchedAt: Date.now(),
306
- error: lastError ?? 'Failed to fetch usage from all endpoints',
318
+ error: lastError ?? "Failed to fetch usage from all endpoints",
307
319
  };
308
320
  },
309
321
  };
@@ -1,13 +1,13 @@
1
1
  import type {
2
- ProviderPlugin,
3
- ProviderFetchContext,
4
- ProviderUsageData,
5
- Credentials,
6
2
  CredentialResult,
3
+ Credentials,
7
4
  OAuthCredentials,
8
5
  PluginContext,
9
6
  ProviderAuth,
10
- } from '../types/provider.ts';
7
+ ProviderFetchContext,
8
+ ProviderPlugin,
9
+ ProviderUsageData,
10
+ } from "../types/provider.ts";
11
11
 
12
12
  interface CodexCliCredentials {
13
13
  access_token?: string;
@@ -57,21 +57,21 @@ interface CodexUsageResponse {
57
57
 
58
58
  export const codexPlugin: ProviderPlugin = {
59
59
  apiVersion: 2,
60
- id: 'codex',
61
- type: 'provider',
62
- name: 'Codex',
63
- version: '1.0.0',
60
+ id: "codex",
61
+ type: "provider",
62
+ name: "Codex",
63
+ version: "1.0.0",
64
64
 
65
65
  meta: {
66
- description: 'OpenAI Codex subscription usage tracking (OAuth)',
67
- homepage: 'https://openai.com/codex',
68
- brandColor: '#10a37f',
66
+ description: "OpenAI Codex subscription usage tracking (OAuth)",
67
+ homepage: "https://openai.com/codex",
68
+ brandColor: "#10a37f",
69
69
  },
70
70
 
71
71
  permissions: {
72
72
  network: {
73
73
  enabled: true,
74
- allowedDomains: ['chatgpt.com'],
74
+ allowedDomains: ["chatgpt.com"],
75
75
  },
76
76
  env: {
77
77
  read: false,
@@ -79,7 +79,7 @@ export const codexPlugin: ProviderPlugin = {
79
79
  },
80
80
  filesystem: {
81
81
  read: true,
82
- paths: ['~/.codex'],
82
+ paths: ["~/.codex"],
83
83
  },
84
84
  },
85
85
 
@@ -91,12 +91,12 @@ export const codexPlugin: ProviderPlugin = {
91
91
  },
92
92
 
93
93
  pricing: {
94
- modelsDevProviderId: 'openai',
94
+ modelsDevProviderId: "openai",
95
95
  },
96
96
 
97
97
  auth: {
98
98
  async discover(ctx: PluginContext): Promise<CredentialResult> {
99
- const entry = await ctx.authSources.opencode.getProviderEntry('openai');
99
+ const entry = await ctx.authSources.opencode.getProviderEntry("openai");
100
100
  if (entry) {
101
101
  const accessToken = entry.accessToken || entry.access;
102
102
  const refreshToken = entry.refreshToken || entry.refresh;
@@ -111,7 +111,7 @@ export const codexPlugin: ProviderPlugin = {
111
111
  expiresAt ?? undefined,
112
112
  entry.accountId,
113
113
  ),
114
- source: 'opencode',
114
+ source: "opencode",
115
115
  },
116
116
  };
117
117
  }
@@ -130,12 +130,12 @@ export const codexPlugin: ProviderPlugin = {
130
130
  data.expires_at,
131
131
  data.account_id,
132
132
  ),
133
- source: 'external',
133
+ source: "external",
134
134
  },
135
135
  };
136
136
  }
137
137
 
138
- return { ok: false, reason: 'missing', message: 'No Codex credentials found' };
138
+ return { ok: false, reason: "missing", message: "No Codex credentials found" };
139
139
  },
140
140
 
141
141
  isConfigured(credentials: Credentials): boolean {
@@ -149,42 +149,42 @@ export const codexPlugin: ProviderPlugin = {
149
149
  if (!credentials.oauth?.accessToken) {
150
150
  return {
151
151
  fetchedAt: Date.now(),
152
- error: 'OAuth token required. Sign in via OpenCode with ChatGPT Pro account.',
152
+ error: "OAuth token required. Sign in via OpenCode with ChatGPT Pro account.",
153
153
  };
154
154
  }
155
155
 
156
156
  if (!credentials.oauth.accountId) {
157
157
  return {
158
158
  fetchedAt: Date.now(),
159
- error: 'ChatGPT account ID required. Re-authenticate in OpenCode.',
159
+ error: "ChatGPT account ID required. Re-authenticate in OpenCode.",
160
160
  };
161
161
  }
162
162
 
163
163
  const headers: Record<string, string> = {
164
- 'Authorization': `Bearer ${credentials.oauth.accessToken}`,
165
- 'ChatGPT-Account-Id': credentials.oauth.accountId,
166
- 'Accept': 'application/json',
167
- 'Content-Type': 'application/json',
168
- 'User-Agent': 'tokentop',
164
+ Authorization: `Bearer ${credentials.oauth.accessToken}`,
165
+ "ChatGPT-Account-Id": credentials.oauth.accountId,
166
+ Accept: "application/json",
167
+ "Content-Type": "application/json",
168
+ "User-Agent": "tokentop",
169
169
  };
170
170
 
171
171
  try {
172
- const response = await http.fetch('https://chatgpt.com/backend-api/wham/usage', {
173
- method: 'GET',
172
+ const response = await http.fetch("https://chatgpt.com/backend-api/wham/usage", {
173
+ method: "GET",
174
174
  headers,
175
175
  });
176
176
 
177
177
  if (!response.ok) {
178
- const bodyText = await response.text().catch(() => '');
179
- log.warn('Failed to fetch ChatGPT usage', { status: response.status, body: bodyText });
180
-
178
+ const bodyText = await response.text().catch(() => "");
179
+ log.warn("Failed to fetch ChatGPT usage", { status: response.status, body: bodyText });
180
+
181
181
  if (response.status === 401) {
182
182
  return {
183
183
  fetchedAt: Date.now(),
184
- error: 'OAuth token expired. Re-authenticate in OpenCode.',
184
+ error: "OAuth token expired. Re-authenticate in OpenCode.",
185
185
  };
186
186
  }
187
-
187
+
188
188
  return {
189
189
  fetchedAt: Date.now(),
190
190
  error: `API error: ${response.status} ${response.statusText}`,
@@ -194,12 +194,12 @@ export const codexPlugin: ProviderPlugin = {
194
194
  const data = (await response.json()) as CodexUsageResponse;
195
195
 
196
196
  const planTypeMap: Record<string, string> = {
197
- 'plus': 'ChatGPT Plus',
198
- 'pro': 'ChatGPT Pro',
199
- 'team': 'ChatGPT Team',
200
- 'enterprise': 'ChatGPT Enterprise',
197
+ plus: "ChatGPT Plus",
198
+ pro: "ChatGPT Pro",
199
+ team: "ChatGPT Team",
200
+ enterprise: "ChatGPT Enterprise",
201
201
  };
202
- const planType = planTypeMap[data.plan_type] ?? data.plan_type ?? 'ChatGPT';
202
+ const planType = planTypeMap[data.plan_type] ?? data.plan_type ?? "ChatGPT";
203
203
 
204
204
  const result: ProviderUsageData = {
205
205
  planType,
@@ -215,7 +215,7 @@ export const codexPlugin: ProviderPlugin = {
215
215
  const pw = data.rate_limit.primary_window;
216
216
  result.limits.primary = {
217
217
  usedPercent: pw.used_percent,
218
- label: '5-hour window',
218
+ label: "5-hour window",
219
219
  ...(pw.reset_at ? { resetsAt: pw.reset_at * 1000 } : {}),
220
220
  };
221
221
  }
@@ -224,7 +224,7 @@ export const codexPlugin: ProviderPlugin = {
224
224
  const sw = data.rate_limit.secondary_window;
225
225
  result.limits.secondary = {
226
226
  usedPercent: sw.used_percent,
227
- label: '7-day window',
227
+ label: "7-day window",
228
228
  ...(sw.reset_at ? { resetsAt: sw.reset_at * 1000 } : {}),
229
229
  };
230
230
  }
@@ -240,10 +240,10 @@ export const codexPlugin: ProviderPlugin = {
240
240
 
241
241
  return result;
242
242
  } catch (err) {
243
- log.error('Failed to fetch ChatGPT usage', { error: err });
243
+ log.error("Failed to fetch ChatGPT usage", { error: err });
244
244
  return {
245
245
  fetchedAt: Date.now(),
246
- error: err instanceof Error ? err.message : 'Unknown error',
246
+ error: err instanceof Error ? err.message : "Unknown error",
247
247
  };
248
248
  }
249
249
  },