@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.
- package/README.md +2 -5
- package/package.json +8 -5
- package/src/agents/aggregator.ts +39 -19
- package/src/agents/costing.ts +11 -9
- package/src/agents/index.ts +7 -9
- package/src/agents/types.ts +2 -2
- package/src/cli.ts +28 -23
- package/src/config/schema.test.ts +42 -40
- package/src/config/schema.ts +26 -26
- package/src/demo/simulator.ts +161 -122
- package/src/plugins/agents/index.ts +2 -2
- package/src/plugins/auth-sources.ts +42 -38
- package/src/plugins/lifecycle.ts +32 -32
- package/src/plugins/loader.test.ts +49 -49
- package/src/plugins/loader.ts +67 -63
- package/src/plugins/notification-bus.ts +21 -16
- package/src/plugins/notifications/index.ts +2 -2
- package/src/plugins/notifications/terminal-bell.ts +29 -23
- package/src/plugins/notifications/visual-flash.ts +21 -19
- package/src/plugins/npm-installer.ts +23 -25
- package/src/plugins/plugin-context-factory.ts +10 -10
- package/src/plugins/plugin-host.ts +12 -5
- package/src/plugins/providers/anthropic.ts +64 -57
- package/src/plugins/providers/antigravity.ts +81 -69
- package/src/plugins/providers/codex.ts +42 -42
- package/src/plugins/providers/gemini.ts +106 -87
- package/src/plugins/providers/github-copilot.ts +49 -45
- package/src/plugins/providers/index.ts +10 -12
- package/src/plugins/providers/minimax.ts +50 -44
- package/src/plugins/providers/openai-api.ts +82 -62
- package/src/plugins/providers/opencode-zen.ts +46 -39
- package/src/plugins/providers/perplexity.ts +55 -46
- package/src/plugins/providers/zai.ts +66 -57
- package/src/plugins/registry.ts +47 -43
- package/src/plugins/sandbox-guard.test.ts +45 -48
- package/src/plugins/sandbox-guard.ts +20 -25
- package/src/plugins/sandbox.ts +26 -48
- package/src/plugins/themes/catppuccin-latte.ts +29 -29
- package/src/plugins/themes/catppuccin-mocha.ts +29 -29
- package/src/plugins/themes/claude-code.ts +29 -29
- package/src/plugins/themes/dracula.ts +29 -29
- package/src/plugins/themes/github-light.ts +29 -29
- package/src/plugins/themes/gruvbox-dark.ts +29 -29
- package/src/plugins/themes/gruvbox-light.ts +29 -29
- package/src/plugins/themes/index.ts +15 -15
- package/src/plugins/themes/kanagawa.ts +29 -29
- package/src/plugins/themes/nord.ts +29 -29
- package/src/plugins/themes/one-dark.ts +29 -29
- package/src/plugins/themes/opencode.ts +31 -31
- package/src/plugins/themes/rose-pine-dawn.ts +29 -29
- package/src/plugins/themes/rose-pine.ts +29 -29
- package/src/plugins/themes/solarized-light.ts +29 -29
- package/src/plugins/themes/tokyo-night.ts +29 -29
- package/src/plugins/types/agent.ts +5 -5
- package/src/plugins/types/base.ts +35 -24
- package/src/plugins/types/index.ts +9 -9
- package/src/plugins/types/notification.ts +12 -12
- package/src/plugins/types/provider.ts +12 -8
- package/src/plugins/types/theme.ts +31 -23
- package/src/plugins/update-checker.ts +6 -6
- package/src/pricing/estimator.test.ts +39 -44
- package/src/pricing/estimator.ts +7 -13
- package/src/pricing/fallback.test.ts +30 -34
- package/src/pricing/fallback.ts +54 -27
- package/src/pricing/index.ts +18 -12
- package/src/pricing/models-dev.ts +6 -6
- package/src/storage/database.ts +56 -46
- package/src/storage/db.ts +12 -12
- package/src/storage/index.ts +44 -45
- package/src/storage/migrations/index.ts +2 -2
- package/src/storage/paths.ts +13 -13
- package/src/storage/repos/agentSessions.ts +33 -23
- package/src/storage/repos/providerSnapshots.ts +5 -5
- package/src/storage/repos/usageEvents.ts +40 -28
- package/src/storage/types.ts +5 -5
- package/src/tui/App.tsx +180 -137
- package/src/tui/components/CommandPalette.tsx +46 -34
- package/src/tui/components/DebugConsole.tsx +39 -33
- package/src/tui/components/DebugPanel.tsx +295 -177
- package/src/tui/components/FooterHints.tsx +2 -2
- package/src/tui/components/GhostProviderCard.tsx +37 -35
- package/src/tui/components/Header.tsx +68 -35
- package/src/tui/components/HelpOverlay.tsx +99 -29
- package/src/tui/components/InlineGauge.tsx +10 -7
- package/src/tui/components/InlineSparkline.tsx +17 -11
- package/src/tui/components/KpiStrip.tsx +128 -98
- package/src/tui/components/LimitGauge.tsx +96 -59
- package/src/tui/components/ModalBackdrop.tsx +4 -4
- package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
- package/src/tui/components/ProviderCard.tsx +232 -226
- package/src/tui/components/ProviderDetailPanel.tsx +133 -58
- package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
- package/src/tui/components/ProvidersList.tsx +215 -126
- package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
- package/src/tui/components/SessionsTable.tsx +313 -166
- package/src/tui/components/SettingsModal.tsx +231 -214
- package/src/tui/components/SidebarBreakdown.tsx +30 -16
- package/src/tui/components/Skeleton.tsx +24 -17
- package/src/tui/components/SmartSidebar.tsx +360 -258
- package/src/tui/components/Sparkline.tsx +64 -52
- package/src/tui/components/Spinner.tsx +3 -3
- package/src/tui/components/StatusBar.tsx +27 -27
- package/src/tui/components/ThemePicker.tsx +67 -66
- package/src/tui/components/Toast.tsx +9 -6
- package/src/tui/components/UsageGauge.tsx +10 -13
- package/src/tui/components/index.ts +5 -5
- package/src/tui/contexts/AgentSessionContext.tsx +193 -144
- package/src/tui/contexts/ConfigContext.tsx +15 -13
- package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
- package/src/tui/contexts/DemoModeContext.tsx +22 -15
- package/src/tui/contexts/DrawerContext.tsx +11 -9
- package/src/tui/contexts/InputContext.tsx +2 -2
- package/src/tui/contexts/LogContext.tsx +62 -47
- package/src/tui/contexts/PluginContext.tsx +232 -183
- package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
- package/src/tui/contexts/StorageContext.tsx +151 -117
- package/src/tui/contexts/ThemeContext.tsx +21 -16
- package/src/tui/contexts/TimeWindowContext.tsx +72 -61
- package/src/tui/contexts/ToastContext.tsx +10 -7
- package/src/tui/contexts/index.ts +9 -5
- package/src/tui/createApp.tsx +8 -12
- package/src/tui/debug/captureFrame.ts +25 -28
- package/src/tui/debug/snapshot.tsx +179 -162
- package/src/tui/driver/assertions.ts +30 -22
- package/src/tui/driver/cli.ts +231 -221
- package/src/tui/driver/coverage.ts +27 -44
- package/src/tui/driver/demo-snapshot.ts +33 -33
- package/src/tui/driver/diff.ts +26 -32
- package/src/tui/driver/driver.ts +43 -48
- package/src/tui/driver/index.ts +36 -40
- package/src/tui/driver/recorder.ts +37 -48
- package/src/tui/driver/test-workflow.ts +51 -51
- package/src/tui/driver/test.ts +25 -25
- package/src/tui/hooks/index.ts +24 -20
- package/src/tui/hooks/useAnimatedValue.ts +13 -18
- package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
- package/src/tui/hooks/useDashboardState.ts +26 -24
- package/src/tui/hooks/useEmaActivity.ts +82 -74
- package/src/tui/hooks/useEntranceAnimation.ts +11 -11
- package/src/tui/hooks/useExitAnimation.ts +3 -3
- package/src/tui/hooks/usePulse.ts +4 -4
- package/src/tui/hooks/useSafeRenderer.ts +5 -5
- package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
- package/src/tui/hooks/useValueFlash.ts +10 -14
- package/src/tui/index.tsx +29 -6
- package/src/tui/shutdown.ts +19 -0
- package/src/tui/utils/providerColor.ts +1 -1
- package/src/tui/views/Dashboard.tsx +127 -102
- package/src/tui/views/HistoricalTrendsView.tsx +579 -250
- package/src/tui/views/ProjectsView.tsx +466 -237
- package/src/tui/views/RealTimeDashboard.tsx +150 -111
- package/src/tui/views/SettingsView.tsx +222 -191
- package/src/tui/views/index.ts +2 -2
- package/src/types/opentui-test-utils.d.ts +7 -7
- package/src/utils/clipboard.ts +29 -29
- package/src/utils/currency.test.ts +53 -53
- package/src/utils/currency.ts +35 -32
- package/src/version.ts +1 -1
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import type { PluginPermissions } from './types/base.ts';
|
|
2
|
-
import type { PluginContext, PluginStorage } from './types/provider.ts';
|
|
3
|
-
import { createAuthSources } from './auth-sources.ts';
|
|
4
|
-
import { createSandboxedHttpClient, createPluginLogger } from './sandbox.ts';
|
|
5
|
-
import { deepFreeze } from './sandbox-guard.ts';
|
|
6
1
|
import {
|
|
7
|
-
|
|
8
|
-
pluginStorageSet,
|
|
2
|
+
getDatabase,
|
|
9
3
|
pluginStorageDelete,
|
|
4
|
+
pluginStorageGet,
|
|
10
5
|
pluginStorageHas,
|
|
11
|
-
|
|
12
|
-
} from
|
|
6
|
+
pluginStorageSet,
|
|
7
|
+
} from "@/storage/database.ts";
|
|
8
|
+
import { createAuthSources } from "./auth-sources.ts";
|
|
9
|
+
import { createPluginLogger, createSandboxedHttpClient } from "./sandbox.ts";
|
|
10
|
+
import { deepFreeze } from "./sandbox-guard.ts";
|
|
11
|
+
import type { PluginPermissions } from "./types/base.ts";
|
|
12
|
+
import type { PluginContext, PluginStorage } from "./types/provider.ts";
|
|
13
13
|
|
|
14
14
|
let dbAvailable: boolean | null = null;
|
|
15
15
|
|
|
@@ -52,7 +52,7 @@ export function resetDbAvailableCache(): void {
|
|
|
52
52
|
export function createPluginContext(
|
|
53
53
|
pluginId: string,
|
|
54
54
|
permissions: PluginPermissions,
|
|
55
|
-
signal?: AbortSignal
|
|
55
|
+
signal?: AbortSignal,
|
|
56
56
|
): PluginContext {
|
|
57
57
|
const ctx: PluginContext = {
|
|
58
58
|
config: {},
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 3. All errors are logged through the scoped plugin logger
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { createPluginLogger } from
|
|
10
|
+
import { createPluginLogger } from "./sandbox.ts";
|
|
11
11
|
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
// Circuit breaker state
|
|
@@ -71,10 +71,14 @@ export async function safeInvoke<T>(
|
|
|
71
71
|
|
|
72
72
|
// Check if circuit is open (plugin disabled)
|
|
73
73
|
if (circuit.disabledUntil > Date.now()) {
|
|
74
|
-
log.warn(
|
|
74
|
+
log.warn(
|
|
75
|
+
`Circuit open — skipping ${method} (disabled until ${new Date(circuit.disabledUntil).toISOString()})`,
|
|
76
|
+
);
|
|
75
77
|
return {
|
|
76
78
|
ok: false,
|
|
77
|
-
error: new Error(
|
|
79
|
+
error: new Error(
|
|
80
|
+
`Plugin "${pluginId}" is temporarily disabled after ${circuit.consecutiveFailures} consecutive failures`,
|
|
81
|
+
),
|
|
78
82
|
circuitOpen: true,
|
|
79
83
|
};
|
|
80
84
|
}
|
|
@@ -89,7 +93,9 @@ export async function safeInvoke<T>(
|
|
|
89
93
|
|
|
90
94
|
// Success — reset consecutive failures
|
|
91
95
|
if (circuit.consecutiveFailures > 0) {
|
|
92
|
-
log.info(
|
|
96
|
+
log.info(
|
|
97
|
+
`${method} succeeded — resetting circuit breaker (was at ${circuit.consecutiveFailures} failures)`,
|
|
98
|
+
);
|
|
93
99
|
}
|
|
94
100
|
circuit.consecutiveFailures = 0;
|
|
95
101
|
circuit.disabledUntil = 0;
|
|
@@ -181,7 +187,8 @@ export function getPluginHealth(pluginId: string): PluginHealth {
|
|
|
181
187
|
const circuit = getCircuit(pluginId);
|
|
182
188
|
return {
|
|
183
189
|
pluginId,
|
|
184
|
-
healthy:
|
|
190
|
+
healthy:
|
|
191
|
+
circuit.disabledUntil < Date.now() && circuit.consecutiveFailures < MAX_CONSECUTIVE_FAILURES,
|
|
185
192
|
consecutiveFailures: circuit.consecutiveFailures,
|
|
186
193
|
totalFailures: circuit.totalFailures,
|
|
187
194
|
totalCalls: circuit.totalCalls,
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { execFileSync } from
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
2
|
import type {
|
|
3
|
-
ProviderPlugin,
|
|
4
|
-
ProviderFetchContext,
|
|
5
|
-
ProviderUsageData,
|
|
6
|
-
Credentials,
|
|
7
3
|
CredentialResult,
|
|
4
|
+
Credentials,
|
|
8
5
|
OAuthCredentials,
|
|
9
6
|
PluginContext,
|
|
10
7
|
ProviderAuth,
|
|
11
|
-
|
|
8
|
+
ProviderFetchContext,
|
|
9
|
+
ProviderPlugin,
|
|
10
|
+
ProviderUsageData,
|
|
11
|
+
} from "../types/provider.ts";
|
|
12
12
|
|
|
13
13
|
const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
|
|
14
14
|
|
|
@@ -28,7 +28,7 @@ function buildOAuthCredentials(
|
|
|
28
28
|
refreshToken?: string,
|
|
29
29
|
expiresAt?: number,
|
|
30
30
|
accountId?: string,
|
|
31
|
-
subscriptionType?: string
|
|
31
|
+
subscriptionType?: string,
|
|
32
32
|
): OAuthCredentials {
|
|
33
33
|
const oauth: OAuthCredentials = { accessToken };
|
|
34
34
|
if (refreshToken !== undefined) oauth.refreshToken = refreshToken;
|
|
@@ -44,12 +44,12 @@ async function discoverClaudeCode(ctx: PluginContext): Promise<CredentialResult>
|
|
|
44
44
|
const now = Date.now();
|
|
45
45
|
|
|
46
46
|
// Try macOS Keychain first (only on darwin)
|
|
47
|
-
if (ctx.authSources.platform.os ===
|
|
47
|
+
if (ctx.authSources.platform.os === "darwin") {
|
|
48
48
|
try {
|
|
49
49
|
const keychainData = execFileSync(
|
|
50
|
-
|
|
51
|
-
[
|
|
52
|
-
{ encoding:
|
|
50
|
+
"/usr/bin/security",
|
|
51
|
+
["find-generic-password", "-s", "Claude Code-credentials", "-w"],
|
|
52
|
+
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 },
|
|
53
53
|
).trim();
|
|
54
54
|
|
|
55
55
|
if (keychainData) {
|
|
@@ -66,9 +66,9 @@ async function discoverClaudeCode(ctx: PluginContext): Promise<CredentialResult>
|
|
|
66
66
|
oauth.refreshToken,
|
|
67
67
|
oauth.expiresAt,
|
|
68
68
|
undefined,
|
|
69
|
-
oauth.subscriptionType
|
|
69
|
+
oauth.subscriptionType,
|
|
70
70
|
),
|
|
71
|
-
source:
|
|
71
|
+
source: "external",
|
|
72
72
|
},
|
|
73
73
|
};
|
|
74
74
|
}
|
|
@@ -83,10 +83,11 @@ async function discoverClaudeCode(ctx: PluginContext): Promise<CredentialResult>
|
|
|
83
83
|
const credPath = `${ctx.authSources.platform.homedir}/.claude/.credentials.json`;
|
|
84
84
|
const fileData = await ctx.authSources.files.readJson<ClaudeCodeCredentialsFile>(credPath);
|
|
85
85
|
const fileOauth = fileData?.claudeAiOauth;
|
|
86
|
-
if (!fileOauth?.accessToken)
|
|
86
|
+
if (!fileOauth?.accessToken)
|
|
87
|
+
return { ok: false, reason: "missing", message: "No Claude Code credentials found" };
|
|
87
88
|
|
|
88
89
|
if (fileOauth.expiresAt && fileOauth.expiresAt <= now) {
|
|
89
|
-
return { ok: false, reason:
|
|
90
|
+
return { ok: false, reason: "expired", message: "Claude Code token expired" };
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
return {
|
|
@@ -97,9 +98,9 @@ async function discoverClaudeCode(ctx: PluginContext): Promise<CredentialResult>
|
|
|
97
98
|
fileOauth.refreshToken,
|
|
98
99
|
fileOauth.expiresAt,
|
|
99
100
|
undefined,
|
|
100
|
-
fileOauth.subscriptionType
|
|
101
|
+
fileOauth.subscriptionType,
|
|
101
102
|
),
|
|
102
|
-
source:
|
|
103
|
+
source: "external",
|
|
103
104
|
},
|
|
104
105
|
};
|
|
105
106
|
}
|
|
@@ -117,29 +118,29 @@ interface AnthropicUsageResponse {
|
|
|
117
118
|
|
|
118
119
|
export const anthropicPlugin: ProviderPlugin = {
|
|
119
120
|
apiVersion: 2,
|
|
120
|
-
id:
|
|
121
|
-
type:
|
|
122
|
-
name:
|
|
123
|
-
version:
|
|
121
|
+
id: "anthropic",
|
|
122
|
+
type: "provider",
|
|
123
|
+
name: "Anthropic",
|
|
124
|
+
version: "1.0.0",
|
|
124
125
|
|
|
125
126
|
meta: {
|
|
126
|
-
description:
|
|
127
|
-
homepage:
|
|
128
|
-
brandColor:
|
|
127
|
+
description: "Anthropic Claude subscription usage tracking",
|
|
128
|
+
homepage: "https://anthropic.com",
|
|
129
|
+
brandColor: "#d4a27f",
|
|
129
130
|
},
|
|
130
131
|
|
|
131
132
|
permissions: {
|
|
132
133
|
network: {
|
|
133
134
|
enabled: true,
|
|
134
|
-
allowedDomains: [
|
|
135
|
+
allowedDomains: ["api.anthropic.com"],
|
|
135
136
|
},
|
|
136
137
|
env: {
|
|
137
138
|
read: true,
|
|
138
|
-
vars: [
|
|
139
|
+
vars: ["ANTHROPIC_API_KEY"],
|
|
139
140
|
},
|
|
140
141
|
filesystem: {
|
|
141
142
|
read: true,
|
|
142
|
-
paths: [
|
|
143
|
+
paths: ["~/.claude", "~/.local/share/opencode"],
|
|
143
144
|
},
|
|
144
145
|
},
|
|
145
146
|
|
|
@@ -152,13 +153,18 @@ export const anthropicPlugin: ProviderPlugin = {
|
|
|
152
153
|
|
|
153
154
|
auth: {
|
|
154
155
|
async discover(ctx: PluginContext): Promise<CredentialResult> {
|
|
155
|
-
const entry = await ctx.authSources.opencode.getProviderEntry(
|
|
156
|
-
if (entry?.type ===
|
|
156
|
+
const entry = await ctx.authSources.opencode.getProviderEntry("anthropic");
|
|
157
|
+
if (entry?.type === "oauth" && entry.access) {
|
|
157
158
|
return {
|
|
158
159
|
ok: true,
|
|
159
160
|
credentials: {
|
|
160
|
-
oauth: buildOAuthCredentials(
|
|
161
|
-
|
|
161
|
+
oauth: buildOAuthCredentials(
|
|
162
|
+
entry.access,
|
|
163
|
+
entry.refresh,
|
|
164
|
+
entry.expires,
|
|
165
|
+
entry.accountId,
|
|
166
|
+
),
|
|
167
|
+
source: "opencode",
|
|
162
168
|
},
|
|
163
169
|
};
|
|
164
170
|
}
|
|
@@ -166,12 +172,12 @@ export const anthropicPlugin: ProviderPlugin = {
|
|
|
166
172
|
const claudeCodeResult = await discoverClaudeCode(ctx);
|
|
167
173
|
if (claudeCodeResult.ok) return claudeCodeResult;
|
|
168
174
|
|
|
169
|
-
const apiKey = ctx.authSources.env.get(
|
|
175
|
+
const apiKey = ctx.authSources.env.get("ANTHROPIC_API_KEY");
|
|
170
176
|
if (apiKey) {
|
|
171
|
-
return { ok: true, credentials: { apiKey, source:
|
|
177
|
+
return { ok: true, credentials: { apiKey, source: "env" } };
|
|
172
178
|
}
|
|
173
179
|
|
|
174
|
-
return { ok: false, reason:
|
|
180
|
+
return { ok: false, reason: "missing", message: "No Anthropic credentials found" };
|
|
175
181
|
},
|
|
176
182
|
|
|
177
183
|
isConfigured(credentials: Credentials): boolean {
|
|
@@ -188,13 +194,13 @@ export const anthropicPlugin: ProviderPlugin = {
|
|
|
188
194
|
if (!hasOAuth && !hasApiKey) {
|
|
189
195
|
return {
|
|
190
196
|
fetchedAt: Date.now(),
|
|
191
|
-
error:
|
|
197
|
+
error: "OAuth token or API key required. Authenticate via OpenCode or Claude Code.",
|
|
192
198
|
};
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
if (hasApiKey && !hasOAuth) {
|
|
196
202
|
return {
|
|
197
|
-
planType:
|
|
203
|
+
planType: "API",
|
|
198
204
|
allowed: true,
|
|
199
205
|
fetchedAt: Date.now(),
|
|
200
206
|
};
|
|
@@ -205,41 +211,42 @@ export const anthropicPlugin: ProviderPlugin = {
|
|
|
205
211
|
if (isExpired) {
|
|
206
212
|
return {
|
|
207
213
|
fetchedAt: Date.now(),
|
|
208
|
-
error:
|
|
214
|
+
error: "Token expired. Run any command in OpenCode to refresh.",
|
|
209
215
|
};
|
|
210
216
|
}
|
|
211
217
|
}
|
|
212
218
|
|
|
213
219
|
const accessToken = credentials.oauth!.accessToken;
|
|
214
|
-
const subscriptionType = (credentials.oauth as { subscriptionType?: string } | undefined)
|
|
220
|
+
const subscriptionType = (credentials.oauth as { subscriptionType?: string } | undefined)
|
|
221
|
+
?.subscriptionType;
|
|
215
222
|
const planType = getPlanName(subscriptionType);
|
|
216
223
|
|
|
217
224
|
try {
|
|
218
|
-
const response = await http.fetch(
|
|
219
|
-
method:
|
|
225
|
+
const response = await http.fetch("https://api.anthropic.com/api/oauth/usage", {
|
|
226
|
+
method: "GET",
|
|
220
227
|
headers: {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
228
|
+
Authorization: `Bearer ${accessToken}`,
|
|
229
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
230
|
+
"User-Agent": "tokentop/1.0",
|
|
224
231
|
},
|
|
225
232
|
});
|
|
226
233
|
|
|
227
234
|
if (!response.ok) {
|
|
228
|
-
log.warn(
|
|
229
|
-
|
|
235
|
+
log.warn("Failed to fetch Anthropic usage", { status: response.status });
|
|
236
|
+
|
|
230
237
|
if (response.status === 401) {
|
|
231
238
|
return {
|
|
232
239
|
fetchedAt: Date.now(),
|
|
233
|
-
error:
|
|
240
|
+
error: "Token expired or invalid. Re-authenticate in OpenCode or Claude Code.",
|
|
234
241
|
};
|
|
235
242
|
}
|
|
236
243
|
if (response.status === 403) {
|
|
237
244
|
return {
|
|
238
245
|
fetchedAt: Date.now(),
|
|
239
|
-
error:
|
|
246
|
+
error: "Token lacks required scope. Re-authenticate in OpenCode or Claude Code.",
|
|
240
247
|
};
|
|
241
248
|
}
|
|
242
|
-
|
|
249
|
+
|
|
243
250
|
return {
|
|
244
251
|
fetchedAt: Date.now(),
|
|
245
252
|
error: `API error: ${response.status} ${response.statusText}`,
|
|
@@ -249,7 +256,7 @@ export const anthropicPlugin: ProviderPlugin = {
|
|
|
249
256
|
const data = (await response.json()) as AnthropicUsageResponse;
|
|
250
257
|
|
|
251
258
|
const result: ProviderUsageData = {
|
|
252
|
-
planType: planType ??
|
|
259
|
+
planType: planType ?? "Pro/Max",
|
|
253
260
|
allowed: true,
|
|
254
261
|
limitReached: false,
|
|
255
262
|
fetchedAt: Date.now(),
|
|
@@ -265,7 +272,7 @@ export const anthropicPlugin: ProviderPlugin = {
|
|
|
265
272
|
result.limits.primary = {
|
|
266
273
|
usedPercent: fiveHour,
|
|
267
274
|
windowMinutes: 300,
|
|
268
|
-
label:
|
|
275
|
+
label: "5-hour window",
|
|
269
276
|
};
|
|
270
277
|
if (data.five_hour?.resets_at) {
|
|
271
278
|
const resetTime = new Date(data.five_hour.resets_at).getTime();
|
|
@@ -279,7 +286,7 @@ export const anthropicPlugin: ProviderPlugin = {
|
|
|
279
286
|
result.limits.secondary = {
|
|
280
287
|
usedPercent: sevenDay,
|
|
281
288
|
windowMinutes: 10080,
|
|
282
|
-
label:
|
|
289
|
+
label: "7-day window",
|
|
283
290
|
};
|
|
284
291
|
if (data.seven_day?.resets_at) {
|
|
285
292
|
const resetTime = new Date(data.seven_day.resets_at).getTime();
|
|
@@ -299,10 +306,10 @@ export const anthropicPlugin: ProviderPlugin = {
|
|
|
299
306
|
|
|
300
307
|
return result;
|
|
301
308
|
} catch (err) {
|
|
302
|
-
log.error(
|
|
309
|
+
log.error("Failed to fetch Anthropic usage", { error: err });
|
|
303
310
|
return {
|
|
304
311
|
fetchedAt: Date.now(),
|
|
305
|
-
error: err instanceof Error ? err.message :
|
|
312
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
306
313
|
};
|
|
307
314
|
}
|
|
308
315
|
},
|
|
@@ -311,10 +318,10 @@ export const anthropicPlugin: ProviderPlugin = {
|
|
|
311
318
|
function getPlanName(subscriptionType?: string): string | null {
|
|
312
319
|
if (!subscriptionType) return null;
|
|
313
320
|
const lower = subscriptionType.toLowerCase();
|
|
314
|
-
if (lower.includes(
|
|
315
|
-
if (lower.includes(
|
|
316
|
-
if (lower.includes(
|
|
317
|
-
if (lower.includes(
|
|
321
|
+
if (lower.includes("max")) return "Max";
|
|
322
|
+
if (lower.includes("pro")) return "Pro";
|
|
323
|
+
if (lower.includes("team")) return "Team";
|
|
324
|
+
if (lower.includes("api")) return null;
|
|
318
325
|
return subscriptionType.charAt(0).toUpperCase() + subscriptionType.slice(1);
|
|
319
326
|
}
|
|
320
327
|
|