@xtruder/opencode-claude-max-plugin 0.1.8 → 0.1.10

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 CHANGED
@@ -31,13 +31,13 @@ Just add the following to `.opencode/opencode.json` in your project (or `~/.conf
31
31
  "reasoning": true,
32
32
  "tool_call": true,
33
33
  "temperature": true,
34
+ "limit": { "context": 200000, "output": 64000 },
34
35
  "options": {
35
- "thinking": { "type": "enabled", "budgetTokens": 10000 }
36
+ "thinking": { "type": "enabled", "budgetTokens": 1024 }
36
37
  },
37
38
  "variants": {
38
- "normal": { "thinking": { "type": "enabled", "budgetTokens": 1024 } },
39
- "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
40
- "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
39
+ "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
40
+ "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
41
41
  }
42
42
  },
43
43
  "claude-opus-4-6": {
@@ -46,13 +46,13 @@ Just add the following to `.opencode/opencode.json` in your project (or `~/.conf
46
46
  "reasoning": true,
47
47
  "tool_call": true,
48
48
  "temperature": true,
49
+ "limit": { "context": 1000000, "output": 64000 },
49
50
  "options": {
50
51
  "thinking": { "type": "adaptive" }
51
52
  },
52
53
  "variants": {
53
- "normal": { "thinking": { "type": "adaptive" } },
54
- "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
55
- "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
54
+ "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
55
+ "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
56
56
  }
57
57
  },
