@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 +31 -65
- package/build/agent-index.js +9 -1
- package/build/agent-model.d.ts.map +1 -1
- package/build/index.d.ts +14 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +228 -29
- package/build/model.d.ts.map +1 -1
- package/build/stream.d.ts.map +1 -1
- package/build/usage-cli.js +12 -3
- package/build/usage.d.ts.map +1 -1
- package/package.json +2 -7
- package/build/plugin.d.ts +0 -14
- package/build/plugin.d.ts.map +0 -1
- package/build/plugin.js +0 -175
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":
|
|
36
|
+
"thinking": { "type": "enabled", "budgetTokens": 1024 }
|
|
36
37
|
},
|
|
37
38
|
"variants": {
|
|
38
|
-
"
|
|
39
|
-
"
|
|
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
|
-
"
|
|
54
|
-
"
|
|
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":
|
|
66
|
+
"thinking": { "type": "enabled", "budgetTokens": 1024 }
|
|
66
67
|
},
|
|
67
68
|
"variants": {
|
|
68
|
-
"
|
|
69
|
-
"
|
|
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":
|
|
81
|
+
"thinking": { "type": "enabled", "budgetTokens": 1024 }
|
|
81
82
|
},
|
|
82
83
|
"variants": {
|
|
83
|
-
"
|
|
84
|
-
"
|
|
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":
|
|
104
|
+
"thinking": { "type": "enabled", "budgetTokens": 1024 }
|
|
103
105
|
},
|
|
104
106
|
"variants": {
|
|
105
|
-
"
|
|
106
|
-
"
|
|
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":
|
|
119
|
+
"thinking": { "type": "enabled", "budgetTokens": 1024 }
|
|
118
120
|
},
|
|
119
121
|
"variants": {
|
|
120
|
-
"
|
|
121
|
-
"
|
|
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":
|
|
134
|
+
"thinking": { "type": "enabled", "budgetTokens": 1024 }
|
|
133
135
|
},
|
|
134
136
|
"variants": {
|
|
135
|
-
"
|
|
136
|
-
"
|
|
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
|
package/build/agent-index.js
CHANGED
|
@@ -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;
|
|
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
|
*
|
package/build/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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:
|
|
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,
|
package/build/model.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/build/stream.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/build/usage-cli.js
CHANGED
|
@@ -43,8 +43,11 @@ async function fetchUsage(credentialsPath) {
|
|
|
43
43
|
"x-app": "cli"
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
|
-
if (!resp.ok)
|
|
47
|
-
|
|
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
|
|
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);
|
package/build/usage.d.ts.map
CHANGED
|
@@ -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,
|
|
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.
|
|
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
|
|
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
|
package/build/plugin.d.ts.map
DELETED
|
@@ -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
|
-
};
|