@cortexkit/opencode-anthropic-auth 1.0.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/LICENSE +21 -0
- package/README.md +500 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +260 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +665 -0
- package/dist/prompt-context.d.ts +18 -0
- package/dist/prompt-context.js +95 -0
- package/dist/transform.d.ts +77 -0
- package/dist/transform.js +450 -0
- package/package.json +45 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
3
|
+
import { createInterface } from 'node:readline/promises';
|
|
4
|
+
import { authorize, exchange, generateRelayToken, getAccountStoragePath, loadAccounts, saveAccounts, WORKER_SCRIPT, } from '@cortexkit/anthropic-auth-core';
|
|
5
|
+
function defaultStorage() {
|
|
6
|
+
return {
|
|
7
|
+
version: 1,
|
|
8
|
+
main: { type: 'opencode', provider: 'anthropic' },
|
|
9
|
+
fallbackOn: [401, 403, 429],
|
|
10
|
+
refresh: {
|
|
11
|
+
enabled: true,
|
|
12
|
+
intervalMinutes: 10,
|
|
13
|
+
refreshBeforeExpiryMinutes: 30,
|
|
14
|
+
},
|
|
15
|
+
quota: {
|
|
16
|
+
enabled: true,
|
|
17
|
+
checkIntervalMinutes: 5,
|
|
18
|
+
minimumRemaining: {
|
|
19
|
+
five_hour: 10,
|
|
20
|
+
seven_day: 20,
|
|
21
|
+
},
|
|
22
|
+
failClosedOnUnknownQuota: true,
|
|
23
|
+
},
|
|
24
|
+
accounts: [],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function usage() {
|
|
28
|
+
console.log(`Usage:
|
|
29
|
+
opencode-anthropic-auth login [label]
|
|
30
|
+
opencode-anthropic-auth list
|
|
31
|
+
opencode-anthropic-auth relay setup
|
|
32
|
+
|
|
33
|
+
Fallback accounts are stored in:
|
|
34
|
+
${getAccountStoragePath()}`);
|
|
35
|
+
}
|
|
36
|
+
function requireText(value, name) {
|
|
37
|
+
const trimmed = value?.trim();
|
|
38
|
+
if (!trimmed)
|
|
39
|
+
throw new Error(`${name} is required`);
|
|
40
|
+
return trimmed;
|
|
41
|
+
}
|
|
42
|
+
async function cloudflareRequest(options) {
|
|
43
|
+
const response = await fetch(`https://api.cloudflare.com/client/v4${options.path}`, {
|
|
44
|
+
method: options.method,
|
|
45
|
+
headers: {
|
|
46
|
+
authorization: `Bearer ${options.token}`,
|
|
47
|
+
...(options.body instanceof FormData
|
|
48
|
+
? {}
|
|
49
|
+
: { 'content-type': 'application/json' }),
|
|
50
|
+
...options.headers,
|
|
51
|
+
},
|
|
52
|
+
body: options.body,
|
|
53
|
+
});
|
|
54
|
+
const text = await response.text();
|
|
55
|
+
let data;
|
|
56
|
+
try {
|
|
57
|
+
data = JSON.parse(text);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw new Error(`Cloudflare API returned ${response.status}: ${text}`);
|
|
61
|
+
}
|
|
62
|
+
if (!response.ok || data.success === false) {
|
|
63
|
+
const message = data.errors
|
|
64
|
+
?.map((error) => error.message)
|
|
65
|
+
.filter(Boolean)
|
|
66
|
+
.join('; ');
|
|
67
|
+
throw new Error(message || `Cloudflare API returned ${response.status}`);
|
|
68
|
+
}
|
|
69
|
+
return data.result;
|
|
70
|
+
}
|
|
71
|
+
async function createKvNamespace(token, accountId, title) {
|
|
72
|
+
return cloudflareRequest({
|
|
73
|
+
token,
|
|
74
|
+
method: 'POST',
|
|
75
|
+
path: `/accounts/${accountId}/storage/kv/namespaces`,
|
|
76
|
+
body: JSON.stringify({ title }),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async function uploadRelayWorker(options) {
|
|
80
|
+
const metadata = {
|
|
81
|
+
main_module: 'worker.js',
|
|
82
|
+
compatibility_date: '2026-04-28',
|
|
83
|
+
bindings: [
|
|
84
|
+
{
|
|
85
|
+
type: 'kv_namespace',
|
|
86
|
+
name: 'RELAY_STATE',
|
|
87
|
+
namespace_id: options.kvNamespaceId,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: 'secret_text',
|
|
91
|
+
name: 'RELAY_TOKEN',
|
|
92
|
+
text: options.relayToken,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
const form = new FormData();
|
|
97
|
+
form.set('metadata', JSON.stringify(metadata));
|
|
98
|
+
form.set('worker.js', new Blob([WORKER_SCRIPT], { type: 'application/javascript+module' }), 'worker.js');
|
|
99
|
+
return cloudflareRequest({
|
|
100
|
+
token: options.token,
|
|
101
|
+
method: 'PUT',
|
|
102
|
+
path: `/accounts/${options.accountId}/workers/scripts/${options.scriptName}`,
|
|
103
|
+
body: form,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async function enableWorkersDev(token, accountId, scriptName) {
|
|
107
|
+
await cloudflareRequest({
|
|
108
|
+
token,
|
|
109
|
+
method: 'POST',
|
|
110
|
+
path: `/accounts/${accountId}/workers/scripts/${scriptName}/subdomain`,
|
|
111
|
+
body: JSON.stringify({ enabled: true, previews_enabled: false }),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
async function getWorkersSubdomain(token, accountId) {
|
|
115
|
+
return cloudflareRequest({
|
|
116
|
+
token,
|
|
117
|
+
method: 'GET',
|
|
118
|
+
path: `/accounts/${accountId}/workers/subdomain`,
|
|
119
|
+
}).catch(() => null);
|
|
120
|
+
}
|
|
121
|
+
async function relaySetup() {
|
|
122
|
+
const storage = (await loadAccounts()) ?? defaultStorage();
|
|
123
|
+
const token = requireText(process.env.CLOUDFLARE_API_TOKEN?.trim() ||
|
|
124
|
+
(await prompt('Cloudflare API token: ')), 'Cloudflare API token');
|
|
125
|
+
const accountId = requireText(process.env.CLOUDFLARE_ACCOUNT_ID ||
|
|
126
|
+
(await prompt('Cloudflare account ID: ')), 'Cloudflare account ID');
|
|
127
|
+
const scriptName = (await prompt('Worker name [opencode-anthropic-relay]: ')) ||
|
|
128
|
+
'opencode-anthropic-relay';
|
|
129
|
+
const kvTitle = `${scriptName}-state`;
|
|
130
|
+
const relayToken = generateRelayToken();
|
|
131
|
+
console.log('Creating Cloudflare KV namespace...');
|
|
132
|
+
const namespace = await createKvNamespace(token, accountId, kvTitle);
|
|
133
|
+
console.log('Uploading relay Worker...');
|
|
134
|
+
await uploadRelayWorker({
|
|
135
|
+
token,
|
|
136
|
+
accountId,
|
|
137
|
+
scriptName,
|
|
138
|
+
kvNamespaceId: namespace.id,
|
|
139
|
+
relayToken,
|
|
140
|
+
});
|
|
141
|
+
await enableWorkersDev(token, accountId, scriptName).catch((error) => {
|
|
142
|
+
console.warn(`Could not enable workers.dev automatically: ${error instanceof Error ? error.message : String(error)}`);
|
|
143
|
+
});
|
|
144
|
+
const subdomain = await getWorkersSubdomain(token, accountId);
|
|
145
|
+
const defaultUrl = subdomain?.subdomain
|
|
146
|
+
? `https://${scriptName}.${subdomain.subdomain}.workers.dev`
|
|
147
|
+
: '';
|
|
148
|
+
const url = defaultUrl ||
|
|
149
|
+
requireText(await prompt('Relay Worker URL: '), 'Relay Worker URL');
|
|
150
|
+
storage.relay = {
|
|
151
|
+
enabled: true,
|
|
152
|
+
url,
|
|
153
|
+
token: relayToken,
|
|
154
|
+
fallbackToDirect: true,
|
|
155
|
+
transport: 'http',
|
|
156
|
+
};
|
|
157
|
+
await saveAccounts(storage);
|
|
158
|
+
console.log(`Relay enabled at ${url}`);
|
|
159
|
+
console.log(`Config saved to ${getAccountStoragePath()}.`);
|
|
160
|
+
}
|
|
161
|
+
async function prompt(message) {
|
|
162
|
+
const rl = createInterface({ input, output });
|
|
163
|
+
try {
|
|
164
|
+
return (await rl.question(message)).trim();
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
rl.close();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function upsertAccount(storage, account) {
|
|
171
|
+
const index = storage.accounts.findIndex((candidate) => candidate.id === account.id ||
|
|
172
|
+
(account.label && candidate.label === account.label));
|
|
173
|
+
if (index >= 0) {
|
|
174
|
+
storage.accounts[index] = {
|
|
175
|
+
...storage.accounts[index],
|
|
176
|
+
...account,
|
|
177
|
+
addedAt: storage.accounts[index]?.addedAt ?? account.addedAt,
|
|
178
|
+
quota: account.quota,
|
|
179
|
+
lastRefreshedAt: account.lastRefreshedAt,
|
|
180
|
+
lastRefreshError: account.lastRefreshError,
|
|
181
|
+
lastQuotaRefreshError: account.lastQuotaRefreshError,
|
|
182
|
+
};
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
storage.accounts.push(account);
|
|
186
|
+
}
|
|
187
|
+
async function login(labelArg) {
|
|
188
|
+
const storage = (await loadAccounts()) ?? defaultStorage();
|
|
189
|
+
const label = labelArg?.trim() || (await prompt('Fallback account label (optional): '));
|
|
190
|
+
const authorization = await authorize('max');
|
|
191
|
+
console.log('\nOpen this URL in your browser and complete Claude sign-in:\n');
|
|
192
|
+
console.log(`${authorization.url}\n`);
|
|
193
|
+
const code = await prompt('Paste the full callback URL or authorization code here: ');
|
|
194
|
+
const result = await exchange(code, authorization.verifier, authorization.redirectUri, authorization.state);
|
|
195
|
+
if (result.type === 'failed') {
|
|
196
|
+
throw new Error('Authentication failed');
|
|
197
|
+
}
|
|
198
|
+
const now = Date.now();
|
|
199
|
+
upsertAccount(storage, {
|
|
200
|
+
id: label || crypto.randomUUID(),
|
|
201
|
+
label: label || undefined,
|
|
202
|
+
type: 'oauth',
|
|
203
|
+
access: result.access,
|
|
204
|
+
refresh: result.refresh,
|
|
205
|
+
expires: result.expires,
|
|
206
|
+
enabled: true,
|
|
207
|
+
addedAt: now,
|
|
208
|
+
lastUsed: now,
|
|
209
|
+
});
|
|
210
|
+
await saveAccounts(storage);
|
|
211
|
+
console.log(`\nSaved fallback account${label ? ` "${label}"` : ''}.`);
|
|
212
|
+
}
|
|
213
|
+
async function listAccounts() {
|
|
214
|
+
const storage = await loadAccounts();
|
|
215
|
+
if (!storage?.accounts.length) {
|
|
216
|
+
console.log(`No fallback accounts found at ${getAccountStoragePath()}.`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
for (const [index, account] of storage.accounts.entries()) {
|
|
220
|
+
const label = account.label || account.id;
|
|
221
|
+
const status = account.enabled === false ? 'disabled' : 'enabled';
|
|
222
|
+
const fiveHour = account.quota?.five_hour?.remainingPercent;
|
|
223
|
+
const sevenDay = account.quota?.seven_day?.remainingPercent;
|
|
224
|
+
const quota = fiveHour === undefined && sevenDay === undefined
|
|
225
|
+
? 'quota unknown'
|
|
226
|
+
: `5h ${fiveHour ?? '?'}%, 1w ${sevenDay ?? '?'}% remaining`;
|
|
227
|
+
console.log(`${index + 1}. ${label} (${status}) — ${quota}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async function main() {
|
|
231
|
+
const [command, label] = process.argv.slice(2);
|
|
232
|
+
if (!command ||
|
|
233
|
+
command === 'help' ||
|
|
234
|
+
command === '--help' ||
|
|
235
|
+
command === '-h') {
|
|
236
|
+
usage();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (command === 'login') {
|
|
240
|
+
await login(label);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (command === 'list') {
|
|
244
|
+
await listAccounts();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (command === 'relay' && label === 'setup') {
|
|
248
|
+
await relaySetup();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
usage();
|
|
252
|
+
process.exitCode = 1;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
await main();
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
259
|
+
process.exitCode = 1;
|
|
260
|
+
}
|
package/dist/index.d.ts
ADDED