58
58
  "claude-opus-4-5-20251101": {
@@ -61,13 +61,13 @@ Just add the following to `.opencode/opencode.json` in your project (or `~/.conf
61
61
  "reasoning": true,
62
62
  "tool_call": true,
63
63
  "temperature": true,
64
+ "limit": { "context": 200000, "output": 32000 },
64
65
  "options": {
65
- "thinking": { "type": "enabled", "budgetTokens": 10000 }
66
+ "thinking": { "type": "enabled", "budgetTokens": 1024 }
66
67
  },
67
68
  "variants": {
68
- "normal": { "thinking": { "type": "enabled", "budgetTokens": 1024 } },
69
- "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
70
- "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
69
+ "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
70
+ "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
71
71
  }
72
72
  },
73
73
  "claude-sonnet-4-5-20250929": {
@@ -76,13 +76,13 @@ Just add the following to `.opencode/opencode.json` in your project (or `~/.conf
76
76
  "reasoning": true,
77
77
  "tool_call": true,
78
78
  "temperature": true,
79
+ "limit": { "context": 200000, "output": 64000 },
79
80
  "options": {
80
- "thinking": { "type": "enabled", "budgetTokens": 10000 }
81
+ "thinking": { "type": "enabled", "budgetTokens": 1024 }
81
82
  },
82
83
  "variants": {
83
- "normal": { "thinking": { "type": "enabled", "budgetTokens": 1024 } },
84
- "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
85
- "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
84
+ "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
85
+ "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
86
86
  }
87
87
  },
88
88
  "claude-haiku-4-5-20251001": {
@@ -90,7 +90,8 @@ Just add the following to `.opencode/opencode.json` in your project (or `~/.conf
90
90
  "attachment": false,
91
91
  "reasoning": false,
92
92
  "tool_call": true,
93
- "temperature": true
93
+ "temperature": true,
94
+ "limit": { "context": 200000, "output": 8192 }
94
95
  },
95
96
  "claude-opus-4-1-20250805": {
96
97
  "name": "Claude Opus 4.1",
@@ -98,13 +99,13 @@ Just add the following to `.opencode/opencode.json` in your project (or `~/.conf
98
99
  "reasoning": true,
99
100
  "tool_call": true,
100
101
  "temperature": true,
102
+ "limit": { "context": 200000, "output": 32000 },
101
103
  "options": {
102
- "thinking": { "type": "enabled", "budgetTokens": 10000 }
104
+ "thinking": { "type": "enabled", "budgetTokens": 1024 }
103
105
  },
104
106
  "variants": {
105
- "normal": { "thinking": { "type": "enabled", "budgetTokens": 1024 } },
106
- "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
107
- "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
107
+ "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
108
+ "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
108
109
  }
109
110
  },
110
111
  "claude-opus-4-20250514": {
@@ -113,13 +114,13 @@ Just add the following to `.opencode/opencode.json` in your project (or `~/.conf
113
114
  "reasoning": true,
114
115
  "tool_call": true,
115
116
  "temperature": true,
117
+ "limit": { "context": 200000, "output": 32000 },
116
118
  "options": {
117
- "thinking": { "type": "enabled", "budgetTokens": 10000 }
119
+ "thinking": { "type": "enabled", "budgetTokens": 1024 }
118
120
  },
119
121
  "variants": {
120
- "normal": { "thinking": { "type": "enabled", "budgetTokens": 1024 } },
121
- "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
122
- "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
122
+ "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
123
+ "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
123
124
  }
124
125
  },
125
126
  "claude-sonnet-4-20250514": {
@@ -128,13 +129,13 @@ Just add the following to `.opencode/opencode.json` in your project (or `~/.conf
128
129
  "reasoning": true,
129
130
  "tool_call": true,
130
131
  "temperature": true,
132
+ "limit": { "context": 200000, "output": 64000 },
131
133
  "options": {
132
- "thinking": { "type": "enabled", "budgetTokens": 10000 }
134
+ "thinking": { "type": "enabled", "budgetTokens": 1024 }
133
135
  },
134
136
  "variants": {
135
- "normal": { "thinking": { "type": "enabled", "budgetTokens": 1024 } },
136
- "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
137
- "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
137
+ "high": { "thinking": { "type": "enabled", "budgetTokens": 10000 } },
138
+ "max": { "thinking": { "type": "enabled", "budgetTokens": 32000 } }
138
139
  }
139
140
  },
140
141
  "claude-3-haiku-20240307": {
@@ -142,7 +143,8 @@ Just add the following to `.opencode/opencode.json` in your project (or `~/.conf
142
143
  "attachment": false,
143
144
  "reasoning": false,
144
145
  "tool_call": true,
145
- "temperature": true
146
+ "temperature": true,
147
+ "limit": { "context": 200000, "output": 4096 }
146
148
  }
147
149
  }
148
150
  }
@@ -161,42 +163,6 @@ Credentials are resolved in order:
161
163
 
162
164
  For Claude Code credentials, log in via `claude` CLI first (`claude auth login`).
163
165
 
164
- ## Models
165
-
166
- | Model ID | Name | Thinking | Notes |
167
- |---|---|---|---|
168
- | `claude-opus-4-6` | Claude Opus 4.6 | ✓ | Native 1M context window |
169
- | `claude-sonnet-4-6` | Claude Sonnet 4.6 | ✓ | ~120K token limit on subscription |
170
- | `claude-opus-4-5-20251101` | Claude Opus 4.5 | ✓ | |
171
- | `claude-sonnet-4-5-20250929` | Claude Sonnet 4.5 | ✓ | |
172
- | `claude-haiku-4-5-20251001` | Claude Haiku 4.5 | — | 200K token hard limit |
173
- | `claude-opus-4-1-20250805` | Claude Opus 4.1 | ✓ | |
174
- | `claude-opus-4-20250514` | Claude Opus 4 | ✓ | |
175
- | `claude-sonnet-4-20250514` | Claude Sonnet 4 | ✓ | |
176
- | `claude-3-haiku-20240307` | Claude 3 Haiku | — | |
177
-
178
- ### Thinking variants
179
-
180
- Reasoning models support thinking levels selectable in OpenCode via `/model`:
181
-
182
- | Variant | Opus 4.6 | Other reasoning models |
183
- |---|---|---|
184
- | **Default** | `adaptive` (Claude decides) | `enabled`, 1024 tokens |
185
- | **high** | `enabled`, 10000 tokens | `enabled`, 10000 tokens |
186
- | **max** | `enabled`, 32000 tokens | `enabled`, 32000 tokens |
187
-
188
- Opus 4.6 uses `{ type: "adaptive" }` by default — Claude decides when and how much to think. Other models default to 1024 tokens (matching Claude Code's `al1 = 1024`).
189
-
190
- ## Context Window Limits
191
-
192
- Tested on Claude Max subscription (Extra usage disabled):
193
-
194
- | Model | Included context | Above limit |
195
- |---|---|---|
196
- | Opus 4.6 | ~615K+ tokens (native 1M) | Not tested |
197
- | Sonnet 4.6 | ~120K tokens | 429 — enable Extra usage at claude.ai |
198
- | Haiku 4.5 | 200K tokens (hard limit) | `prompt is too long` error |
199
-
200
166
  ## Features
201
167
 
202
168
  - Streaming and non-streaming completions
@@ -92,13 +92,21 @@ function convertAgentStream(stream, modelId) {
92
92
  }
93
93
  if (msg.type === "result") {
94
94
  const result = msg;
95
+ const cacheRead = result.usage?.cache_read_input_tokens ?? 0;
96
+ const cacheCreate = result.usage?.cache_creation_input_tokens ?? 0;
95
97
  controller.enqueue({
96
98
  type: "finish",
97
99
  finishReason: mapFinishReason(result.stop_reason),
98
100
  usage: {
99
101
  inputTokens: result.usage?.input_tokens ?? 0,
100
102
  outputTokens: result.usage?.output_tokens ?? 0,
101
- totalTokens: (result.usage?.input_tokens ?? 0) + (result.usage?.output_tokens ?? 0)
103
+ totalTokens: (result.usage?.input_tokens ?? 0) + (result.usage?.output_tokens ?? 0) + cacheRead + cacheCreate,
104
+ cachedInputTokens: cacheRead
105
+ },
106
+ providerMetadata: {
107
+ anthropic: {
108
+ cacheCreationInputTokens: cacheCreate
109
+ }
102
110
  }
103
111
  });
104
112
  firstAssistantTurnDone = true;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-model.d.ts","sourceRoot":"","sources":["../src/agent-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAO3B,MAAM,kBAAkB,CAAA;AAMzB,KAAK,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;AACtE,KAAK,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;AAqJ1E,qBAAa,aAAc,YAAW,eAAe;IAajD,OAAO,CAAC,YAAY;IAZtB,QAAQ,CAAC,oBAAoB,EAAG,IAAI,CAAS;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAK;IAErD,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,qBAAqB,CAAI;IACjC,OAAO,CAAC,gBAAgB,CAAgC;gBAItD,OAAO,EAAE,MAAM,EACP,YAAY,EAAE,MAAM;IAM9B;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,aAAa;IAoBf,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,cAAc,CAAC;IA2EtE,UAAU,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+DhF;;OAEG;IACH,KAAK;CAMN"}
1
+ {"version":3,"file":"agent-model.d.ts","sourceRoot":"","sources":["../src/agent-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAO3B,MAAM,kBAAkB,CAAA;AAMzB,KAAK,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;AACtE,KAAK,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;AA6J1E,qBAAa,aAAc,YAAW,eAAe;IAajD,OAAO,CAAC,YAAY;IAZtB,QAAQ,CAAC,oBAAoB,EAAG,IAAI,CAAS;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAK;IAErD,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,qBAAqB,CAAI;IACjC,OAAO,CAAC,gBAAgB,CAAgC;gBAItD,OAAO,EAAE,MAAM,EACP,YAAY,EAAE,MAAM;IAM9B;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,aAAa;IAoBf,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,cAAc,CAAC;IA2EtE,UAAU,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+DhF;;OAEG;IACH,KAAK;CAMN"}
package/build/index.d.ts CHANGED
@@ -51,6 +51,20 @@ export interface AnthropicSDKProvider {
51
51
  */
52
52
  languageModel(modelId: string): LanguageModelV2;
53
53
  }
54
+ /**
55
+ * Cached ratelimit usage from the most recent API response headers.
56
+ * Updated on every successful inference call — same data Claude Code
57
+ * reads for its /usage display. No extra API call needed.
58
+ */
59
+ export interface CachedUsage {
60
+ fiveHourUtil?: number;
61
+ sevenDayUtil?: number;
62
+ fiveHourReset?: number;
63
+ sevenDayReset?: number;
64
+ overageStatus?: string;
65
+ timestamp?: number;
66
+ }
67
+ export declare const cachedUsage: CachedUsage;
54
68
  /**
55
69
  * Create an Anthropic SDK provider for use with OpenCode / Vercel AI SDK.
56
70
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAQvD;;;GAGG;AACH,eAAO,MAAM,WAAW,UAOvB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,0BAA0B,CAAA;AAEtD;;GAEG;AACH,eAAO,MAAM,aAAa,UAGzB,CAAA;AAED,MAAM,WAAW,2BAA2B;IAC1C;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEhC;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;IAE/B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAAA;CAChD;AAgCD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,2BAAgC,GACxC,oBAAoB,CA8FtB;AAGD,eAAe,kBAAkB,CAAA;AAGjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EACL,qBAAqB,EACrB,SAAS,EACT,kBAAkB,EAClB,KAAK,sBAAsB,GAC5B,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAQvD;;;GAGG;AACH,eAAO,MAAM,WAAW,UAOvB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,0BAA0B,CAAA;AAEtD;;GAEG;AACH,eAAO,MAAM,aAAa,UAGzB,CAAA;AAED,MAAM,WAAW,2BAA2B;IAC1C;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEhC;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;IAE/B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IAExB;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,CAAA;CAChD;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,eAAO,MAAM,WAAW,EAAE,WAAgB,CAAA;AAgC1C;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,2BAAgC,GACxC,oBAAoB,CA0GtB;AAGD,eAAe,kBAAkB,CAAA;AAGjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9C,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAClD,OAAO,EACL,qBAAqB,EACrB,SAAS,EACT,kBAAkB,EAClB,KAAK,sBAAsB,GAC5B,MAAM,kBAAkB,CAAA"}
package/build/index.js CHANGED
@@ -265,6 +265,136 @@ function formatToolResultContent(output) {
265
265
  }
266
266
  }
267
267
 
268
+ // src/credentials.ts
269
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
270
+ import { join as join2 } from "node:path";
271
+ import { homedir as homedir2 } from "node:os";
272
+ function getCredentialsPath() {
273
+ return join2(homedir2(), ".claude", ".credentials.json");
274
+ }
275
+ function readClaudeCredentials(path) {
276
+ const credPath = path ?? getCredentialsPath();
277
+ if (!existsSync2(credPath))
278
+ return null;
279
+ try {
280
+ const raw = readFileSync2(credPath, "utf-8");
281
+ const parsed = JSON.parse(raw);
282
+ if (!parsed.claudeAiOauth?.accessToken)
283
+ return null;
284
+ return parsed.claudeAiOauth;
285
+ } catch {
286
+ return null;
287
+ }
288
+ }
289
+ function isExpired(creds) {
290
+ const buffer = 5 * 60 * 1000;
291
+ return Date.now() > creds.expiresAt - buffer;
292
+ }
293
+
294
+ // src/usage.ts
295
+ var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
296
+ async function fetchUsage(credentialsPath) {
297
+ const creds = readClaudeCredentials(credentialsPath);
298
+ if (!creds)
299
+ return null;
300
+ if (isExpired(creds))
301
+ return null;
302
+ const resp = await fetch(USAGE_URL, {
303
+ headers: {
304
+ authorization: `Bearer ${creds.accessToken}`,
305
+ "anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
306
+ "content-type": "application/json",
307
+ "user-agent": "claude-cli/2.1.81 (external, sdk-cli)",
308
+ "x-app": "cli"
309
+ }
310
+ });
311
+ if (!resp.ok) {
312
+ const body = await resp.text().catch(() => "");
313
+ const msg = body.includes("message") ? JSON.parse(body)?.error?.message ?? body : `HTTP ${resp.status}`;
314
+ throw new Error(`Usage API error: ${msg}`);
315
+ }
316
+ return resp.json();
317
+ }
318
+ function progressBar(utilization, width = 50) {
319
+ const filled = Math.round(utilization / 100 * width);
320
+ const bar = "█".repeat(filled) + " ".repeat(width - filled);
321
+ return bar;
322
+ }
323
+ function formatReset(resetsAt) {
324
+ if (!resetsAt)
325
+ return "";
326
+ const reset = new Date(resetsAt);
327
+ const now = new Date;
328
+ const diffMs = reset.getTime() - now.getTime();
329
+ if (diffMs <= 0)
330
+ return "Reset now";
331
+ const diffMin = Math.floor(diffMs / 60000);
332
+ const diffHour = Math.floor(diffMin / 60);
333
+ const timeStr = reset.toLocaleTimeString("en-US", {
334
+ hour: "numeric",
335
+ minute: "2-digit",
336
+ timeZoneName: "short"
337
+ });
338
+ const dateStr = reset.toLocaleDateString("en-US", {
339
+ month: "short",
340
+ day: "numeric"
341
+ });
342
+ if (diffHour < 24) {
343
+ return `Resets ${timeStr}`;
344
+ }
345
+ return `Resets ${dateStr}, ${timeStr}`;
346
+ }
347
+ function formatUsage(data) {
348
+ const lines = [];
349
+ if (data.five_hour) {
350
+ const pct = Math.round(data.five_hour.utilization);
351
+ lines.push(" Current session");
352
+ lines.push(` ${progressBar(pct)} ${pct}% used`);
353
+ lines.push(` ${formatReset(data.five_hour.resets_at)}`);
354
+ lines.push("");
355
+ }
356
+ if (data.seven_day) {
357
+ const pct = Math.round(data.seven_day.utilization);
358
+ lines.push(" Current week (all models)");
359
+ lines.push(` ${progressBar(pct)} ${pct}% used`);
360
+ lines.push(` ${formatReset(data.seven_day.resets_at)}`);
361
+ lines.push("");
362
+ }
363
+ if (data.seven_day_sonnet) {
364
+ const pct = Math.round(data.seven_day_sonnet.utilization);
365
+ lines.push(" Current week (Sonnet only)");
366
+ lines.push(` ${progressBar(pct)} ${pct}% used`);
367
+ if (data.seven_day_sonnet.resets_at) {
368
+ lines.push(` ${formatReset(data.seven_day_sonnet.resets_at)}`);
369
+ }
370
+ lines.push("");
371
+ }
372
+ if (data.seven_day_opus) {
373
+ const pct = Math.round(data.seven_day_opus.utilization);
374
+ lines.push(" Current week (Opus only)");
375
+ lines.push(` ${progressBar(pct)} ${pct}% used`);
376
+ if (data.seven_day_opus.resets_at) {
377
+ lines.push(` ${formatReset(data.seven_day_opus.resets_at)}`);
378
+ }
379
+ lines.push("");
380
+ }
381
+ if (data.extra_usage) {
382
+ if (data.extra_usage.is_enabled) {
383
+ const pct = Math.round(data.extra_usage.utilization ?? 0);
384
+ lines.push(" Extra usage");
385
+ lines.push(` ${progressBar(pct)} ${pct}% used`);
386
+ if (data.extra_usage.monthly_limit != null) {
387
+ lines.push(` $${data.extra_usage.used_credits?.toFixed(2) ?? "0.00"} / $${data.extra_usage.monthly_limit.toFixed(2)}`);
388
+ }
389
+ } else {
390
+ lines.push(" Extra usage: disabled");
391
+ }
392
+ lines.push("");
393
+ }
394
+ return lines.join(`
395
+ `);
396
+ }
397
+
268
398
  // src/tools.ts
269
399
  function cleanSchema(schema) {
270
400
  const cleaned = {};
@@ -352,8 +482,13 @@ function convertStream(anthropicStream, modelId) {
352
482
  usage: {
353
483
  inputTokens: inputTokens ?? 0,
354
484
  outputTokens: outputTokens ?? 0,
355
- totalTokens: (inputTokens ?? 0) + (outputTokens ?? 0),
485
+ totalTokens: (inputTokens ?? 0) + (outputTokens ?? 0) + (cachedInputTokens ?? 0) + (cacheCreationTokens ?? 0),
356
486
  cachedInputTokens
487
+ },
488
+ providerMetadata: {
489
+ anthropic: {
490
+ cacheCreationInputTokens: cacheCreationTokens ?? 0
491
+ }
357
492
  }
358
493
  });
359
494
  }
@@ -521,6 +656,74 @@ function buildMetadata() {
521
656
  user_id: JSON.stringify({ device_id: DEVICE_ID })
522
657
  };
523
658
  }
659
+ function isUsageCommand(prompt) {
660
+ for (let i = prompt.length - 1;i >= 0; i--) {
661
+ const msg = prompt[i];
662
+ if (msg.role !== "user")
663
+ continue;
664
+ const text = msg.content.filter((p) => p.type === "text").map((p) => p.text).join("").trim();
665
+ return /^\/usage(\s|$)/i.test(text);
666
+ }
667
+ return false;
668
+ }
669
+ function formatReset2(epoch) {
670
+ if (!epoch)
671
+ return "";
672
+ const reset = new Date(epoch * 1000);
673
+ const now = new Date;
674
+ const diffMs = reset.getTime() - now.getTime();
675
+ if (diffMs <= 0)
676
+ return "Reset now";
677
+ const timeStr = reset.toLocaleTimeString("en-US", {
678
+ hour: "numeric",
679
+ minute: "2-digit",
680
+ timeZoneName: "short"
681
+ });
682
+ const dateStr = reset.toLocaleDateString("en-US", { month: "short", day: "numeric" });
683
+ return diffMs < 86400000 ? `Resets ${timeStr}` : `Resets ${dateStr}, ${timeStr}`;
684
+ }
685
+ function formatCachedUsage(usage) {
686
+ const bar = (pct, width = 50) => "█".repeat(Math.round(pct / 100 * width)) + " ".repeat(width - Math.round(pct / 100 * width));
687
+ const lines = [];
688
+ const h5 = Math.round((usage.fiveHourUtil ?? 0) * 100);
689
+ lines.push(" Current session");
690
+ lines.push(` ${bar(h5)} ${h5}% used`);
691
+ if (usage.fiveHourReset)
692
+ lines.push(` ${formatReset2(usage.fiveHourReset)}`);
693
+ lines.push("");
694
+ const d7 = Math.round((usage.sevenDayUtil ?? 0) * 100);
695
+ lines.push(" Current week");
696
+ lines.push(` ${bar(d7)} ${d7}% used`);
697
+ if (usage.sevenDayReset)
698
+ lines.push(` ${formatReset2(usage.sevenDayReset)}`);
699
+ lines.push("");
700
+ if (usage.overageStatus === "rejected") {
701
+ lines.push(" Extra usage: disabled");
702
+ } else if (usage.overageStatus) {
703
+ lines.push(` Extra usage: ${usage.overageStatus}`);
704
+ }
705
+ return lines.join(`
706
+ `);
707
+ }
708
+ async function handleUsageCommand() {
709
+ if (cachedUsage.timestamp) {
710
+ throw new Error(`Claude Subscription Usage
711
+ ` + "─".repeat(52) + `
712
+ ` + formatCachedUsage(cachedUsage));
713
+ }
714
+ try {
715
+ const data = await fetchUsage();
716
+ if (data) {
717
+ throw new Error(`Claude Subscription Usage
718
+ ` + "─".repeat(52) + `
719
+ ` + formatUsage(data));
720
+ }
721
+ } catch (e) {
722
+ if (e.message?.startsWith("Claude Subscription Usage"))
723
+ throw e;
724
+ }
725
+ throw new Error("No usage data available. Send a message first, then try /usage again.");
726
+ }
524
727
 
525
728
  class AnthropicSDKModel {
526
729
  client;
@@ -652,6 +855,8 @@ class AnthropicSDKModel {
652
855
  return { params, warnings };
653
856
  }
654
857
  async doGenerate(options) {
858
+ if (isUsageCommand(options.prompt))
859
+ await handleUsageCommand();
655
860
  const { params, warnings } = this.buildParams(options);
656
861
  if (!options.maxOutputTokens && params.max_tokens > 4096) {
657
862
  params.max_tokens = 4096;
@@ -692,14 +897,21 @@ class AnthropicSDKModel {
692
897
  }
693
898
  }
694
899
  }
900
+ const cacheReadTokens = response.usage.cache_read_input_tokens ?? 0;
901
+ const cacheCreateTokens = response.usage.cache_creation_input_tokens ?? 0;
695
902
  return {
696
903
  content,
697
904
  finishReason: mapFinishReason2(response.stop_reason),
698
905
  usage: {
699
906
  inputTokens: response.usage.input_tokens,
700
907
  outputTokens: response.usage.output_tokens,
701
- totalTokens: response.usage.input_tokens + response.usage.output_tokens,
702
- cachedInputTokens: response.usage.cache_read_input_tokens
908
+ totalTokens: response.usage.input_tokens + response.usage.output_tokens + cacheReadTokens + cacheCreateTokens,
909
+ cachedInputTokens: cacheReadTokens
910
+ },
911
+ providerMetadata: {
912
+ anthropic: {
913
+ cacheCreationInputTokens: cacheCreateTokens
914
+ }
703
915
  },
704
916
  response: {
705
917
  id: response.id,
@@ -710,6 +922,8 @@ class AnthropicSDKModel {
710
922
  };
711
923
  }
712
924
  async doStream(options) {
925
+ if (isUsageCommand(options.prompt))
926
+ await handleUsageCommand();
713
927
  const { params, warnings } = this.buildParams(options);
714
928
  let anthropicStream;
715
929
  try {
@@ -748,32 +962,6 @@ class AnthropicSDKModel {
748
962
  }
749
963
  }
750
964
 
751
- // src/credentials.ts
752
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
753
- import { join as join2 } from "node:path";
754
- import { homedir as homedir2 } from "node:os";
755
- function getCredentialsPath() {
756
- return join2(homedir2(), ".claude", ".credentials.json");
757
- }
758
- function readClaudeCredentials(path) {
759
- const credPath = path ?? getCredentialsPath();
760
- if (!existsSync2(credPath))
761
- return null;
762
- try {
763
- const raw = readFileSync2(credPath, "utf-8");
764
- const parsed = JSON.parse(raw);
765
- if (!parsed.claudeAiOauth?.accessToken)
766
- return null;
767
- return parsed.claudeAiOauth;
768
- } catch {
769
- return null;
770
- }
771
- }
772
- function isExpired(creds) {
773
- const buffer = 5 * 60 * 1000;
774
- return Date.now() > creds.expiresAt - buffer;
775
- }
776
-
777
965
  // src/index.ts
778
966
  var CLAUDE_CODE_VERSION = "2.1.81";
779
967
  var OAUTH_BETAS = [
@@ -789,6 +977,7 @@ var API_KEY_BETAS = [
789
977
  "interleaved-thinking-2025-05-14",
790
978
  "fine-grained-tool-streaming-2025-05-14"
791
979
  ];
980
+ var cachedUsage = {};
792
981
  function resolveAuth(options) {
793
982
  if (options.apiKey) {
794
983
  return { apiKey: options.apiKey, isOAuth: false };
@@ -838,6 +1027,15 @@ function createAnthropicSDK(options = {}) {
838
1027
  }
839
1028
  }
840
1029
  const resp = await baseFetch(url, init);
1030
+ const h5 = resp.headers.get("anthropic-ratelimit-unified-5h-utilization");
1031
+ if (h5 != null) {
1032
+ cachedUsage.fiveHourUtil = parseFloat(h5);
1033
+ cachedUsage.sevenDayUtil = parseFloat(resp.headers.get("anthropic-ratelimit-unified-7d-utilization") ?? "0");
1034
+ cachedUsage.fiveHourReset = parseInt(resp.headers.get("anthropic-ratelimit-unified-5h-reset") ?? "0");
1035
+ cachedUsage.sevenDayReset = parseInt(resp.headers.get("anthropic-ratelimit-unified-7d-reset") ?? "0");
1036
+ cachedUsage.overageStatus = resp.headers.get("anthropic-ratelimit-unified-overage-status") ?? undefined;
1037
+ cachedUsage.timestamp = Date.now();
1038
+ }
841
1039
  if (resp.status === 429) {
842
1040
  const unifiedStatus = resp.headers.get("anthropic-ratelimit-unified-status") ?? "";
843
1041
  const retryAfter = parseInt(resp.headers.get("retry-after") ?? "0");
@@ -877,6 +1075,7 @@ export {
877
1075
  getCredentialsPath,
878
1076
  src_default as default,
879
1077
  createAnthropicSDK,
1078
+ cachedUsage,
880
1079
  OAUTH_BETAS,
881
1080
  CONTEXT_1M_BETA,
882
1081
  AnthropicSDKModel,
@@ -1 +1 @@
1
- {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAK3B,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAQ9C,KAAK,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;AAC1E,KAAK,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;AAqGtE,qBAAa,iBAAkB,YAAW,eAAe;IAQrD,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IATjB,QAAQ,CAAC,oBAAoB,EAAG,IAAI,CAAS;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAK;gBAGnD,OAAO,EAAE,MAAM,EACP,MAAM,EAAE,SAAS,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,OAAe;IAMlC,OAAO,CAAC,WAAW;IAsLb,UAAU,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsE1E,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,cAAc,CAAC;CA2C7E"}
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,0BAA0B,EAK3B,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAU9C,KAAK,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;AAC1E,KAAK,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;AAmMtE,qBAAa,iBAAkB,YAAW,eAAe;IAQrD,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,OAAO;IATjB,QAAQ,CAAC,oBAAoB,EAAG,IAAI,CAAS;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAK;gBAGnD,OAAO,EAAE,MAAM,EACP,MAAM,EAAE,SAAS,EACjB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,OAAe;IAMlC,OAAO,CAAC,WAAW;IAsLb,UAAU,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+E1E,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,cAAc,CAAC;CA4C7E"}
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAG9C,KAAK,kBAAkB,GAAG,SAAS,CAAC,kBAAkB,CAAA;AAiCtD,wBAAgB,aAAa,CAC3B,eAAe,EAAE,aAAa,CAAC,kBAAkB,CAAC,EAClD,OAAO,EAAE,MAAM,GACd,cAAc,CAAC,yBAAyB,CAAC,CA4C3C"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,SAAS,MAAM,mBAAmB,CAAA;AAG9C,KAAK,kBAAkB,GAAG,SAAS,CAAC,kBAAkB,CAAA;AAiCtD,wBAAgB,aAAa,CAC3B,eAAe,EAAE,aAAa,CAAC,kBAAkB,CAAC,EAClD,OAAO,EAAE,MAAM,GACd,cAAc,CAAC,yBAAyB,CAAC,CAiD3C"}
@@ -43,8 +43,11 @@ async function fetchUsage(credentialsPath) {
43
43
  "x-app": "cli"
44
44
  }
45
45
  });
46
- if (!resp.ok)
47
- return null;
46
+ if (!resp.ok) {
47
+ const body = await resp.text().catch(() => "");
48
+ const msg = body.includes("message") ? JSON.parse(body)?.error?.message ?? body : `HTTP ${resp.status}`;
49
+ throw new Error(`Usage API error: ${msg}`);
50
+ }
48
51
  return resp.json();
49
52
  }
50
53
  function progressBar(utilization, width = 50) {
@@ -128,7 +131,13 @@ function formatUsage(data) {
128
131
  }
129
132
 
130
133
  // src/usage-cli.ts
131
- var data = await fetchUsage();
134
+ var data;
135
+ try {
136
+ data = await fetchUsage();
137
+ } catch (e) {
138
+ console.error(e.message ?? "Could not fetch usage.");
139
+ process.exit(1);
140
+ }
132
141
  if (!data) {
133
142
  console.error("Could not fetch usage. Make sure you're logged into Claude Code (~/.claude/.credentials.json).");
134
143
  process.exit(1);
@@ -1 +1 @@
1
- {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;IAC7B,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;IAC7B,gBAAgB,EAAE,WAAW,GAAG,IAAI,CAAA;IACpC,cAAc,EAAE,WAAW,GAAG,IAAI,CAAA;IAClC,oBAAoB,EAAE,WAAW,GAAG,IAAI,CAAA;IACxC,gBAAgB,EAAE,WAAW,GAAG,IAAI,CAAA;IACpC,WAAW,EAAE;QACX,UAAU,EAAE,OAAO,CAAA;QACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;QAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;QAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAC3B,GAAG,IAAI,CAAA;CACT;AAID;;;GAGG;AACH,wBAAsB,UAAU,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAiBpF;AA0CD;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAsDnD"}
1
+ {"version":3,"file":"usage.d.ts","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;IAC7B,SAAS,EAAE,WAAW,GAAG,IAAI,CAAA;IAC7B,gBAAgB,EAAE,WAAW,GAAG,IAAI,CAAA;IACpC,cAAc,EAAE,WAAW,GAAG,IAAI,CAAA;IAClC,oBAAoB,EAAE,WAAW,GAAG,IAAI,CAAA;IACxC,gBAAgB,EAAE,WAAW,GAAG,IAAI,CAAA;IACpC,WAAW,EAAE;QACX,UAAU,EAAE,OAAO,CAAA;QACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;QAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;QAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAC3B,GAAG,IAAI,CAAA;CACT;AAID;;;GAGG;AACH,wBAAsB,UAAU,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAuBpF;AA0CD;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAsDnD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtruder/opencode-claude-max-plugin",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "OpenCode plugin for Claude Max/Pro subscription via @anthropic-ai/sdk - uses Claude Code OAuth credentials",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -13,10 +13,6 @@
13
13
  "./agent": {
14
14
  "import": "./build/agent-index.js",
15
15
  "types": "./build/agent-index.d.ts"
16
- },
17
- "./plugin": {
18
- "import": "./build/plugin.js",
19
- "types": "./build/plugin.d.ts"
20
16
  }
21
17
  },
22
18
  "bin": {
@@ -26,7 +22,7 @@
26
22
  "build"
27
23
  ],
28
24
  "scripts": {
29
- "build": "bun build ./src/index.ts ./src/agent-index.ts ./src/usage-cli.ts ./src/plugin.ts --outdir ./build --target node --format esm --packages external && bun x tsc --emitDeclarationOnly --declaration --outDir build",
25
+ "build": "bun build ./src/index.ts ./src/agent-index.ts ./src/usage-cli.ts --outdir ./build --target node --format esm --packages external && bun x tsc --emitDeclarationOnly --declaration --outDir build",
30
26
  "test": "bun run src/test.ts"
31
27
  },
32
28
  "dependencies": {
@@ -39,7 +35,6 @@
39
35
  "devDependencies": {
40
36
  "@ai-sdk/provider": "2.0.1",
41
37
  "@ai-sdk/provider-utils": "3.0.2",
42
- "@opencode-ai/plugin": "^1.3.0",
43
38
  "ai": "5.0.124",
44
39
  "bun-types": "^1.3.11",
45
40
  "typescript": "^5.7.0"
package/build/plugin.d.ts DELETED
@@ -1,14 +0,0 @@
1
- /**
2
- * OpenCode Plugin entry point for @xtruder/opencode-claude-max-plugin.
3
- *
4
- * Registers a /usage command that fetches Claude subscription usage directly
5
- * from the Anthropic OAuth API — no bash, no markdown file required.
6
- *
7
- * Register in opencode.json:
8
- * "plugin": ["@xtruder/opencode-claude-max-plugin/plugin"]
9
- */
10
- import type { Plugin } from "@opencode-ai/plugin";
11
- declare const UsagePlugin: Plugin;
12
- export default UsagePlugin;
13
- export { UsagePlugin };
14
- //# sourceMappingURL=plugin.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA+CjD,QAAA,MAAM,WAAW,EAAE,MAgClB,CAAA;AAED,eAAe,WAAW,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,CAAA"}
package/build/plugin.js DELETED
@@ -1,175 +0,0 @@
1
- // src/credentials.ts
2
- import { readFileSync, existsSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { homedir } from "node:os";
5
- function getCredentialsPath() {
6
- return join(homedir(), ".claude", ".credentials.json");
7
- }
8
- function readClaudeCredentials(path) {
9
- const credPath = path ?? getCredentialsPath();
10
- if (!existsSync(credPath))
11
- return null;
12
- try {
13
- const raw = readFileSync(credPath, "utf-8");
14
- const parsed = JSON.parse(raw);
15
- if (!parsed.claudeAiOauth?.accessToken)
16
- return null;
17
- return parsed.claudeAiOauth;
18
- } catch {
19
- return null;
20
- }
21
- }
22
- function isExpired(creds) {
23
- const buffer = 5 * 60 * 1000;
24
- return Date.now() > creds.expiresAt - buffer;
25
- }
26
-
27
- // src/usage.ts
28
- var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
29
- async function fetchUsage(credentialsPath) {
30
- const creds = readClaudeCredentials(credentialsPath);
31
- if (!creds)
32
- return null;
33
- if (isExpired(creds))
34
- return null;
35
- const resp = await fetch(USAGE_URL, {
36
- headers: {
37
- authorization: `Bearer ${creds.accessToken}`,
38
- "anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
39
- "content-type": "application/json",
40
- "user-agent": "claude-cli/2.1.81 (external, sdk-cli)",
41
- "x-app": "cli"
42
- }
43
- });
44
- if (!resp.ok)
45
- return null;
46
- return resp.json();
47
- }
48
- function progressBar(utilization, width = 50) {
49
- const filled = Math.round(utilization / 100 * width);
50
- const bar = "█".repeat(filled) + " ".repeat(width - filled);
51
- return bar;
52
- }
53
- function formatReset(resetsAt) {
54
- if (!resetsAt)
55
- return "";
56
- const reset = new Date(resetsAt);
57
- const now = new Date;
58
- const diffMs = reset.getTime() - now.getTime();
59
- if (diffMs <= 0)
60
- return "Reset now";
61
- const diffMin = Math.floor(diffMs / 60000);
62
- const diffHour = Math.floor(diffMin / 60);
63
- const timeStr = reset.toLocaleTimeString("en-US", {
64
- hour: "numeric",
65
- minute: "2-digit",
66
- timeZoneName: "short"
67
- });
68
- const dateStr = reset.toLocaleDateString("en-US", {
69
- month: "short",
70
- day: "numeric"
71
- });
72
- if (diffHour < 24) {
73
- return `Resets ${timeStr}`;
74
- }
75
- return `Resets ${dateStr}, ${timeStr}`;
76
- }
77
- function formatUsage(data) {
78
- const lines = [];
79
- if (data.five_hour) {
80
- const pct = Math.round(data.five_hour.utilization);
81
- lines.push(" Current session");
82
- lines.push(` ${progressBar(pct)} ${pct}% used`);
83
- lines.push(` ${formatReset(data.five_hour.resets_at)}`);
84
- lines.push("");
85
- }
86
- if (data.seven_day) {
87
- const pct = Math.round(data.seven_day.utilization);
88
- lines.push(" Current week (all models)");
89
- lines.push(` ${progressBar(pct)} ${pct}% used`);
90
- lines.push(` ${formatReset(data.seven_day.resets_at)}`);
91
- lines.push("");
92
- }
93
- if (data.seven_day_sonnet) {
94
- const pct = Math.round(data.seven_day_sonnet.utilization);
95
- lines.push(" Current week (Sonnet only)");
96
- lines.push(` ${progressBar(pct)} ${pct}% used`);
97
- if (data.seven_day_sonnet.resets_at) {
98
- lines.push(` ${formatReset(data.seven_day_sonnet.resets_at)}`);
99
- }
100
- lines.push("");
101
- }
102
- if (data.seven_day_opus) {
103
- const pct = Math.round(data.seven_day_opus.utilization);
104
- lines.push(" Current week (Opus only)");
105
- lines.push(` ${progressBar(pct)} ${pct}% used`);
106
- if (data.seven_day_opus.resets_at) {
107
- lines.push(` ${formatReset(data.seven_day_opus.resets_at)}`);
108
- }
109
- lines.push("");
110
- }
111
- if (data.extra_usage) {
112
- if (data.extra_usage.is_enabled) {
113
- const pct = Math.round(data.extra_usage.utilization ?? 0);
114
- lines.push(" Extra usage");
115
- lines.push(` ${progressBar(pct)} ${pct}% used`);
116
- if (data.extra_usage.monthly_limit != null) {
117
- lines.push(` $${data.extra_usage.used_credits?.toFixed(2) ?? "0.00"} / $${data.extra_usage.monthly_limit.toFixed(2)}`);
118
- }
119
- } else {
120
- lines.push(" Extra usage: disabled");
121
- }
122
- lines.push("");
123
- }
124
- return lines.join(`
125
- `);
126
- }
127
-
128
- // src/plugin.ts
129
- var COMMAND_NAME = "usage";
130
- async function buildUsageText(credentialsPath) {
131
- const data = await fetchUsage(credentialsPath);
132
- if (!data) {
133
- return `Display this message verbatim to the user:
134
-
135
- ` + " Could not fetch Claude usage. " + "Make sure you are logged into Claude Code (~/.claude/.credentials.json).";
136
- }
137
- return "Display the following Claude subscription usage verbatim to the user. " + `Do not add any commentary, formatting changes, or explanation:
138
-
139
- ` + ` Claude Subscription Usage
140
- ` + " " + "─".repeat(52) + `
141
- ` + formatUsage(data);
142
- }
143
- function replaceTextInParts(parts, text) {
144
- const idx = parts.findIndex((p) => p.type === "text");
145
- if (idx >= 0) {
146
- parts[idx] = { ...parts[idx], text };
147
- } else if (parts.length > 0) {
148
- parts[0] = { ...parts[0], text };
149
- } else {
150
- parts.push({ type: "text", text });
151
- }
152
- }
153
- var UsagePlugin = async (_ctx) => {
154
- return {
155
- "command.execute.before": async (input, output) => {
156
- if (input.command.toLowerCase() !== COMMAND_NAME)
157
- return;
158
- const text = await buildUsageText();
159
- replaceTextInParts(output.parts, text);
160
- },
161
- "chat.message": async (_input, output) => {
162
- const parts = output.parts;
163
- const idx = parts.findIndex((p) => p.type === "text" && typeof p.text === "string" && p.text.trim().toLowerCase().startsWith(`/${COMMAND_NAME}`));
164
- if (idx < 0)
165
- return;
166
- const text = await buildUsageText();
167
- parts[idx] = { ...parts[idx], text };
168
- }
169
- };
170
- };
171
- var plugin_default = UsagePlugin;
172
- export {
173
- plugin_default as default,
174
- UsagePlugin
175
- };